Modifies the behavior of the chat interface on the OpenAI website
Від
// ==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();
});
}