ChatBot Text Replacer

Replace shortcuts with predefined text in ChatBot input

As of 2025-01-04. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ChatBot Text Replacer
// @namespace    http://tampermonkey.net/
// @version      0.2.2
// @description  Replace shortcuts with predefined text in ChatBot input
// @author       Eric
// @match        https://chatgpt.com/*
// @match        https://claude.ai/*
// @match        https://chat.deepseek.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @license      GPL Licence
// ==/UserScript==

(function () {
    'use strict';

    // Load replacements from GM storage
    let replacements = GM_getValue('replacements', {
        '/tl': '翻译以下内容:',
        '/pr': "我正在写一篇计算机领域的英文学术论文,请帮我润色。请以```latex ```格式输出,并注意符合latex格式",
        '/qa': "完成这道题。请先分析这道题目,再给出答案。",
        '/cmd': "将以下内容准换为Markdown格式,使用 LaTeX 语法来编写数学公式,并以```Markdown ```的格式输出"
    });

    // Register Settings Menu Command
    GM_registerMenuCommand('Settings', openSettings);
    GM_registerMenuCommand('Reload', observeTargetNode);

    function openSettings() {
        // Create Settings Modal
        const modal = document.createElement('div');
        modal.style.display = 'block';
        modal.style.position = 'fixed';
        modal.style.top = '50%';
        modal.style.left = '50%';
        modal.style.transform = 'translate(-50%, -50%)';
        modal.style.backgroundColor = '#000';
        modal.style.color = '#fff';
        modal.style.padding = '20px';
        modal.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        modal.style.zIndex = '1000';

        modal.innerHTML = '<h2>Settings</h2>';

        for (const [shortcut, replacement] of Object.entries(replacements)) {
            const div = document.createElement('div');
            div.style.marginBottom = '10px';
            div.style.display = 'flex';
            div.style.alignItems = 'center';
            div.innerHTML = `
                <input type="text" value="${shortcut}" placeholder="Shortcut" style="margin-right: 10px; background-color: #333; color: #fff; width: 100px;" />
                <textarea placeholder="Replacement" style="background-color: #333; color: #fff; width: 300px; height: 60px;"></textarea>
            `;
            const textarea = div.querySelector('textarea');
            textarea.value = replacement;
            modal.appendChild(div);
        }

        const addBtn = document.createElement('button');
        addBtn.textContent = 'Add';
        addBtn.style.backgroundColor = '#333';
        addBtn.style.color = '#fff';
        modal.appendChild(addBtn);

        const saveBtn = document.createElement('button');
        saveBtn.textContent = 'Save';
        saveBtn.style.marginLeft = '10px';
        saveBtn.style.backgroundColor = '#333';
        saveBtn.style.color = '#fff';
        modal.appendChild(saveBtn);

        const cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Cancel';
        cancelBtn.style.marginLeft = '10px';
        cancelBtn.style.backgroundColor = '#333';
        cancelBtn.style.color = '#fff';
        modal.appendChild(cancelBtn);

        cancelBtn.addEventListener('click', () => {
            document.body.removeChild(modal);
        });

        addBtn.addEventListener('click', () => {
            const div = document.createElement('div');
            div.style.marginBottom = '10px';
            div.style.display = 'flex';
            div.style.alignItems = 'center';
            div.innerHTML = `
                <input type="text" placeholder="Shortcut" style="margin-right: 10px; background-color: #333; color: #fff; width: 100px;" />
                <textarea placeholder="Replacement" style="background-color: #333; color: #fff; width: 300px; height: 60px;"></textarea>
            `;
            modal.insertBefore(div, addBtn);
        });

        saveBtn.addEventListener('click', () => {
            const inputs = modal.querySelectorAll('div > input, div > textarea');
            const newReplacements = {};
            for (let i = 0; i < inputs.length; i += 2) {
                const shortcut = inputs[i].value.trim();
                const replacement = inputs[i + 1].value.trim();
                if (shortcut && replacement) {
                    newReplacements[shortcut] = replacement;
                }
            }
            GM_setValue('replacements', newReplacements);
            replacements = newReplacements;
            document.body.removeChild(modal);
        });

        // Close modal on outside click
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                document.body.removeChild(modal);
            }
        });

        document.body.appendChild(modal);
    }

    // GPT
    function mutationCallback_ChatGPT(mutationList, observer) {
        console.log(mutationList);
        mutationList.forEach(mutation => {
            if (mutation.type === 'characterData') {
                const inputString = mutation.target.data;
                console.log(inputString);
                // replace shortcuts
                for (const [shortcut, replacement] of Object.entries(replacements)) {
                    if (inputString.includes(shortcut + ' ')) {
                        mutation.target.data = inputString.replace(shortcut + ' ', replacement);
                        const promptTextarea = document.getElementById('prompt-textarea');
                        // 增加一个<p>
                        const newP = document.createElement('p');
                        promptTextarea.appendChild(newP);
                        // 将光标设置到新的<p>中


                        const selection = window.getSelection();
                        const range = document.createRange();
                        range.selectNodeContents(newP);
                        range.collapse(false);
                        selection.removeAllRanges();
                        selection.addRange(range);

                        break;
                    }
                }
            }
        });
    }

    // 确保 targetNode 被正确获取
    function observeTargetNode_GPT() {
        const targetNode = document.getElementById('prompt-textarea');
        if (targetNode) {
            console.log("Target node found:", targetNode);

            const observer = new MutationObserver(mutationCallback_ChatGPT);

            observer.observe(targetNode, {
                childList: true,
                subtree: true,
                characterData: true
            });
        } else {
            console.log("Target node not found, retrying...");
            setTimeout(observeTargetNode_GPT, 1000); // 每秒重试一次
        }
    }

    // Claude AI
    function mutationCallback_Claude(mutationList, observer) {
        console.log(mutationList);
        mutationList.forEach(mutation => {
            if (mutation.type === 'characterData') {
                const inputString = mutation.target.data;
                console.log(inputString);
                // replace shortcuts
                for (const [shortcut, replacement] of Object.entries(replacements)) {
                    if (inputString.includes(shortcut + ' ')) {
                        mutation.target.data = inputString.replace(shortcut + ' ', replacement);

                        // 增加一个<p>
                        const promptTextarea = document.querySelector("body > div.flex.min-h-screen.w-full > div > main > div.top-5.z-10.mx-auto.w-full.max-w-2xl.md\\:sticky > div > fieldset > div.flex.flex-col.bg-bg-000.gap-1\\.5.border-0\\.5.border-border-300.pl-4.pt-2\\.5.pr-2\\.5.pb-2\\.5.sm\\:mx-0.items-stretch.transition-all.duration-200.relative.shadow-\\[0_0\\.25rem_1\\.25rem_rgba\\(0\\,0\\,0\\,0\\.035\\)\\].focus-within\\:shadow-\\[0_0\\.25rem_1\\.25rem_rgba\\(0\\,0\\,0\\,0\\.075\\)\\].hover\\:border-border-200.focus-within\\:border-border-200.cursor-text.z-10.rounded-2xl > div.flex.gap-2 > div.mt-1.max-h-96.w-full.overflow-y-auto.break-words.min-h-\\[4\\.5rem\\] > div");
                        const newP = document.createElement('p');
                        promptTextarea.appendChild(newP);

                        // 将光标设置到新的<p>中
                        const selection = window.getSelection();
                        const range = document.createRange();
                        range.selectNodeContents(newP);
                        range.collapse(false);
                        selection.removeAllRanges();
                        selection.addRange(range);

                        break;
                    }
                }
            }
        });
    }

    function observeTargetNode_ClaudeAI() {
        const targetNode = document.querySelector("body > div.flex.min-h-screen.w-full > div > main > div.top-5.z-10.mx-auto.w-full.max-w-2xl.md\\:sticky > div > fieldset > div.flex.flex-col.bg-bg-000.gap-1\\.5.border-0\\.5.border-border-300.pl-4.pt-2\\.5.pr-2\\.5.pb-2\\.5.sm\\:mx-0.items-stretch.transition-all.duration-200.relative.shadow-\\[0_0\\.25rem_1\\.25rem_rgba\\(0\\,0\\,0\\,0\\.035\\)\\].focus-within\\:shadow-\\[0_0\\.25rem_1\\.25rem_rgba\\(0\\,0\\,0\\,0\\.075\\)\\].hover\\:border-border-200.focus-within\\:border-border-200.cursor-text.z-10.rounded-2xl > div.flex.gap-2 > div.mt-1.max-h-96.w-full.overflow-y-auto.break-words.min-h-\\[4\\.5rem\\] > div")
        if (targetNode) {
            console.log("Target node found:", targetNode);

            const observer = new MutationObserver(mutationCallback_Claude);

            observer.observe(targetNode, {
                childList: true,
                subtree: true,
                characterData: true
            });
        } else {
            console.log("Target node not found, retrying...");
            setTimeout(observeTargetNode_ClaudeAI, 1000); // 每秒重试一次
        }
    }

    // DeepSeek
    function mutationCallback_DeepSeek(mutationList, observer) {
        console.log(mutationList);
        mutationList.forEach(mutation => {
            if (mutation.type === 'childList') {
                const inputString = mutation.target.value;
                console.log(inputString);
                // replace shortcuts
                for (const [shortcut, replacement] of Object.entries(replacements)) {
                    if (inputString.includes(shortcut + ' ')) {
                        const chat_input = document.getElementById('chat-input');
                        chat_input.value = inputString.replace(shortcut + ' ', replacement) + '\n';
                        break;
                    }
                }
            }
        });
    }

    // 确保 targetNode 被正确获取
    function observeTargetNode_DeekSeek() {
        const targetNode = document.getElementById('chat-input');
        if (targetNode) {
            console.log("Target node found:", targetNode);

            const observer = new MutationObserver(mutationCallback_DeepSeek);

            observer.observe(targetNode, {
                childList: true,
                subtree: true,
                characterData: true
            });
        } else {
            console.log("Target node not found, retrying...");
            setTimeout(observeTargetNode_DeekSeek, 1000); // 每秒重试一次
        }
    }


    function observeTargetNode() {
        const isClaudeAI = window.location.href.includes('claude.ai');
        const isChatGPT = window.location.href.includes('chatgpt.com');
        const isDeepSeek = window.location.href.includes("deepseek.com");
        if (isClaudeAI) {
            observeTargetNode_ClaudeAI();
        } else if (isChatGPT) {
            observeTargetNode_GPT();
        } else if (isDeepSeek) {
            observeTargetNode_DeekSeek();
        }
    }

    // Add URL change detection
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            console.log('URL changed to', url);
            setTimeout(() => {
                observeTargetNode();
            }, 1000);

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

    // Also listen to history changes
    window.addEventListener('popstate', function () {
        console.log('URL changed via back/forward');
        setTimeout(() => {
            observeTargetNode();
        }, 1000);
    });



    observeTargetNode();
})();