ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

Stan na 23-12-2022. Zobacz najnowsza wersja.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

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.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         ChatGPT Utils
// @description  Modifies the behavior of the chat interface on the OpenAI website
// @version      1.5.1
// @match        https://chat.openai.com/*
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @license      MIT
// @namespace https://greatest.deepsurf.us/users/913219
// ==/UserScript==

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

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

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

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

    for (var i = 0; i < messages.length; i++) {
        const msgMarkdown = messages[i];
        const parentMsgMarkdown = msgMarkdown.parentElement;
        const msgIcon = parentMsgMarkdown.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;"; // margin-left: 8px; display: flex; flex-direction: column;
            msgIcon.insertBefore(btn, msgIcon.firstChild);

            // Add event listener for click event on translate button
            btn.addEventListener("click", () => {
                const translateClassName = "translate-markdown";

                var translateNode = parentMsgMarkdown.querySelector(`.${translateClassName}`);
                if (translateNode) {
                    parentMsgMarkdown.removeChild(translateNode);
                    delete parentMsgMarkdown.translateNode;
                }

                translateNode = msgMarkdown.cloneNode(true);
                translateNode.classList.add(translateClassName);

                const htmlContent = translateNode.outerHTML;
                parentMsgMarkdown.translateNode = translateNode;
                parentMsgMarkdown.insertBefore(translateNode, parentMsgMarkdown.firstChild);

                translateHTML(htmlContent, "auto", navigator.language).then(translatedContent => {
                    let index = translatedContent.lastIndexOf('</div>');
                    let before = translatedContent.slice(0, index);

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

                    //console.log("Translated content: " + translatedContent);

                    translateNode.outerHTML = translatedContent;
                });
            });
        }
    }
}

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 translate(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 translateHTML(html, sLang, tLang) {
    const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
    const excludeTags = [];
    const excludePlaceholder = 'e0x';

    const replaceTagRegex = /<[^>]*>/g;
    const replaceTags = [];
    const replacePlaceholder = 'r0e';

    let translateHTML = html;

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

    let replaceTagsMatch;
    while (replaceTagsMatch = replaceTagRegex.exec(html)) {
        replaceTags.push(replaceTagsMatch[0]);
        translateHTML = translateHTML.replace(replaceTagsMatch[0], `[${replacePlaceholder}${replaceTags.length - 1}]`);
    }

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

    translateHTML = await translate(translateHTML, sLang, tLang);
    translateHTML = removeSpaces(translateHTML);

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

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

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

    return translateHTML;
}

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

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

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

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

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

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

function removeSpaces(string) {
    // Use a regular expression to find [] blocks in the string
    const regex = /\[([^\[\]]*)\]/g;
    let result;

    // Keep searching for [] blocks until there are no more
    while ((result = regex.exec(string)) !== null) {
        // Replace all spaces within the block with an empty string
        string = string.replace(result[1], result[1].replace(/\s/g, ''));
    }

    return string;
}

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