ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

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

  1. // ==UserScript==
  2. // @name ChatGPT Utils
  3. // @description Modifies the behavior of the chat interface on the OpenAI website
  4. // @namespace ChatGPTUtils
  5. // @version 1.6.8
  6. // @author CriDos
  7. // @match https://chat.openai.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=chat.openai.com
  9. // @grant GM_xmlhttpRequest
  10. // @run-at document-end
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. 'use strict';
  15.  
  16. console.log(`ChatGPT Utils initializing...`);
  17.  
  18. let debug = false;
  19.  
  20. setInterval(() => {
  21. try {
  22. addAutoTranslate();
  23. //addTranslateButtons();
  24. } catch (error) {
  25. console.error(error);
  26. }
  27.  
  28. try {
  29. findAndHookTextareaElement();
  30. } catch (error) {
  31. console.error(error);
  32. }
  33. }, 100);
  34.  
  35. function addAutoTranslate() {
  36. var messages = document.querySelectorAll(".markdown.prose");
  37.  
  38. for (var i = 0; i < messages.length; i++) {
  39. const msgMarkdownNode = messages[i];
  40. const parentMsgMarkdown = msgMarkdownNode.parentElement;
  41.  
  42. if (parentMsgMarkdown.isAutoTranslate) {
  43. continue;
  44. }
  45. parentMsgMarkdown.isAutoTranslate = true;
  46.  
  47. setInterval(async () => {
  48. await translateNode(msgMarkdownNode);
  49. }, 500);
  50. }
  51. }
  52.  
  53. function addTranslateButtons() {
  54. var messages = document.querySelectorAll(".markdown.prose");
  55.  
  56. for (var i = 0; i < messages.length; i++) {
  57. const msgMarkdownNode = messages[i];
  58. const msgIcon = msgMarkdownNode.parentElement.parentElement.parentElement.previousElementSibling;
  59.  
  60. if (!msgIcon.querySelector(".translate-button")) {
  61. var btn = document.createElement("button");
  62. btn.textContent = "Tr";
  63. btn.classList.add("translate-button");
  64. btn.style.cssText = "width: 30px; height: 30px;";
  65. msgIcon.insertBefore(btn, msgIcon.firstChild);
  66.  
  67. btn.addEventListener("click", async () => {
  68. await translateNode(msgMarkdownNode);
  69. });
  70. }
  71. }
  72. }
  73.  
  74. function findAndHookTextareaElement() {
  75. const targetElement = document.querySelector("textarea");
  76. if (targetElement === null) {
  77. return;
  78. }
  79.  
  80. if (targetElement.isAddHookKeydownEvent === true) {
  81. return;
  82. }
  83.  
  84. targetElement.isAddHookKeydownEvent = true;
  85.  
  86. console.log(`Textarea element found. Adding keydown event listener.`);
  87. targetElement.addEventListener("keydown", async event => await handleSubmit(event, targetElement), true);
  88. }
  89.  
  90. async function handleSubmit(event, targetElement) {
  91. console.log(`Keydown event detected: type - ${event.type}, key - ${event.key}`);
  92.  
  93. if (event.shiftKey && event.key === "Enter") {
  94. return;
  95. }
  96.  
  97. if (window.isActiveOnSubmit === true) {
  98. return;
  99. }
  100.  
  101. if (event.key === "F2") {
  102. const request = targetElement.value;
  103. targetElement.value = "";
  104.  
  105. const translatedText = await translateText(request, "ru", "en");
  106.  
  107. targetElement.focus();
  108. targetElement.value = translatedText;
  109. } else if (event.key === "Enter") {
  110. window.isActiveOnSubmit = true;
  111. event.stopImmediatePropagation();
  112.  
  113. const request = targetElement.value;
  114. targetElement.value = "";
  115.  
  116. const translatedText = await translateText(request, "ru", "en");
  117.  
  118. targetElement.focus();
  119. targetElement.value = translatedText;
  120. const enterEvent = new KeyboardEvent("keydown", {
  121. bubbles: true,
  122. cancelable: true,
  123. key: "Enter",
  124. code: "Enter"
  125. });
  126. targetElement.dispatchEvent(enterEvent);
  127.  
  128. window.isActiveOnSubmit = false;
  129. }
  130. }
  131.  
  132. async function translateNode(msgMarkdownNode) {
  133. const translateClassName = "translate-markdown";
  134. const parentMsgMarkdown = msgMarkdownNode.parentElement;
  135.  
  136. const msgMarkdownContent = msgMarkdownNode.outerHTML;
  137. if (msgMarkdownNode.storeContent == msgMarkdownContent) {
  138. return;
  139. }
  140. msgMarkdownNode.storeContent = msgMarkdownContent;
  141.  
  142. var translateNode = parentMsgMarkdown.querySelector(`.${translateClassName}`);
  143. if (translateNode == null) {
  144. translateNode = msgMarkdownNode.cloneNode(true);
  145. translateNode.classList.add(translateClassName);
  146. parentMsgMarkdown.insertBefore(translateNode, parentMsgMarkdown.firstChild);
  147. }
  148.  
  149. var msgMarkdownClone = msgMarkdownNode.cloneNode(true);
  150. msgMarkdownClone.classList.add(translateClassName);
  151. const msgMarkdownCloneContent = msgMarkdownClone.outerHTML;
  152. msgMarkdownClone = null;
  153.  
  154. var translatedContent = await translateHTML(msgMarkdownCloneContent, "en", navigator.language)
  155.  
  156. translatedContent = translatedContent.replace('< div>', '<div>')
  157. translatedContent = translatedContent.replace('< дел>', '<div>')
  158. translatedContent = translatedContent.replace('<дел>', '<div>')
  159. translatedContent = translatedContent.replace('< /div>', '</div>')
  160. translatedContent = translatedContent.replace('< /дел>', '</div>')
  161. translatedContent = translatedContent.replace('</дел>', '</div>')
  162. translatedContent = translatedContent.replace('< p>', '<p>')
  163. translatedContent = translatedContent.replace('< /p>', '</p>')
  164.  
  165. translateNode.outerHTML = translatedContent + `<p>.......... конец_перевода ..........</p></div>`;
  166. }
  167.  
  168. async function translateHTML(html, sLang, tLang) {
  169. const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
  170. const excludeTags = [];
  171. const excludePlaceholder = 'e0x0c';
  172.  
  173. let translateHTML = html;
  174.  
  175. let excludeTagsMatch;
  176. while (excludeTagsMatch = excludeTagRegex.exec(html)) {
  177. excludeTags.push(excludeTagsMatch[0]);
  178. translateHTML = translateHTML.replace(excludeTagsMatch[0], `${excludePlaceholder}${excludeTags.length - 1}`);
  179. }
  180.  
  181. if (debug) {
  182. console.log(`preTranslateHTML: ${html}`);
  183. }
  184.  
  185. translateHTML = await translateText(translateHTML, sLang, tLang);
  186.  
  187. for (let i = 0; i < excludeTags.length; i++) {
  188. translateHTML = translateHTML.replace(`${excludePlaceholder}${i}`, excludeTags[i]);
  189. }
  190.  
  191. if (debug) {
  192. console.log(`postTranslateHTML: ${translateHTML}`);
  193. }
  194.  
  195. return translateHTML;
  196. }
  197.  
  198. async function translateText(text, sLang, tLang) {
  199. const url = `https://translate.googleapis.com/translate_a/single?client=gtx&format=text&sl=${sLang}&tl=${tLang}&dt=t&q=${encodeURIComponent(text)}`;
  200.  
  201. try {
  202. if (debug) {
  203. console.log(`preTranslate: ${text}`);
  204. }
  205.  
  206. const response = await doXHR(url);
  207. const responseText = JSON.parse(response.responseText);
  208.  
  209. let postTranslate = "";
  210. responseText[0].forEach(part => {
  211. postTranslate += part[0];
  212. });
  213.  
  214. if (debug) {
  215. console.log(`postTranslate: ${postTranslate}`);
  216. }
  217.  
  218. return postTranslate;
  219. } catch (error) {
  220. console.error(error);
  221. }
  222. }
  223.  
  224. async function doXHR(url) {
  225. return new Promise((resolve, reject) => {
  226. const xhr = new XMLHttpRequest();
  227. xhr.open("GET", url);
  228. xhr.onload = () => resolve(xhr);
  229. xhr.onerror = () => reject(xhr.statusText);
  230. xhr.send();
  231. });
  232. }