Greasy Fork 还支持 简体中文。

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