ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

Per 24-12-2022. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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;
}