ChatGPT GDScript Syntax Highlighter

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);

})();