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