StackOverflow Code Language Switcher

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

Versión del día 08/01/2023. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        StackOverflow Code Language Switcher
// @namespace   https://github.com/Christopher-Hayes/stackoverflow-code-language-switcher
// @homepageURL https://github.com/Christopher-Hayes/stackoverflow-code-language-switcher
// @supportURL  https://github.com/Christopher-Hayes/stackoverflow-code-language-switcher
// @description Adds a dropdown to code blocks in StackOverflow to switch the code language. Powered by GPT-3 AI.
// @match       https://stackoverflow.com/questions/*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_addValueChangeListener
// @grant       GM_setClipboard
// @version     1.0
// @author      Chris Hayes
// @license     GPL3
// ==/UserScript==

class LangConvert {
  constructor() {
    this.key = GM_getValue("openai_key", "");

    if (!this.key || this.key === "your-openai-key-here") {
      GM_setValue("openai_key", "your-openai-key-here");
      console.error(
        '"openai_key" not set in violent monkey script. Please get an OpenAI key and set this config value to use the OpenAI API.'
      );
    }

    this.supportedLanguages = GM_getValue("supported_languages", [
      "javascript",
      "typescript",
      "python",
      "java",
      "bash",
      "css",
      "scss",
      "html",
      "c",
      "c++",
      "c#",
      "rust",
      "go",
      "kotlin",
      "php",
      "ruby",
      "liquid",
      "swift",
      "react",
      "vue",
      "svelte",
      "angular",
    ]);

    GM_setValue("supported_languages", this.supportedLanguages);

    this.previousConversions = {};
  }

  setKey(newKey) {
    this.key = newKey;
  }
  setSupportedLanguages(newLanguages) {
    this.supportedLanguages = newLanguages;
  }

  async makeGPT3Request(code, oldLang, newLang) {
    const url = "https://api.openai.com/v1/completions";
    const headers = {
      "Content-Type": "application/json",
      Authorization: `Bearer ${this.key}`,
    };

    // Uses Davinci 003 text-completion
    // code-completion and edit were slower and didn't work as well
    const body = {
      model: "text-davinci-003",
      prompt: `# Convert this${oldLang ? " from " + oldLang : ""} to ${newLang}
# ${oldLang ? oldLang : "Old"} version

${code}

# ${newLang} version`,
      max_tokens: 1000,
    };

    const response = await fetch(url, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
    });
    console.log(
      "[StackOverflow Code Language Switcher] Full OpenAI response:",
      response
    );

    const data = await response.json();
    console.log("[StackOverflow Code Language Switcher] Response data:", data);

    let newCode = data.choices[0].text;
    // if the first line is an empty newline, remove it
    if (newCode.startsWith("\n")) {
      newCode = newCode.slice(1);
    }
    // if the last line is an empty newline, remove it
    if (newCode.endsWith("\n")) {
      newCode = newCode.slice(0, -1);
    }

    return newCode;
  }

  getCodeElements() {
    return Array.from(document.querySelectorAll(".s-prose pre code"));
  }

  setup() {
    const codeElements = this.getCodeElements();

    for (const codeElem of codeElements) {
      const pre = codeElem.parentElement;
      const div = document.createElement("div");
      div.classList.add("lang-convert");
      div.style = `
        height: 0;
        float: right;
        margin-bottom: -20px;
        display: block;
        position: relative;
        top: 10px;
        right: 8px;
      `;

      // Show supported languages in dropdown
      const select = document.createElement("select");
      select.innerHTML = `${this.supportedLanguages
        .slice(0, 20)
        .map((lang) => `<option value="${lang}">${lang}</option>`)
        .join("")}`;
      select.style = `
        background: #181818;
        color: #999;
        outline: none;
        border: 0;
        padding: .5em 0.7em;
        border-radius: 7px;
        font-size: 12px;
      `;
      // Change style of select element when hovered over
      select.addEventListener("mouseenter", () => {
        select.style.background = "black";
        select.style.color = "white";
      });
      select.addEventListener("mouseleave", () => {
        select.style.background = "#181818";
        select.style.color = "#999";
      });
      div.appendChild(select);

      // Add div before pre element
      pre.parentElement.insertBefore(div, pre);

      // Set default value of dropdown to the language of the code element
      const lang = codeElem.className.split("-")[1];
      select.value = lang;

      this.previousConversions[lang] = codeElem.textContent;

      select.addEventListener("change", async (e) => {
        const oldLang = codeElem.className.split("-")[1];
        const newLang = e.target.value;
        if (oldLang === newLang) return;

        // Show "converting" overlay message
        const overlay = document.createElement("div");
        overlay.style = `
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background: rgba(0, 0, 0, 0.5);
          display: flex;
          justify-content: center;
          align-items: center;
          color: white;
          font-size: 20px;
          font-weight: bold;
        `;
        overlay.innerHTML = "Converting...";
        pre.style.position = "relative";
        pre.appendChild(overlay);

        const oldCode = codeElem.textContent;
        let newCode = "";
        if (this.previousConversions[newLang]) {
          newCode = this.previousConversions[newLang];
        } else {
          newCode = await this.makeGPT3Request(oldCode, oldLang, newLang);
          this.previousConversions[newLang] = newCode;
        }

        GM_setClipboard(newCode);

        codeElem.classList.remove(`language-${oldLang}`);
        codeElem.classList.add(`language-${newLang}`);
        codeElem.textContent = newCode;

        // add a script to the page that will highlight the new code using window.hljs
        // This must be injected to have access to the highlight.js object already loaded into StackOverflow
        const codeID =
          Math.random().toString(36).substring(2, 15) +
          Math.random().toString(36).substring(2, 15);
        codeElem.id = codeID;
        const script = document.createElement("script");
        script.innerHTML = `if (window.hljs) window.hljs.highlightElement(document.getElementById("${codeID}"));`;
        document.body.appendChild(script);

        // Remove "converting" overlay message
        pre.removeChild(overlay);
        pre.style.position = "static";
      });
    }
  }
}

let langConvert = new LangConvert();
langConvert.setup();

GM_addValueChangeListener("openai_key", (name, oldValue, newValue) => {
  langConvert.setKey(newValue);
});

GM_addValueChangeListener("supported_languages", (name, oldValue, newValue) => {
  langConvert.setSupportedLanguages(newValue);
});