ChatGPT Utils

Modifies the behavior of the chat interface on the OpenAI website

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

  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.5.3
  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. console.log(`ChatGPT Utils initializing...`);
  15.  
  16. setInterval(() => {
  17. try {
  18. addTranslateButtons();
  19. } catch (error) {
  20. console.error(error);
  21. }
  22.  
  23. try {
  24. findAndHookTextareaElement();
  25. } catch (error) {
  26. console.error(error);
  27. }
  28. }, 100);
  29.  
  30. function addTranslateButtons() {
  31. var messages = document.querySelectorAll(".markdown.prose");
  32.  
  33. for (var i = 0; i < messages.length; i++) {
  34. const msgMarkdown = messages[i];
  35. const parentMsgMarkdown = msgMarkdown.parentElement;
  36. const msgIcon = parentMsgMarkdown.parentElement.parentElement.previousElementSibling;
  37.  
  38. if (!msgIcon.querySelector(".translate-button")) {
  39. var btn = document.createElement("button");
  40. btn.textContent = "Tr";
  41. btn.classList.add("translate-button");
  42. btn.style.cssText = "width: 30px; height: 30px;"; // margin-left: 8px; display: flex; flex-direction: column;
  43. msgIcon.insertBefore(btn, msgIcon.firstChild);
  44.  
  45. // Add event listener for click event on translate button
  46. btn.addEventListener("click", () => {
  47. const translateClassName = "translate-markdown";
  48.  
  49. var translateNode = parentMsgMarkdown.querySelector(`.${translateClassName}`);
  50. if (translateNode) {
  51. parentMsgMarkdown.removeChild(translateNode);
  52. delete parentMsgMarkdown.translateNode;
  53. }
  54.  
  55. translateNode = msgMarkdown.cloneNode(true);
  56. translateNode.classList.add(translateClassName);
  57.  
  58. const htmlContent = translateNode.outerHTML;
  59. parentMsgMarkdown.translateNode = translateNode;
  60. parentMsgMarkdown.insertBefore(translateNode, parentMsgMarkdown.firstChild);
  61.  
  62. translateHTML(htmlContent, "auto", navigator.language).then(translatedContent => {
  63. let index = translatedContent.lastIndexOf('</div>');
  64. let before = translatedContent.slice(0, index);
  65.  
  66. const endTranslate = `<p>.......... конец_перевода ..........</p>`;
  67. translatedContent = before.concat(endTranslate, '</div>');
  68.  
  69. //console.log("Translated content: " + translatedContent);
  70.  
  71. translateNode.outerHTML = translatedContent;
  72. });
  73. });
  74. }
  75. }
  76. }
  77.  
  78. function findAndHookTextareaElement() {
  79. const targetElement = document.querySelector("textarea");
  80. if (targetElement === null) {
  81. return;
  82. }
  83.  
  84. if (targetElement.isAddHookKeydownEvent === true) {
  85. return;
  86. }
  87.  
  88. targetElement.isAddHookKeydownEvent = true;
  89.  
  90. console.log(`Textarea element found. Adding keydown event listener.`);
  91. targetElement.addEventListener("keydown", async event => await handleSubmit(event, targetElement), true);
  92. }
  93.  
  94. async function handleSubmit(event, targetElement) {
  95. console.log(`Keydown event detected: type - ${event.type}, key - ${event.key}`);
  96.  
  97. if (event.shiftKey && event.key === "Enter") {
  98. return;
  99. }
  100.  
  101. if (window.isActiveOnSubmit === true) {
  102. return;
  103. }
  104.  
  105. if (event.key === "Enter") {
  106. window.isActiveOnSubmit = true;
  107. event.stopImmediatePropagation();
  108.  
  109. const request = targetElement.value;
  110. targetElement.value = "";
  111.  
  112. const translatedText = await translate(request, "ru", "en");
  113.  
  114. targetElement.focus();
  115. targetElement.value = translatedText;
  116. const enterEvent = new KeyboardEvent("keydown", {
  117. bubbles: true,
  118. cancelable: true,
  119. key: "Enter",
  120. code: "Enter"
  121. });
  122. targetElement.dispatchEvent(enterEvent);
  123.  
  124. window.isActiveOnSubmit = false;
  125. }
  126. }
  127.  
  128. async function translateHTML(html, sLang, tLang) {
  129. const excludeTagRegex = /<(pre|code)[^>]*>([\s\S]*?)<\/(pre|code)>/g;
  130. const excludeTags = [];
  131. const excludePlaceholder = 'e0x';
  132.  
  133. const replaceTagRegex = /<[^>]*>/g;
  134. const replaceTags = [];
  135. const replacePlaceholder = 'r0e';
  136.  
  137. let translateHTML = html;
  138.  
  139. let excludeTagsMatch;
  140. while (excludeTagsMatch = excludeTagRegex.exec(html)) {
  141. excludeTags.push(excludeTagsMatch[0]);
  142. translateHTML = translateHTML.replace(excludeTagsMatch[0], `[${excludePlaceholder}${excludeTags.length - 1}]`);
  143. }
  144.  
  145. let replaceTagsMatch;
  146. while (replaceTagsMatch = replaceTagRegex.exec(html)) {
  147. replaceTags.push(replaceTagsMatch[0]);
  148. translateHTML = translateHTML.replace(replaceTagsMatch[0], `[${replacePlaceholder}${replaceTags.length - 1}]`);
  149. }
  150.  
  151. console.log(`preTranslateHTML: ${html}`);
  152.  
  153. translateHTML = await translate(translateHTML, sLang, tLang);
  154. translateHTML = removeSpaces(translateHTML);
  155.  
  156. for (let i = 0; i < excludeTags.length; i++) {
  157. translateHTML = translateHTML.replace(`[${excludePlaceholder}${i}]`, excludeTags[i]);
  158. }
  159.  
  160. for (let i = 0; i < replaceTags.length; i++) {
  161. translateHTML = translateHTML.replace(`[${replacePlaceholder}${i}]`, replaceTags[i]);
  162. }
  163.  
  164. console.log(`postTranslateHTML: ${translateHTML}`);
  165.  
  166. return translateHTML;
  167. }
  168.  
  169. async function translate(text, sLang, tLang) {
  170. const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sLang}&tl=${tLang}&dt=t&q=${encodeURIComponent(text)}`;
  171.  
  172. console.log(`preTranslate: ${text}`);
  173.  
  174. try {
  175. const response = await doXHR(url);
  176. const responseText = JSON.parse(response.responseText);
  177.  
  178. let postTranslate = "";
  179. responseText[0].forEach(part => {
  180. postTranslate += part[0];
  181. });
  182.  
  183. console.log(`postTranslate: ${postTranslate}`);
  184.  
  185. return postTranslate;
  186. } catch (error) {
  187. console.error(error);
  188. }
  189. }
  190.  
  191. function removeSpaces(string) {
  192. // Use a regular expression to find [] blocks in the string
  193. const regex = /\[([^\[\]]*)\]/g;
  194. let result;
  195.  
  196. // Keep searching for [] blocks until there are no more
  197. while ((result = regex.exec(string)) !== null) {
  198. // Replace all spaces within the block with an empty string
  199. string = string.replace(result[1], result[1].replace(/\s/g, ''));
  200. }
  201.  
  202. return string;
  203. }
  204.  
  205. async function doXHR(url) {
  206. return new Promise((resolve, reject) => {
  207. const xhr = new XMLHttpRequest();
  208. xhr.open("GET", url);
  209. xhr.onload = () => resolve(xhr);
  210. xhr.onerror = () => reject(xhr.statusText);
  211. xhr.send();
  212. });
  213. }