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);

})();