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.1
  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. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. console.log(`ChatGPT Utils initializing...`);
  18.  
  19. let debug = false;
  20.  
  21. setInterval(() => {
  22. try {
  23. addAutoTranslate();
  24. //addTranslateButtons();
  25. } catch (error) {
  26. console.error(error);
  27. }
  28.  
  29. try {
  30. findAndHookTextareaElement();
  31. } catch (error) {
  32. console.error(error);
  33. }
  34. }, 100);
  35.  
  36. function addAutoTranslate() {
  37. var messages = document.querySelectorAll(".markdown.prose");
  38.  
  39. for (var i = 0; i < messages.length; i++) {
  40. const msgMarkdownNode = messages[i];
  41. const parentMsgMarkdown = msgMarkdownNode.parentElement;
  42.  
  43. if (parentMsgMarkdown.isAutoTranslate) {
  44. continue;
  45. }
  46. parentMsgMarkdown.isAutoTranslate = true;
  47.  
  48. setInterval(async () => {
  49. await translateNode(msgMarkdownNode);
  50. }, 500);
  51. }
  52. }
  53.  
  54. function addTranslateButtons() {
  55. var messages = document.querySelectorAll(".markdown.prose");
  56.  
  57. for (var i = 0; i < messages.length; i++) {
  58. const msgMarkdownNode = messages[i];
  59. const msgIcon = msgMarkdownNode.parentElement.parentElement.parentElement.previousElementSibling;
  60.  
  61. if (!msgIcon.querySelector(".translate-button")) {
  62. var btn = document.createElement("button");
  63. btn.textContent = "Tr";
  64. btn.classList.add("translate-button");
  65. btn.style.cssText = "width: 30px; height: 30px;";
  66. msgIcon.insertBefore(btn, msgIcon.firstChild);
  67.  
  68. btn.addEventListener("click", async () => {
  69. await translateNode(msgMarkdownNode);
  70. });
  71. }
  72. }
  73. }
  74.  
  75. async function translateNode(msgMarkdownNode) {
  76. const translateClassName = "translate-markdown";
  77. const parentMsgMarkdown = msgMarkdownNode.parentElement;
  78.  
  79. const msgMarkdownContent = msgMarkdownNode.outerHTML;
  80. if (msgMarkdownNode.storeContent == msgMarkdownContent) {
  81. return;
  82. }
  83. msgMarkdownNode.storeContent = msgMarkdownContent;
  84.  
  85. var translateNode = parentMsgMarkdown.querySelector(`.${translateClassName}`);
  86. if (translateNode == null) {
  87. translateNode = msgMarkdownNode.cloneNode(true);
  88. translateNode.classList.add(translateClassName);
  89. parentMsgMarkdown.insertBefore(translateNode, parentMsgMarkdown.firstChild);
  90. }
  91.  
  92. var msgMarkdownClone = msgMarkdownNode.cloneNode(true);
  93. msgMarkdownClone.classList.add(translateClassName);
  94. const msgMarkdownCloneContent = msgMarkdownClone.outerHTML;
  95. msgMarkdownClone = null;
  96.  
  97. const translatedContent = await translateHTML(msgMarkdownCloneContent, "auto", navigator.language)
  98.  
  99. let index = translatedContent.lastIndexOf('</div>');
  100. let before = translatedContent.slice(0, index);
  101. const endTranslate = `<p>.......... конец_перевода ..........</p>`;
  102.  
  103. translateNode.outerHTML = before.concat(endTranslate, '</div>');
  104. }
  105.  
  106. function findAndHookTextareaElement() {
  107. const targetElement = document.querySelector("textarea");
  108. if (targetElement === null) {
  109. return;
  110. }
  111.  
  112. if (targetElement.isAddHookKeydownEvent === true) {
  113. return;
  114. }
  115.  
  116. targetElement.isAddHookKeydownEvent = true;
  117.  
  118. console.log(`Textarea element found. Adding keydown event listener.`);
  119. targetElement.addEventListener("keydown", async event => await handleSubmit(event, targetElement), true);
  120. }
  121.  
  122. function addTranslateButtons() {
  123. var messages = document.querySelectorAll(".markdown.prose");
  124.  
  125. for (var i = 0; i < messages.length; i++) {
  126. const msgMarkdown = messages[i];
  127. const parentMsgMarkdown = msgMarkdown.parentElement;
  128. const msgIcon = parentMsgMarkdown.parentElement.parentElement.previousElementSibling;
  129.  
  130. if (!msgIcon.querySelector(".translate-button")) {
  131. var btn = document.createElement("button");
  132. btn.textContent = "Tr";
  133. btn.classList.add("translate-button");
  134. btn.style.cssText = "width: 30px; height: 30px;";
  135. msgIcon.insertBefore(btn, msgIcon.firstChild);
  136.  
  137. btn.addEventListener("click", () => {
  138. const translateClassName = "translate-markdown";
  139.  
  140. var translateNode = parentMsgMarkdown.querySelector(`.${translateClassName}`);
  141. if (translateNode) {
  142. parentMsgMarkdown.removeChild(translateNode);
  143. delete parentMsgMarkdown.translateNode;
  144. }
  145.  
  146. translateNode = msgMarkdown.cloneNode(true);
  147. translateNode.classList.add(translateClassName);
  148.  
  149. const htmlContent = translateNode.outerHTML;
  150. parentMsgMarkdown.translateNode = translateNode;
  151. parentMsgMarkdown.insertBefore(translateNode, parentMsgMarkdown.firstChild);
  152.  
  153. translateHTML(htmlContent, "auto", navigator.language).then(translatedContent => {
  154. let index = translatedContent.lastIndexOf('</div>');
  155. let before = translatedContent.slice(0, index);
  156.  
  157. const endTranslate = `<p>.......... конец_перевода ..........</p>`;
  158. translatedContent = before.concat(endTranslate, '</div>');
  159.  
  160. translateNode.outerHTML = translatedContent;
  161. });
  162. });
  163. }
  164. }
  165. }
  166.  
  167. async function handleSubmit(event, targetElement) {
  168. console.log(`Keydown event detected: type - ${event.type}, key - ${event.key}`);
  169.  
  170. if (event.shiftKey && event.key === "Enter") {
  171. return;
  172. }
  173.  
  174. if (window.isActiveOnSubmit === true) {
  175. return;
  176. }
  177.  
  178. if (event.key === "Enter") {
  179. window.isActiveOnSubmit = true;
  180. event.stopImmediatePropagation();
  181.  
  182. const request = targetElement.value;
  183. targetElement.value = "";
  184.  
  185. const translatedText = await translateText(request, "ru", "en");
  186.  
  187. targetElement.focus();
  188. targetElement.value = translatedText;
  189. const enterEvent = new KeyboardEvent("keydown", {
  190. bubbles: true,
  191. cancelable: true,
  192. key: "Enter",
  193. code: "Enter"
  194. });
  195. targetElement.dispatchEvent(enterEvent);
  196.  
  197. window.isActiveOnSubmit = false;
  198. }
  199. }
  200.  
  201. async function translateHTML(html, sLang, tLang) {
  202. const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
  203. const excludeTags = [];
  204. const excludePlaceholder = 'e0x';
  205.  
  206. let translateHTML = html;
  207.  
  208. let excludeTagsMatch;
  209. while (excludeTagsMatch = excludeTagRegex.exec(html)) {
  210. excludeTags.push(excludeTagsMatch[0]);
  211. translateHTML = translateHTML.replace(excludeTagsMatch[0], `[${excludePlaceholder}${excludeTags.length - 1}]`);
  212. }
  213.  
  214. if (debug) {
  215. console.log(`preTranslateHTML: ${html}`);
  216. }
  217.  
  218. translateHTML = await translateText(translateHTML, sLang, tLang);
  219. translateHTML = removeSpaces(translateHTML);
  220.  
  221. for (let i = 0; i < excludeTags.length; i++) {
  222. translateHTML = translateHTML.replace(`[${excludePlaceholder}${i}]`, excludeTags[i]);
  223. }
  224.  
  225. if (debug) {
  226. console.log(`postTranslateHTML: ${translateHTML}`);
  227. }
  228.  
  229. return translateHTML;
  230. }
  231.  
  232. async function translateText(text, sLang, tLang) {
  233. const url = `https://translate.googleapis.com/translate_a/single?client=gtx&format=html&sl=${sLang}&tl=${tLang}&dt=t&q=${encodeURIComponent(text)}`;
  234.  
  235. try {
  236. if (debug) {
  237. console.log(`preTranslate: ${text}`);
  238. }
  239.  
  240. const response = await doXHR(url);
  241. const responseText = JSON.parse(response.responseText);
  242.  
  243. let postTranslate = "";
  244. responseText[0].forEach(part => {
  245. postTranslate += part[0];
  246. });
  247.  
  248. if (debug) {
  249. console.log(`postTranslate: ${postTranslate}`);
  250. }
  251.  
  252. return postTranslate;
  253. } catch (error) {
  254. console.error(error);
  255. }
  256. }
  257.  
  258. async function doXHR(url) {
  259. return new Promise((resolve, reject) => {
  260. const xhr = new XMLHttpRequest();
  261. xhr.open("GET", url);
  262. xhr.onload = () => resolve(xhr);
  263. xhr.onerror = () => reject(xhr.statusText);
  264. xhr.send();
  265. });
  266. }
  267.  
  268. function removeSpaces(string) {
  269. const regex = /\[([^\[\]]*)\]/g;
  270. let result;
  271.  
  272. while ((result = regex.exec(string)) !== null) {
  273. string = string.replace(result[1], result[1].replace(/\s/g, ''));
  274. }
  275.  
  276. return string;
  277. }