ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

目前为 2022-12-26 提交的版本。查看 最新版本

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