ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

当前为 2023-01-04 提交的版本,查看 最新版本

  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.8.0
  6. // @author CriDos
  7. // @match https://chat.openai.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=chat.openai.com
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js
  10. // @grant GM_xmlhttpRequest
  11. // @run-at document-end
  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. function findAndHookTextareaElement() {
  76. const targetElement = document.querySelector("textarea");
  77. if (targetElement === null) {
  78. return;
  79. }
  80.  
  81. if (targetElement.isAddHookKeydownEvent === true) {
  82. return;
  83. }
  84.  
  85. targetElement.isAddHookKeydownEvent = true;
  86.  
  87. console.log(`Textarea element found. Adding keydown event listener.`);
  88. targetElement.addEventListener("keydown", async event => await handleSubmit(event, targetElement), true);
  89. }
  90.  
  91. async function handleSubmit(event, targetElement) {
  92. console.log(`Keydown event detected: type - ${event.type}, key - ${event.key}`);
  93.  
  94. if (event.shiftKey && event.key === "Enter") {
  95. return;
  96. }
  97.  
  98. if (window.isActiveOnSubmit === true) {
  99. return;
  100. }
  101.  
  102. if (event.key === "Enter") {
  103. window.isActiveOnSubmit = true;
  104. event.stopImmediatePropagation();
  105.  
  106. const request = targetElement.value;
  107. targetElement.value = "";
  108.  
  109. const translatedText = await translate(request, navigator.language, "en", "text");
  110.  
  111. targetElement.focus();
  112. targetElement.value = translatedText;
  113. const enterEvent = new KeyboardEvent("keydown", {
  114. bubbles: true,
  115. cancelable: true,
  116. key: "Enter",
  117. code: "Enter"
  118. });
  119. targetElement.dispatchEvent(enterEvent);
  120.  
  121. window.isActiveOnSubmit = false;
  122. }
  123. }
  124.  
  125. async function translateNode(node) {
  126. const translateClassName = "translate-markdown";
  127. const parentNode = node.parentElement;
  128. const nodeContent = $(node).html();
  129.  
  130. if (node.storeContent == nodeContent) {
  131. return;
  132. }
  133. node.storeContent = nodeContent;
  134.  
  135. var translateNode = parentNode.querySelector(`.${translateClassName}`);
  136. if (translateNode == null) {
  137. translateNode = node.cloneNode(true);
  138. translateNode.classList.add(translateClassName);
  139. parentNode.insertBefore(translateNode, parentNode.firstChild);
  140. }
  141.  
  142. var translatedContent = await translateHTML(nodeContent, "auto", navigator.language)
  143. translatedContent = translatedContent.replace(/<\/div>$/, '');
  144.  
  145. const endOfTranslation = await translate("end of translation", "en", navigator.language, "text");
  146.  
  147. $(translateNode).html(translatedContent + `<p>.......... ${endOfTranslation} ..........</p></div>`);
  148. }
  149.  
  150. async function translateHTML(html, sLang, tLang) {
  151. const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
  152. const excludeTags = [];
  153. const excludePlaceholder = 'e0x1c';
  154.  
  155. let htmlContent = html;
  156.  
  157. let excludeTagsMatch;
  158. while (excludeTagsMatch = excludeTagRegex.exec(html)) {
  159. excludeTags.push(excludeTagsMatch[0]);
  160. htmlContent = htmlContent.replace(excludeTagsMatch[0], `<${excludePlaceholder}${excludeTags.length - 1}>`);
  161. }
  162.  
  163. if (debug) {
  164. console.log(`preTranslateHTML: ${html}`);
  165. }
  166.  
  167. htmlContent = await translate(htmlContent, sLang, tLang);
  168.  
  169. for (let i = 0; i < excludeTags.length; i++) {
  170. htmlContent = htmlContent.replace(`<${excludePlaceholder}${i}>`, excludeTags[i]);
  171. }
  172.  
  173. if (debug) {
  174. console.log(`postTranslateHTML: ${htmlContent}`);
  175. }
  176.  
  177. return htmlContent;
  178. }
  179.  
  180. const cache = {};
  181.  
  182. async function translate(text, sLang, tLang, format, key) {
  183. try {
  184. if (debug) {
  185. console.log(`preTranslate: ${text}`);
  186. }
  187.  
  188. if (format == null) {
  189. format = "html";
  190. }
  191.  
  192. if (key == null) {
  193. key = "AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw";
  194. }
  195.  
  196. const cacheKey = text + '_' + format;
  197.  
  198. if (cache[cacheKey]) {
  199. return cache[cacheKey];
  200. }
  201.  
  202. const response = await new Promise((resolve, reject) => {
  203. GM_xmlhttpRequest({
  204. method: "POST",
  205. url: `https://translate.googleapis.com/translate_a/t?client=gtx&format=${format}&sl=${sLang}&tl=${tLang}&key=${key}`,
  206. data: `q=${encodeURIComponent(text)}`,
  207. headers: {
  208. "Content-Type": "application/x-www-form-urlencoded"
  209. },
  210. onload: response => {
  211. const json = JSON.parse(response.responseText);
  212. if (Array.isArray(json[0])) {
  213. resolve(json[0][0]);
  214. } else {
  215. resolve(json[0]);
  216. }
  217. },
  218. onerror: response => {
  219. reject(response.statusText);
  220. }
  221. });
  222. });
  223.  
  224. cache[cacheKey] = response;
  225.  
  226. if (debug) {
  227. console.log(`postTranslate: ${response}`);
  228. }
  229.  
  230. return response;
  231. } catch (error) {
  232. console.error(error);
  233. }
  234. }