ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

当前为 2022-12-24 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         ChatGPT Utils
// @description  Modifies the behavior of the chat interface on the OpenAI website
// @namespace    ChatGPTUtils
// @version      1.6.4
// @author       CriDos
// @match        https://chat.openai.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chat.openai.com
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
// @license      MIT
// ==/UserScript==

'use strict';

console.log(`ChatGPT Utils initializing...`);

let debug = false;

setInterval(() => {
    try {
        addAutoTranslate();
        //addTranslateButtons();
    } catch (error) {
        console.error(error);
    }

    try {
        findAndHookTextareaElement();
    } catch (error) {
        console.error(error);
    }
}, 100);

function addAutoTranslate() {
    var messages = document.querySelectorAll(".markdown.prose");

    for (var i = 0; i < messages.length; i++) {
        const msgMarkdownNode = messages[i];
        const parentMsgMarkdown = msgMarkdownNode.parentElement;

        if (parentMsgMarkdown.isAutoTranslate) {
            continue;
        }
        parentMsgMarkdown.isAutoTranslate = true;

        setInterval(async () => {
            await translateNode(msgMarkdownNode);
        }, 500);
    }
}

function addTranslateButtons() {
    var messages = document.querySelectorAll(".markdown.prose");

    for (var i = 0; i < messages.length; i++) {
        const msgMarkdownNode = messages[i];
        const msgIcon = msgMarkdownNode.parentElement.parentElement.parentElement.previousElementSibling;

        if (!msgIcon.querySelector(".translate-button")) {
            var btn = document.createElement("button");
            btn.textContent = "Tr";
            btn.classList.add("translate-button");
            btn.style.cssText = "width: 30px; height: 30px;";
            msgIcon.insertBefore(btn, msgIcon.firstChild);

            btn.addEventListener("click", async () => {
                await translateNode(msgMarkdownNode);
            });
        }
    }
}

function findAndHookTextareaElement() {
    const targetElement = document.querySelector("textarea");
    if (targetElement === null) {
        return;
    }

    if (targetElement.isAddHookKeydownEvent === true) {
        return;
    }

    targetElement.isAddHookKeydownEvent = true;

    console.log(`Textarea element found. Adding keydown event listener.`);
    targetElement.addEventListener("keydown", async event => await handleSubmit(event, targetElement), true);
}

async function handleSubmit(event, targetElement) {
    console.log(`Keydown event detected: type - ${event.type}, key - ${event.key}`);

    if (event.shiftKey && event.key === "Enter") {
        return;
    }

    if (window.isActiveOnSubmit === true) {
        return;
    }

    if (event.key === "Enter") {
        window.isActiveOnSubmit = true;
        event.stopImmediatePropagation();

        const request = targetElement.value;
        targetElement.value = "";

        const translatedText = await translateText(request, "ru", "en");

        targetElement.focus();
        targetElement.value = translatedText;
        const enterEvent = new KeyboardEvent("keydown", {
            bubbles: true,
            cancelable: true,
            key: "Enter",
            code: "Enter"
        });
        targetElement.dispatchEvent(enterEvent);

        window.isActiveOnSubmit = false;
    }
}

async function translateNode(msgMarkdownNode) {
    const translateClassName = "translate-markdown";
    const parentMsgMarkdown = msgMarkdownNode.parentElement;

    const msgMarkdownContent = msgMarkdownNode.outerHTML;
    if (msgMarkdownNode.storeContent == msgMarkdownContent) {
        return;
    }
    msgMarkdownNode.storeContent = msgMarkdownContent;

    var translateNode = parentMsgMarkdown.querySelector(`.${translateClassName}`);
    if (translateNode == null) {
        translateNode = msgMarkdownNode.cloneNode(true);
        translateNode.classList.add(translateClassName);
        parentMsgMarkdown.insertBefore(translateNode, parentMsgMarkdown.firstChild);
    }

    var msgMarkdownClone = msgMarkdownNode.cloneNode(true);
    msgMarkdownClone.classList.add(translateClassName);
    const msgMarkdownCloneContent = msgMarkdownClone.outerHTML;
    msgMarkdownClone = null;

    const translatedContent = await translateHTML(msgMarkdownCloneContent, "auto", navigator.language)

    let index = translatedContent.lastIndexOf('</div>');
    let before = translatedContent.slice(0, index);
    const endTranslate = `<p>.......... конец_перевода ..........</p>`;

    translateNode.outerHTML = before.concat(endTranslate, '</div>');
}

async function translateHTML(html, sLang, tLang) {
    const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
    const excludeTags = [];
    const excludePlaceholder = 'e0x0c';

    let translateHTML = html;

    let excludeTagsMatch;
    while (excludeTagsMatch = excludeTagRegex.exec(html)) {
        excludeTags.push(excludeTagsMatch[0]);
        translateHTML = translateHTML.replace(excludeTagsMatch[0], `${excludePlaceholder}${excludeTags.length - 1}`);
    }

    if (debug) {
        console.log(`preTranslateHTML: ${html}`);
    }

    translateHTML = await translateText(translateHTML, sLang, tLang);

    for (let i = 0; i < excludeTags.length; i++) {
        translateHTML = translateHTML.replace(`${excludePlaceholder}${i}`, excludeTags[i]);
    }

    if (debug) {
        console.log(`postTranslateHTML: ${translateHTML}`);
    }

    return translateHTML;
}

async function translateText(text, sLang, tLang) {
    const url = `https://translate.googleapis.com/translate_a/single?client=gtx&format=text&sl=${sLang}&tl=${tLang}&dt=t&q=${encodeURIComponent(text)}`;

    try {
        if (debug) {
            console.log(`preTranslate: ${text}`);
        }

        const response = await doXHR(url);
        const responseText = JSON.parse(response.responseText);

        let postTranslate = "";
        responseText[0].forEach(part => {
            postTranslate += part[0];
        });

        if (debug) {
            console.log(`postTranslate: ${postTranslate}`);
        }

        return postTranslate;
    } catch (error) {
        console.error(error);
    }
}

async function doXHR(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = () => resolve(xhr);
        xhr.onerror = () => reject(xhr.statusText);
        xhr.send();
    });
}