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