为 ChatGPT 网页版的 GDScript 代码片段提供语法高亮,自动适配暗色/亮色主题,基于 Prism.js。
// ==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);
})();