ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

Verze ze dne 24. 12. 2022. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

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.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         ChatGPT Utils
// @description  Modifies the behavior of the chat interface on the OpenAI website
// @namespace    ChatGPTUtils
// @version      1.6.6
// @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).html();
    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).html();
    msgMarkdownClone = null;

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

    translatedContent = removeSpacesBetweenBlocks(translatedContent);
    translatedContent = translatedContent.replace('<див>', '<div>')
    translatedContent = translatedContent.replace('</див>', '</div>')

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

    $(translateNode).html(before + endTranslate)
}

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();
    });
}

function removeSpacesBetweenBlocks(html) {
    const blocks = html.match(/<.*>/g);

    for (let i = 0; i < blocks.length; i++) {
        const updatedBlock = blocks[i].replace(/\s/g, '');
        html = html.replace(blocks[i], updatedBlock);
    }

    return html;
}