ChatGPT GDScript Syntax Highlighter

Highlights GDScript code in the ChatGPT web interface using Prism.js.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         ChatGPT GDScript Syntax Highlighter
// @name:zh-CN   ChatGPT GDScript 代码高亮
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Highlights GDScript code in the ChatGPT web interface using Prism.js.
// @description:zh-CN 为 ChatGPT 网页版的 GDScript 代码片段提供语法高亮,自动适配暗色/亮色主题,基于 Prism.js。
// @author       Anonymous
// @match        https://chatgpt.com/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-gdscript.min.js
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    if (typeof Prism === 'undefined') {
        console.error('❌ [GDScript Highlighter] 核心库 Prism.js 未加载。');
        return;
    }

    // 1. 创建 TrustedHTML 策略 (应对潜在的 CSP 限制)
    let ttPolicy;
    if (window.trustedTypes && window.trustedTypes.createPolicy) {
        try {
            const policyName = 'gdscript-policy-gpt-' + Math.random().toString(36).substring(2, 9);
            ttPolicy = window.trustedTypes.createPolicy(policyName, {
                createHTML: (string) => string
            });
        } catch (e) {
            console.error('❌ [GDScript Highlighter] TrustedTypes 策略创建失败:', e);
        }
    }

    // 2. 实时检测 ChatGPT 网页的实际主题模式
    function updateThemeMode() {
        const textColor = window.getComputedStyle(document.body).color;
        const match = textColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
        let isDarkMode = true;

        if (match) {
            const r = parseInt(match[1], 10);
            const g = parseInt(match[2], 10);
            const b = parseInt(match[3], 10);
            // 通过 RGB 计算视觉亮度 (Luma)
            const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
            isDarkMode = luma > 128; // 亮度大于 128 (文字发白),即为暗色背景
        }
        document.documentElement.setAttribute('data-gds-theme', isDarkMode ? 'dark' : 'light');
    }

    const themeObserver = new MutationObserver((mutations) => {
        for (let m of mutations) {
            if (m.type === 'attributes' && (m.attributeName === 'class' || m.attributeName === 'style' || m.attributeName === 'data-theme')) {
                updateThemeMode();
                break;
            }
        }
    });
    // ChatGPT 的主题通常挂载在 html class 上(例如 "light" 或 "dark")
    themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style', 'data-theme'] });
    themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'style'] });
    updateThemeMode();

    // 3. 注入统一的高亮样式 (兼容 ChatGPT 的 div 容器)
    GM_addStyle(`
        .gdscript-injected {
            background: transparent !important;
            text-shadow: none !important;
            font-family: inherit !important;
            tab-size: 4 !important;
        }

        /* ====== 浅色主题 ====== */
        html[data-gds-theme="light"] .gdscript-injected { color: #24292e !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.comment { color: #6a737d !important; font-style: italic !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.punctuation { color: #24292e !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.keyword { color: #d73a49 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.function { color: #6f42c1 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.string { color: #032f62 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.number { color: #005cc5 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.class-name,
        html[data-gds-theme="light"] .gdscript-injected .token.builtin { color: #e36209 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.operator { color: #d73a49 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.boolean,
        html[data-gds-theme="light"] .gdscript-injected .token.property,
        html[data-gds-theme="light"] .gdscript-injected .token.constant { color: #005cc5 !important; }
        html[data-gds-theme="light"] .gdscript-injected .token.variable { color: #e36209 !important; }

        /* ====== 深色主题 ====== */
        html[data-gds-theme="dark"] .gdscript-injected { color: #d4d4d4 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.comment { color: #6a9955 !important; font-style: italic !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.punctuation { color: #d4d4d4 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.keyword { color: #569cd6 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.function { color: #dcdcaa !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.string { color: #ce9178 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.number { color: #b5cea8 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.class-name,
        html[data-gds-theme="dark"] .gdscript-injected .token.builtin { color: #4ec9b0 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.operator { color: #d4d4d4 !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.boolean,
        html[data-gds-theme="dark"] .gdscript-injected .token.property,
        html[data-gds-theme="dark"] .gdscript-injected .token.variable { color: #9cdcfe !important; }
        html[data-gds-theme="dark"] .gdscript-injected .token.constant { color: #4fc1ff !important; }
    `);

    // 4. 执行 GDScript 识别与渲染 (适配 ChatGPT 结构)
    function highlightGDScript() {
        const pres = document.querySelectorAll('pre');

        pres.forEach(pre => {
            // 查找顶部标题栏区域,ChatGPT 通常在这里标明语言
            const header = pre.querySelector('.bg-token-bg-elevated-secondary');

            // 验证是否包含 gdscript 标识
            if (header && header.textContent.toLowerCase().includes('gdscript')) {
                // ChatGPT 代码正文放置在 .cm-content 内
                const contentDiv = pre.querySelector('.cm-content');
                if (contentDiv) {
                    // 使用 innerText 可以自然提取出带 \n 的纯文本
                    const rawText = contentDiv.innerText;

                    // 避免打断流式输出
                    if (contentDiv.dataset.rawText === rawText) return;
                    contentDiv.dataset.rawText = rawText;

                    contentDiv.classList.add('gdscript-injected', 'language-gdscript');

                    // 利用 Prism.js 生成标准高亮 HTML
                    const highlightedHTML = Prism.highlight(rawText, Prism.languages.gdscript, 'gdscript');

                    // 核心适配:将 \n 转换回 <br>,以兼容 ChatGPT 的 CodeMirror div 结构
                    const finalHTML = highlightedHTML.replace(/\n/g, '<br>');

                    if (ttPolicy) {
                        contentDiv.innerHTML = ttPolicy.createHTML(finalHTML);
                    } else {
                        contentDiv.innerHTML = finalHTML;
                    }
                }
            }
        });
    }

    // 5. 监听页面变化,适配流式输出
    let debounceTimer = null;
    const domObserver = new MutationObserver(() => {
        clearTimeout(debounceTimer);
        debounceTimer = setTimeout(highlightGDScript, 300);
    });

    domObserver.observe(document.body, { childList: true, subtree: true, characterData: true });

    // 初始化执行
    setTimeout(highlightGDScript, 500);

})();