adbrt.network JSON数据高亮(多主题)

高亮并格式化显示页面内 JSON 数据,支持多主题切换

当前为 2025-06-12 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         adbrt.network JSON数据高亮(多主题)
// @namespace    http://tampermonkey.net/
// @version      1.0.3
// @description  高亮并格式化显示页面内 JSON 数据,支持多主题切换
// @author       slowFever
// @match        https://partner.adbrt.network/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @icon         https://www.google.com/s2/favicons?sz=64&domain=adbrt.network
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // JSON 高亮配色主题
    const themes = [
        {
            name: 'vsCode dark',
            css: `
            .json-block {
                background: #1e1e1e;
                color: #d4d4d4;
            }
            .json-key { color: #ff6670; }
            .json-string { color: #a4ce78; }
            .json-number { color: #a4ce78; }
            .json-boolean { color: #569cd6; }
            .json-null { color: #c586c0; }
        `
        },
        {
            name: '暗黑',
            css: `
            .json-block {
                background: #2e2e2e;
                color: #ddd;
            }
            .json-key { color: #61afef; }
            .json-string { color: #98c379; }
            .json-number { color: #d19a66; }
            .json-boolean { color: #56b6c2; }
            .json-null { color: #c678dd; }
        `
        },
        {
            name: '亮白',
            css: `
            .json-block {
                background: #f8f8f8;
                color: #333;
            }
            .json-key { color: #007acc; }
            .json-string { color: #008000; }
            .json-number { color: #b05a00; }
            .json-boolean { color: #006080; }
            .json-null { color: #800080; }
        `
        },
        {
            name: '夜蓝',
            css: `
            .json-block {
                background: #001f3f;
                color: #d1ecff;
            }
            .json-key { color: #7FDBFF; }
            .json-string { color: #2ECC40; }
            .json-number { color: #FFDC00; }
            .json-boolean { color: #FF851B; }
            .json-null { color: #B10DC9; }
        `
        },
        {
            name: '暗红紫',
            css: `
        .json-block {
            background: #1a0000;
            color: #ffcccc;
        }
        .json-key { color: #ff3333; }
        .json-string { color: #ff6666; }
        .json-number { color: #ff4040; }
        .json-boolean { color: #cc0000; }
        .json-null { color: #990000; }
        `
        },
        {
            name: '深紫幻',
            css: `
            .json-block {
                background: #1e0033;
                color: #f3e6ff;
            }
            .json-key { color: #c084fc; }
            .json-string { color: #d8b4fe; }
            .json-number { color: #e879f9; }
            .json-boolean { color: #a855f7; }
            .json-null { color: #d946ef; }
        `
        },
        {
            name: '霓虹青紫',
            css: `
        .json-block {
            background: #001a1a;
            color: #ccffff;
        }
        .json-key { color: #00ffff; }
        .json-string { color: #66ffff; }
        .json-number { color: #33ffff; }
        .json-boolean { color: #00cccc; }
        .json-null { color: #009999; }
        `
        },
        {
            name: '霓虹紫红',
            css: `
        .json-block {
            background: #1a001a;
            color: #ffccff;
        }
        .json-key { color: #ff66ff; }
        .json-string { color: #ff99ff; }
        .json-number { color: #ff33ff; }
        .json-boolean { color: #ff00ff; }
        .json-null { color: #cc00cc; }
        `
        }
    ];

    // 样式类名前缀,防止污染其他样式
    const CLASS_PREFIX = 'tm-json';
    // 获取当前主题索引(默认值为0)
    const THEME_INDEX = GM_getValue('jsonThemeIndex', 0);
    // 样式注入容器
    const styleEl = document.createElement('style');
    document.head.appendChild(styleEl);

    /**
     * 应用选中的主题样式到页面中
     * @param {number} index - 主题索引
     */
    function applyTheme(index) {
        const baseCSS = `
            .${CLASS_PREFIX}-block {
                font-family: 'Courier New', Courier, monospace;
                white-space: pre-wrap;
                word-wrap: break-word;
                padding: 8px;
                border-radius: 4px;
                margin: 4px 0;
                width: 100%;
                max-height: 200px;
                box-sizing: border-box;
                overflow-y: auto;
                overflow-x: hidden;
            }
        `;
        // 替换 json- 前缀,避免与页面已有类冲突
        styleEl.textContent = baseCSS + themes[index].css.replaceAll('.json-', `.${CLASS_PREFIX}-`);
    }

    // 初始应用主题
    applyTheme(THEME_INDEX);

    /**
     * 对 JSON 文本进行语法高亮,返回带 span 的 HTML 字符串
     * @param {string} json - 格式化的 JSON 字符串
     * @returns {string} - HTML 字符串
     */
    function highlightJSON(json) {
        return json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
            .replace(/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(\.\d+)?([eE][+\-]?\d+)?)/g, m => {
                let cls = `${CLASS_PREFIX}-number`;
                if (/^"/.test(m)) cls = /:$/.test(m) ? `${CLASS_PREFIX}-key` : `${CLASS_PREFIX}-string`;
                else if (/true|false/.test(m)) cls = `${CLASS_PREFIX}-boolean`;
                else if (/null/.test(m)) cls = `${CLASS_PREFIX}-null`;
                return `<span class="${cls}">${m}</span>`;
            });
    }

    /**
     * 尝试将字符串解析为 JSON,并格式化为字符串
     * @param {string} text
     * @returns {string|null} - 格式化后的 JSON 字符串或 null
     */
    function tryJSON(text) {
        try {
            return JSON.stringify(JSON.parse(text), null, 2);
        } catch {
            return null;
        }
    }

    /**
     * 从文本中提取所有有效 JSON 字符串片段
     * @param {string} text
     * @returns {string[]} - 所有解析成功的 JSON 字符串
     */
    function extractJSON(text) {
        const segments = [];
        let rest = text.trim();
        while (rest.length) {
            const start = Math.min(...['{', '['].map(c => {
                const i = rest.indexOf(c);
                return i >= 0 ? i : Infinity;
            }));
            if (start === Infinity) break;

            const slice = rest.slice(start);
            let end = -1, stack = [];
            for (let i = 0; i < slice.length; i++) {
                const ch = slice[i];
                if (ch === '{' || ch === '[') stack.push(ch);
                else if (ch === '}' || ch === ']') {
                    const last = stack.at(-1);
                    if ((ch === '}' && last === '{') || (ch === ']' && last === '[')) {
                        stack.pop();
                        if (!stack.length) {
                            end = i;
                            break;
                        }
                    } else break;
                }
            }
            if (end === -1) break;

            const candidate = slice.slice(0, end + 1);
            const parsed = tryJSON(candidate);
            if (parsed) {
                segments.push(parsed);
                rest = rest.slice(start + end + 1).trim();
            } else {
                rest = rest.slice(start + 1).trim();
            }
        }
        return segments;
    }

    /**
     * 格式化并高亮显示某个 DOM 元素中的 JSON 内容
     * @param {HTMLElement} el - 需要处理的元素
     */
    function formatElement(el) {
        if (el.dataset.jsonDone === '1') return;
        if (el.querySelector(`.${CLASS_PREFIX}-block`)) {
            el.dataset.jsonDone = '1';
            return;
        }

        // 优先从 span.word-wrap 提取文本
        const span = el.querySelector('span.word-wrap');
        let raw = span
            ? [...span.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join('')
            : el.textContent;

        raw = raw.trim();
        if (!raw) return;

        const jsons = extractJSON(raw);
        if (jsons.length === 0) return;

        const html = highlightJSON(jsons.join('\n\n'));
        el.innerHTML = `<div class="${CLASS_PREFIX}-block">${html}</div>`;
        el.dataset.jsonDone = '1';
    }

    /**
     * 遍历页面所有目标元素进行 JSON 格式化
     */
    function updateAll() {
        document.querySelectorAll('div.table2__column').forEach(formatElement);
    }

    // 创建并启动 JSON 格式化的 DOM 观察器
    const jsonObserver = new MutationObserver(() => {
        requestAnimationFrame(updateAll);
    });

    jsonObserver.observe(document.body, { childList: true, subtree: true });
    updateAll();

    /**
     * 插入主题切换按钮到页面顶栏
     */
    function insertButton() {
        const container = document.querySelector('div.d-flex.my-auto.ml-auto');
        if (!container || container.querySelector('.json-theme-btn')) return;

        const btn = document.createElement('button');
        btn.className = 'json-theme-btn';
        btn.textContent = `主题:${themes[THEME_INDEX].name}`;
        btn.style.cssText = `
            margin-right: 8px;
            padding: 4px 10px;
            border-radius: 5px;
            font-size: 12px;
            background: #444;
            color: #fff;
            border: none;
            cursor: pointer;
            transition: background 0.2s;
        `;

        btn.onmouseenter = () => btn.style.background = '#222';
        btn.onmouseleave = () => btn.style.background = '#444';

        btn.onclick = () => {
            const index = (GM_getValue('jsonThemeIndex', 0) + 1) % themes.length;
            GM_setValue('jsonThemeIndex', index);
            applyTheme(index);
            btn.textContent = `主题:${themes[index].name}`;
        };

        container.insertBefore(btn, container.firstChild);
    }

    // 初始化插入按钮
    insertButton();

    // 监听 DOM 变化以确保按钮在页面更新时仍存在
    const buttonObserver = new MutationObserver(insertButton);
    buttonObserver.observe(document.body, { childList: true, subtree: true });

})();