Greasy Fork is available in English.

StackOverflow Code Language Switcher

Adds a dropdown to code blocks in StackOverflow to switch the code language. Powered by GPT-3 AI.

  1. // ==UserScript==
  2. // @name StackOverflow Code Language Switcher
  3. // @namespace https://github.com/Christopher-Hayes/stackoverflow-code-language-switcher
  4. // @homepageURL https://github.com/Christopher-Hayes/stackoverflow-code-language-switcher
  5. // @supportURL https://github.com/Christopher-Hayes/stackoverflow-code-language-switcher
  6. // @description Adds a dropdown to code blocks in StackOverflow to switch the code language. Powered by GPT-3 AI.
  7. // @match https://stackoverflow.com/questions/*
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_addValueChangeListener
  11. // @grant GM_setClipboard
  12. // @version 1.0
  13. // @author Chris Hayes
  14. // @license GPL3
  15. // ==/UserScript==
  16.  
  17. class LangConvert {
  18. constructor() {
  19. this.key = GM_getValue("openai_key", "");
  20.  
  21. if (!this.key || this.key === "your-openai-key-here") {
  22. GM_setValue("openai_key", "your-openai-key-here");
  23. console.error(
  24. '"openai_key" not set in violent monkey script. Please get an OpenAI key and set this config value to use the OpenAI API.'
  25. );
  26. }
  27.  
  28. this.supportedLanguages = GM_getValue("supported_languages", [
  29. "javascript",
  30. "typescript",
  31. "python",
  32. "java",
  33. "bash",
  34. "css",
  35. "scss",
  36. "html",
  37. "c",
  38. "c++",
  39. "c#",
  40. "rust",
  41. "go",
  42. "kotlin",
  43. "php",
  44. "ruby",
  45. "liquid",
  46. "swift",
  47. "react",
  48. "vue",
  49. "svelte",
  50. "angular",
  51. ]);
  52.  
  53. GM_setValue("supported_languages", this.supportedLanguages);
  54.  
  55. this.previousConversions = {};
  56. }
  57.  
  58. setKey(newKey) {
  59. this.key = newKey;
  60. }
  61. setSupportedLanguages(newLanguages) {
  62. this.supportedLanguages = newLanguages;
  63. }
  64.  
  65. async makeGPT3Request(code, oldLang, newLang) {
  66. const url = "https://api.openai.com/v1/completions";
  67. const headers = {
  68. "Content-Type": "application/json",
  69. Authorization: `Bearer ${this.key}`,
  70. };
  71.  
  72. // Uses Davinci 003 text-completion
  73. // code-completion and edit were slower and didn't work as well
  74. const body = {
  75. model: "text-davinci-003",
  76. prompt: `# Convert this${oldLang ? " from " + oldLang : ""} to ${newLang}
  77. # ${oldLang ? oldLang : "Old"} version
  78.  
  79. ${code}
  80.  
  81. # ${newLang} version`,
  82. max_tokens: 1000,
  83. };
  84.  
  85. const response = await fetch(url, {
  86. method: "POST",
  87. headers,
  88. body: JSON.stringify(body),
  89. });
  90. console.log(
  91. "[StackOverflow Code Language Switcher] Full OpenAI response:",
  92. response
  93. );
  94.  
  95. const data = await response.json();
  96. console.log("[StackOverflow Code Language Switcher] Response data:", data);
  97.  
  98. let newCode = data.choices[0].text;
  99. // if the first line is an empty newline, remove it
  100. if (newCode.startsWith("\n")) {
  101. newCode = newCode.slice(1);
  102. }
  103. // if the last line is an empty newline, remove it
  104. if (newCode.endsWith("\n")) {
  105. newCode = newCode.slice(0, -1);
  106. }
  107.  
  108. return newCode;
  109. }
  110.  
  111. getCodeElements() {
  112. return Array.from(document.querySelectorAll(".s-prose pre code"));
  113. }
  114.  
  115. setup() {
  116. const codeElements = this.getCodeElements();
  117.  
  118. for (const codeElem of codeElements) {
  119. const pre = codeElem.parentElement;
  120. const div = document.createElement("div");
  121. div.classList.add("lang-convert");
  122. div.style = `
  123. height: 0;
  124. float: right;
  125. margin-bottom: -20px;
  126. display: block;
  127. position: relative;
  128. top: 10px;
  129. right: 8px;
  130. `;
  131.  
  132. // Show supported languages in dropdown
  133. const select = document.createElement("select");
  134. select.innerHTML = `${this.supportedLanguages
  135. .slice(0, 20)
  136. .map((lang) => `<option value="${lang}">${lang}</option>`)
  137. .join("")}`;
  138. select.style = `
  139. background: #181818;
  140. color: #999;
  141. outline: none;
  142. border: 0;
  143. padding: .5em 0.7em;
  144. border-radius: 7px;
  145. font-size: 12px;
  146. `;
  147. // Change style of select element when hovered over
  148. select.addEventListener("mouseenter", () => {
  149. select.style.background = "black";
  150. select.style.color = "white";
  151. });
  152. select.addEventListener("mouseleave", () => {
  153. select.style.background = "#181818";
  154. select.style.color = "#999";
  155. });
  156. div.appendChild(select);
  157.  
  158. // Add div before pre element
  159. pre.parentElement.insertBefore(div, pre);
  160.  
  161. // Set default value of dropdown to the language of the code element
  162. const lang = codeElem.className.split("-")[1];
  163. select.value = lang;
  164.  
  165. this.previousConversions[lang] = codeElem.textContent;
  166.  
  167. select.addEventListener("change", async (e) => {
  168. const oldLang = codeElem.className.split("-")[1];
  169. const newLang = e.target.value;
  170. if (oldLang === newLang) return;
  171.  
  172. // Show "converting" overlay message
  173. const overlay = document.createElement("div");
  174. overlay.style = `
  175. position: absolute;
  176. top: 0;
  177. left: 0;
  178. width: 100%;
  179. height: 100%;
  180. background: rgba(0, 0, 0, 0.5);
  181. display: flex;
  182. justify-content: center;
  183. align-items: center;
  184. color: white;
  185. font-size: 20px;
  186. font-weight: bold;
  187. `;
  188. overlay.innerHTML = "Converting...";
  189. pre.style.position = "relative";
  190. pre.appendChild(overlay);
  191.  
  192. const oldCode = codeElem.textContent;
  193. let newCode = "";
  194. if (this.previousConversions[newLang]) {
  195. newCode = this.previousConversions[newLang];
  196. } else {
  197. newCode = await this.makeGPT3Request(oldCode, oldLang, newLang);
  198. this.previousConversions[newLang] = newCode;
  199. }
  200.  
  201. GM_setClipboard(newCode);
  202.  
  203. codeElem.classList.remove(`language-${oldLang}`);
  204. codeElem.classList.add(`language-${newLang}`);
  205. codeElem.textContent = newCode;
  206.  
  207. // add a script to the page that will highlight the new code using window.hljs
  208. // This must be injected to have access to the highlight.js object already loaded into StackOverflow
  209. const codeID =
  210. Math.random().toString(36).substring(2, 15) +
  211. Math.random().toString(36).substring(2, 15);
  212. codeElem.id = codeID;
  213. const script = document.createElement("script");
  214. script.innerHTML = `if (window.hljs) window.hljs.highlightElement(document.getElementById("${codeID}"));`;
  215. document.body.appendChild(script);
  216.  
  217. // Remove "converting" overlay message
  218. pre.removeChild(overlay);
  219. pre.style.position = "static";
  220. });
  221. }
  222. }
  223. }
  224.  
  225. let langConvert = new LangConvert();
  226. langConvert.setup();
  227.  
  228. GM_addValueChangeListener("openai_key", (name, oldValue, newValue) => {
  229. langConvert.setKey(newValue);
  230. });
  231.  
  232. GM_addValueChangeListener("supported_languages", (name, oldValue, newValue) => {
  233. langConvert.setSupportedLanguages(newValue);
  234. });