Superscript Pinyin for Chinese Characters with API (Optimized)

Adds superscript pinyin to all Chinese characters on a webpage using an external API with accurate processing and placement. Includes a toggle shortcut. Uses superscript to show the pinyin next to the character. Toggles on and off with ctrl + shift + p. Defaults to off. Perfectly reversible.

当前为 2024-08-21 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Superscript Pinyin for Chinese Characters with API (Optimized)
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Adds superscript pinyin to all Chinese characters on a webpage using an external API with accurate processing and placement. Includes a toggle shortcut. Uses superscript to show the pinyin next to the character. Toggles on and off with ctrl + shift + p. Defaults to off. Perfectly reversible.
// @author       Louis
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Cache for storing pinyin results to avoid duplicate API calls
    const pinyinCache = new Map(Object.entries({'的':'de','一':'yī','是':'shì','不':'bù','了':'le','在':'zài','人':'rén','有':'yǒu','我':'wǒ','他':'tā','这':'zhè','个':'gè','们':'men','中':'zhōng','来':'lái','上':'shàng','大':'dà','为':'wèi','和':'hé','国':'guó','地':'de','到':'dào','以':'yǐ','说':'shuō','时':'shí','要':'yào','就':'jiù','出':'chū','会':'huì','可':'kě','也':'yě','你':'nǐ','对':'duì','生':'shēng','能':'néng','而':'ér','子':'zi','那':'nà','得':'dé','于':'yú','着':'zhe','下':'xià','自':'zì','之':'zhī','年':'nián','过':'guò','发':'fā','后':'hòu','作':'zuò','里':'lǐ','用':'yòng','道':'dào','行':'xíng','所':'suǒ','然':'rán','家':'jiā','种':'zhǒng','事':'shì','成':'chéng','方':'fāng','多':'duō','经':'jīng','么':'me','去':'qù','法':'fǎ','学':'xué','如':'rú','都':'dōu','同':'tóng','现':'xiàn','当':'dāng','没':'méi','动':'dòng','面':'miàn','起':'qǐ','看':'kàn','定':'dìng','天':'tiān','分':'fēn','还':'hái','进':'jìn','好':'hǎo','小':'xiǎo','部':'bù','其':'qí','些':'xiē','主':'zhǔ','样':'yàng','理':'lǐ','心':'xīn','她':'tā','本':'běn','前':'qián','开':'kāi','但':'dàn','因':'yīn','只':'zhǐ','从':'cóng','想':'xiǎng','实':'shí','日':'rì','军':'jūn','者':'zhě','意':'yì','无':'wú','力':'lì','它':'tā','与':'yǔ','长':'zhǎng','把':'bǎ','机':'jī','十':'shí','民':'mín','第':'dì','公':'gōng','此':'cǐ','已':'yǐ','工':'gōng','使':'shǐ','情':'qíng','明':'míng','性':'xìng','知':'zhī','全':'quán','三':'sān','又':'yòu','关':'guān','点':'diǎn','正':'zhèng','业':'yè','外':'wài','将':'jiāng','两':'liǎng','高':'gāo','间':'jiān','由':'yóu','问':'wèn','很':'hěn','最':'zuì','重':'zhòng','并':'bìng','物':'wù','手':'shǒu','应':'yīng','战':'zhàn','向':'xiàng','头':'tóu','文':'wén','体':'tǐ','政':'zhèng','美':'měi','相':'xiāng','见':'jiàn','被':'bèi','利':'lì','什':'shén','二':'èr','等':'děng','产':'chǎn','或':'huò','新':'xīn','己':'jǐ','制':'zhì','身':'shēn','果':'guǒ','加':'jiā','西':'xī','斯':'sī','月':'yuè','话':'huà','合':'hé','回':'huí','特':'tè','代':'dài','内':'nèi','信':'xìn','表':'biǎo','化':'huà','老':'lǎo','给':'gěi','世':'shì','位':'wèi','次':'cì','度':'dù','门':'mén','任':'rèn','常':'cháng','先':'xiān','海':'hǎi','通':'tōng','教':'jiào','儿':'ér','原':'yuán','东':'dōng','声':'shēng','提':'tí','立':'lì','及':'jí','比':'bǐ','员':'yuán','解':'jiě','水':'shuǐ','名':'míng','真':'zhēn','论':'lùn','处':'chù','走':'zǒu','义':'yì','各':'gè','入':'rù','几':'jǐ','口':'kǒu','认':'rèn','条':'tiáo','平':'píng','系':'xì','气':'qì','题':'tí','活':'huó','尔':'ěr','更':'gèng','别':'bié','打':'dǎ','女':'nǚ','变':'biàn','四':'sì','神':'shén','总':'zǒng','何':'hé','电':'diàn','数':'shù','安':'ān','少':'shǎo','报':'bào','才':'cái','结':'jié','反':'fǎn','受':'shòu','目':'mù','太':'tài','量':'liàng','再':'zài','感':'gǎn','建':'jiàn','务':'wù','做':'zuò','接':'jiē','必':'bì','场':'chǎng','件':'jiàn','计':'jì','管':'guǎn','期':'qī','市':'shì','直':'zhí','德':'dé','资':'zī','命':'mìng','山':'shān','金':'jīn','指':'zhǐ','克':'kè','许':'xǔ','统':'tǒng','区':'qū','保':'bǎo','至':'zhì','队':'duì','形':'xíng','社':'shè','便':'biàn','空':'kōng','决':'jué','治':'zhì','展':'zhǎn','马':'mǎ','科':'kē','司':'sī','五':'wǔ','基':'jī','眼':'yǎn','书':'shū','非':'fēi','则':'zé','听':'tīng','白':'bái','却':'què','界':'jiè','达':'dá','光':'guāng','放':'fàng','强':'qiáng','即':'jí','像':'xiàng','难':'nán','且':'qiě','权':'quán','思':'sī','王':'wáng','象':'xiàng','完':'wán','设':'shè','式':'shì','色':'sè','路':'lù','记':'jì','南':'nán','品':'pǐn','住':'zhù','告':'gào','类':'lèi','求':'qiú','据':'jù','程':'chéng','北':'běi','边':'biān','死':'sǐ','张':'zhāng','该':'gāi','交':'jiāo','规':'guī','万':'wàn','取':'qǔ','拉':'lā','格':'gé','望':'wàng','觉':'jué','术':'shù','领':'lǐng','共':'gòng','确':'què','传':'chuán','师':'shī','观':'guān','清':'qīng','今':'jīn','切':'qiè','院':'yuàn','让':'ràng','识':'shí','候':'hòu','带':'dài','导':'dǎo','争':'zhēng','运':'yùn','笑':'xiào','飞':'fēi','风':'fēng','步':'bù','改':'gǎi','收':'shōu','根':'gēn','干':'gàn','造':'zào','言':'yán','联':'lián','持':'chí','组':'zǔ','每':'měi','济':'jì','车':'chē','亲':'qīn','极':'jí','林':'lín','服':'fú','快':'kuài','办':'bàn','议':'yì','往':'wǎng','元':'yuán','英':'yīng','士':'shì','证':'zhèng','近':'jìn','失':'shī','转':'zhuǎn','夫':'fū','令':'lìng','准':'zhǔn','布':'bù','始':'shǐ','怎':'zěn','呢':'ne','存':'cún','未':'wèi','远':'yuǎn','叫':'jiào','台':'tái','单':'dān','影':'yǐng','具':'jù','罗':'luō','字':'zì','爱':'ài','击':'jī','流':'liú','备':'bèi','兵':'bīng','连':'lián','调':'diào','深':'shēn','商':'shāng','算':'suàn','质':'zhì','团':'tuán','集':'jí','百':'bǎi','需':'xū','价':'jià','花':'huā','党':'dǎng','华':'huá','城':'chéng','石':'shí','级':'jí','整':'zhěng','府':'fǔ','离':'lí','况':'kuàng','亚':'yà','请':'qǐng','技':'jì','际':'jì','约':'yuē','示':'shì','复':'fù','病':'bìng','息':'xī','究':'jiū','线':'xiàn','似':'shì','官':'guān','火':'huǒ','断':'duàn','精':'jīng','满':'mǎn','支':'zhī','视':'shì','消':'xiāo','越':'yuè','器':'qì','容':'róng','照':'zhào','须':'xū','九':'jiǔ','增':'zēng','研':'yán','写':'xiě','称':'chēng','企':'qǐ','八':'bā','功':'gōng','吗':'ma','包':'bāo','片':'piàn','史':'shǐ','委':'wěi','乎':'hū','查':'chá','轻':'qīng','易':'yì','早':'zǎo','曾':'céng','除':'chú','农':'nóng','找':'zhǎo','装':'zhuāng','广':'guǎng','显':'xiǎn','吧':'ba','阿':'ā','李':'lǐ','标':'biāo','谈':'tán','吃':'chī','图':'tú','念':'niàn','六':'liù','引':'yǐn','历':'lì','首':'shǒu','医':'yī','局':'jú','突':'tū','专':'zhuān','费':'fèi','号':'hào','尽':'jǐn','另':'lìng','周':'zhōu','较':'jiào','注':'zhù','语':'yǔ','仅':'jǐn','考':'kǎo','落':'luò','青':'qīng','随':'suí','选':'xuǎn','列':'liè','武':'wǔ','红':'hóng','响':'xiǎng','虽':'suī','推':'tuī','势':'shì','参':'cān','希':'xī','古':'gǔ','众':'zhòng','构':'gòu','房':'fáng','半':'bàn','节':'jié','土':'tǔ','投':'tóu','某':'mǒu','案':'àn','黑':'hēi','维':'wéi','革':'gé','划':'huà','敌':'dí','致':'zhì','陈':'chén','律':'lǜ','足':'zú','态':'tài','护':'hù','七':'qī','兴':'xìng','派':'pài','孩':'hái','验':'yàn','责':'zé','营':'yíng','星':'xīng','够':'gòu','章':'zhāng','音':'yīn','跟':'gēn','志':'zhì','底':'dǐ','站':'zhàn','严':'yán','巴':'bā','例':'lì','防':'fáng','族':'zú','供':'gōng','效':'xiào','续':'xù','施':'shī','留':'liú','讲':'jiǎng','型':'xíng','料':'liào','终':'zhōng','答':'dá','紧':'jǐn','黄':'huáng','绝':'jué','奇':'qí','察':'chá','母':'mǔ','京':'jīng','段':'duàn','依':'yī','批':'pī','群':'qún','项':'xiàng','故':'gù','按':'àn','河':'hé','米':'mǐ','围':'wéi','江':'jiāng','织':'zhī','害':'hài','斗':'dòu','双':'shuāng','境':'jìng','客':'kè','纪':'jì','采':'cǎi','举':'jǔ','杀':'shā','攻':'gōng','父':'fù','苏':'sū','密':'mì','低':'dī','朝':'cháo','友':'yǒu','诉':'sù','止':'zhǐ','细':'xì','愿':'yuàn','千':'qiān','值':'zhí','仍':'réng','男':'nán','钱':'qián','破':'pò','网':'wǎng','热':'rè','助':'zhù','倒':'dào','育':'yù','属':'shǔ','坐':'zuò','帝':'dì','限':'xiàn','船':'chuán','脸':'liǎn','职':'zhí','速':'sù','刻':'kè','乐':'lè','否':'fǒu','刚':'gāng','威':'wēi','毛':'máo','状':'zhuàng','率':'lǜ','甚':'shén','独':'dú','球':'qiú','般':'bān','普':'pǔ','怕':'pà','弹':'dàn','校':'xiào','苦':'kǔ','创':'chuàng','假':'jiǎ','久':'jiǔ','错':'cuò','承':'chéng','印':'yìn','晚':'wǎn','兰':'lán','试':'shì','股':'gǔ','拿':'ná','脑':'nǎo','预':'yù','谁':'shuí','益':'yì','阳':'yáng','若':'ruò','哪':'nǎ','微':'wēi','尼':'ní','继':'jì','送':'sòng','急':'jí','血':'xuè','惊':'jīng','伤':'shāng','素':'sù','药':'yào','适':'shì','波':'bō','夜':'yè','省':'shěng','初':'chū','喜':'xǐ','卫':'wèi','源':'yuán','食':'shí','险':'xiǎn','待':'dài','述':'shù','陆':'lù','习':'xí','置':'zhì','居':'jū','劳':'láo','财':'cái','环':'huán','排':'pái','福':'fú','纳':'nà','欢':'huān','雷':'léi','警':'jǐng','获':'huò','模':'mó','充':'chōng','负':'fù','云':'yún','停':'tíng','木':'mù','游':'yóu','龙':'lóng','树':'shù','疑':'yí','层':'céng','冷':'lěng','洲':'zhōu','冲':'chōng','射':'shè','略':'lüè','范':'fàn','竟':'jìng','句':'jù','室':'shì','异':'yì','激':'jī','汉':'hàn','村':'cūn','哈':'hā','策':'cè','演':'yǎn','简':'jiǎn','卡':'kǎ','罪':'zuì','判':'pàn','担':'dān','州':'zhōu','静':'jìng','退':'tuì','既':'jì','衣':'yī','您':'nín','宗':'zōng','积':'jī','余':'yú','痛':'tòng','检':'jiǎn','差':'chà','富':'fù','灵':'líng','协':'xié','角':'jiǎo','占':'zhàn','配':'pèi','征':'zhēng','修':'xiū','皮':'pí','挥':'huī','胜':'shèng','降':'jiàng','阶':'jiē','审':'shěn','沉':'chén','坚':'jiān','善':'shàn','妈':'mā','刘':'liú','读':'dú','啊':'a','超':'chāo','免':'miǎn','压':'yā','银':'yín','买':'mǎi','皇':'huáng','养':'yǎng','伊':'yī','怀':'huái','执':'zhí','副':'fù','乱':'luàn','抗':'kàng','犯':'fàn','追':'zhuī','帮':'bāng','宣':'xuān','佛':'fú','岁':'suì','航':'háng','优':'yōu','怪':'guài','香':'xiāng','著':'zhe','田':'tián','铁':'tiě','控':'kòng','税':'shuì','左':'zuǒ','右':'yòu','份':'fèn','穿':'chuān','艺':'yì','背':'bèi','阵':'zhèn','草':'cǎo','脚':'jiǎo','概':'gài','恶':'è','块':'kuài','顿':'dùn','敢':'gǎn','守':'shǒu','酒':'jiǔ','岛':'dǎo','托':'tuō','央':'yāng','户':'hù','烈':'liè','洋':'yáng','哥':'gē','索':'suǒ','胡':'hú','款':'kuǎn','靠':'kào','评':'píng','版':'bǎn','宝':'bǎo','座':'zuò','释':'shì','景':'jǐng','顾':'gù','弟':'dì','登':'dēng','货':'huò','互':'hù','付':'fù','伯':'bó','慢':'màn','欧':'ōu','换':'huàn','闻':'wén','危':'wēi','忙':'máng','核':'hé','暗':'àn','姐':'jiě','介':'jiè','坏':'huài','讨':'tǎo','丽':'lì','良':'liáng','序':'xù','升':'shēng','监':'jiān','临':'lín','亮':'liàng','露':'lù','永':'yǒng','呼':'hū','味':'wèi','野':'yě','架':'jià','域':'yù','沙':'shā','掉':'diào','括':'kuò','舰':'jiàn','鱼':'yú','杂':'zá','误':'wù','湾':'wān','吉':'jí','减':'jiǎn','编':'biān','楚':'chǔ','肯':'kěn','测':'cè','败':'bài','屋':'wū','跑':'pǎo','梦':'mèng','散':'sàn','温':'wēn','困':'kùn','剑':'jiàn','渐':'jiàn','封':'fēng','救':'jiù','贵':'guì','枪':'qiāng','缺':'quē','楼':'lóu','县':'xiàn','尚':'shàng','毫':'háo','移':'yí','娘':'niáng','朋':'péng','画':'huà','班':'bān','智':'zhì','亦':'yì','耳':'ěr','恩':'ēn','短':'duǎn','掌':'zhǎng','恐':'kǒng','遗':'yí','固':'gù','席':'xí','松':'sōng','秘':'mì','谢':'xiè','鲁':'lǔ','遇':'yù','康':'kāng','虑':'lǜ','幸':'xìng','均':'jūn','销':'xiāo','钟':'zhōng','诗':'shī','藏':'cáng','赶':'gǎn','剧':'jù','票':'piào','损':'sǔn','忽':'hū','巨':'jù','炮':'pào','旧':'jiù','端':'duān','探':'tàn','湖':'hú','录':'lù','叶':'yè','春':'chūn','乡':'xiāng','附':'fù','吸':'xī','予':'yǔ','礼':'lǐ','港':'gǎng','雨':'yǔ','呀':'ya','板':'bǎn','庭':'tíng','妇':'fù','归':'guī','睛':'jīng','饭':'fàn','额':'é','含':'hán','顺':'shùn','输':'shū','摇':'yáo','招':'zhāo','婚':'hūn','脱':'tuō','补':'bǔ','谓':'wèi','督':'dū','毒':'dú','油':'yóu','疗':'liáo','旅':'lǚ','泽':'zé','材':'cái','灭':'miè','逐':'zhú','莫':'mò','笔':'bǐ','亡':'wáng','鲜':'xiān','词':'cí','圣':'shèng','择':'zé','寻':'xún','厂':'chǎng','睡':'shuì','博':'bó','勒':'lēi','烟':'yān','授':'shòu','诺':'nuò','伦':'lún','岸':'àn','奥':'ào','唐':'táng','卖':'mài','俄':'é','炸':'zhà','载':'zài','洛':'luò','健':'jiàn','堂':'táng','旁':'páng','宫':'gōng','喝':'hē','借':'jiè','君':'jūn','禁':'jìn','阴':'yīn','园':'yuán','谋':'móu','宋':'sòng','避':'bì','抓':'zhuā','荣':'róng','姑':'gū','孙':'sūn','逃':'táo','牙':'yá','束':'shù','跳':'tiào','顶':'dǐng'}));

    // Variable to track whether pinyin superscript is enabled or disabled
    let pinyinEnabled = false;
    let mutationObserver = null;

    // Function to debounce processing to reduce excessive executions
    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            const context = this;
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(context, args), wait);
        };
    }

    // Function to make a request to the pinyin API (offloaded to worker)
    async function fetchPinyin(chars) {
        const results = {};
        const uncachedChars = [];

        chars.forEach(char => {
            if (pinyinCache.has(char)) {
                results[char] = pinyinCache.get(char);
            } else {
                uncachedChars.push(char);
            }
        });

        if (uncachedChars.length === 0) return results;

        try {
            const response = await fetch(`https://12.yvelin.net/pinyin.php?text=${encodeURIComponent(uncachedChars.join(''))}`);
            const data = await response.json();

            // Cache the results and assign pinyin
            data.pinyin.forEach((pinyin, index) => {
                const char = uncachedChars[index];
                pinyinCache.set(char, pinyin);
                results[char] = pinyin;
            });

            return results;
        } catch (e) {
            console.error(`Failed to fetch pinyin for characters: "${uncachedChars.join('')}"`, e);
            return results;
        }
    }

    // Function to apply pinyin superscripts to a given text node
    async function applyPinyin(node) {
        if (node.nodeType === Node.TEXT_NODE && !node.parentNode.hasAttribute('data-pinyin-processed')) {
            const text = node.textContent;
            const chineseChars = [...text].filter(char => /[\u4e00-\u9fff]/.test(char));

            if (chineseChars.length > 0) {
                const pinyinResults = await fetchPinyin(chineseChars);
                const fragment = document.createDocumentFragment();

                for (const char of text) {
                    if (/[\u4e00-\u9fff]/.test(char)) {
                        const charNode = document.createTextNode(char);
                        const supNode = document.createElement('sup');
                        supNode.textContent = pinyinResults[char] || '';
                        supNode.setAttribute('data-custom-pinyin-superscript', 'true');

                        fragment.appendChild(charNode);
                        fragment.appendChild(supNode);
                    } else {
                        fragment.appendChild(document.createTextNode(char));
                    }
                }

                const parent = node.parentNode;
                parent.replaceChild(fragment, node);
                parent.setAttribute('data-pinyin-processed', 'true'); // Mark as processed
            }
        }
    }

    // Function to handle text nodes within an element
    async function processElement(element) {
        const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
        const nodes = [];
        while (walker.nextNode()) {
            nodes.push(walker.currentNode);
        }

        for (const node of nodes) {
            await applyPinyin(node);
        }
    }

    // Function to enable or disable the pinyin display
    async function togglePinyin() {
        if (pinyinEnabled) {
            // Remove all <sup> elements with the data attribute
            const superscripts = document.querySelectorAll('sup[data-custom-pinyin-superscript]');
            superscripts.forEach(sup => {
                const parent = sup.parentNode;
                if (parent) {
                    parent.removeChild(sup);
                    parent.removeAttribute('data-pinyin-processed');
                }
            });
            pinyinEnabled = false;

            if (mutationObserver) mutationObserver.disconnect();
        } else {
            await processElement(document.body);
            pinyinEnabled = true;

            // Observe for changes to dynamically added content
            mutationObserver = new MutationObserver(debounce(async (mutations) => {
                for (const mutation of mutations) {
                    if (mutation.type === 'childList') {
                        for (const node of mutation.addedNodes) {
                            if (node.nodeType === Node.ELEMENT_NODE && !node.hasAttribute('data-pinyin-processed')) {
                                await processElement(node);
                            }
                        }
                    }
                }
            }, 300)); // Adjust the debounce delay as needed
            mutationObserver.observe(document.body, { childList: true, subtree: true });
        }
    }

    // Add CSS for pinyin display (optional, you can style it differently)
    function addCSS() {
        const style = document.createElement('style');
        style.textContent = `
            sup[data-custom-pinyin-superscript] {
                font-size: 0.8em;
                vertical-align: super;
                color: #888;
            }
        `;
        document.head.appendChild(style);
    }

    // Initialize CSS
    addCSS();

    // Listen for keyboard shortcut to toggle pinyin
    document.addEventListener('keydown', (event) => {
        if (event.ctrlKey && event.shiftKey && event.key === 'P') {
            event.preventDefault();
            togglePinyin();
        }
    });

})();