Greasy Fork is available in English.

ProBot ✨ OCR Captcha Solver ~ noCaptchaAI

ProBot TextCaptcha Solver by noCaptchaAI

// ==UserScript==
// @name         ProBot ✨ OCR Captcha Solver ~ noCaptchaAI
// @name:ar      نص Probot حل ~ noCaptcha AI
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  ProBot TextCaptcha Solver by noCaptchaAI
// @description:ar  حل ProBot TextCaptcha بواسطة noCaptchaAI
// @author       noCaptchaAI & Diego
// @match        https://discord.com/channels/*
// @icon         https://avatars.githubusercontent.com/u/110127579
// @require      https://greasyfork.org/scripts/395037-monkeyconfig-modern/code/MonkeyConfig%20Modern.js
// @connect      nocaptchaai.com
// @grant        GM_registerMenuCommand
// @grant        window.onurlchange
// @grant        GM_xmlhttpRequest
// @grant        GM_addElement
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @run-at       document-end
// @noframes
// @license MIT
// ==/UserScript==
const cfg = new MonkeyConfig({
  title: "⚙️ Settings",
  params: {
    APIKEY: {
      label: "apikey",
      type: "text",
      default: "",
    },
    PLAN: {
      label: "plan",
      type: "select",
      choices: ["free", "pro"],
      default: "free",
    },
    AUTO_SOLVE: {
      label: "Auto Solve",
      type: "checkbox",
      default: true,
    },
  },
  onSave: function () {},
  menuCommand: true,
});

// saves only local storge of tampermonkey, not in the cloud
// if (getToken() != undefined) {
//   cfg.set("token", getToken());
//   // console.log(getToken(), cfg.get("token"), "token");
// }

window.addEventListener("urlchange", function (event) {
  if (isChannel(event.url)) {
    init("urlchange");
  }
});

if (isChannel(location.pathname)) {
  const timer = setInterval(function () {
    const element = document.querySelector("ol[data-list-id=chat-messages]");
    if (!document.contains(element)) {
      return;
    }
    init("main");
    clearInterval(timer);
  }, 500);
}

function init(value) {
  const css =
    "text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue; font-size: 40px;";
  console.log("%c" + value, css);
  const observer = new MutationObserver(async function (mutations, observer) {
    // for (const mutation of mutations) {}
    // for (const node of mutation.addedNodes) {}
    const mutation = mutations.at(-1);
    if (mutation.addedNodes.length === 0) {
      return;
    }
    const node = [...mutation.addedNodes].at(-1);
    if (!node.innerHTML.includes("ProBot")) {
      return;
    }
    const image = document.querySelector('a[href*="captcha.png"]');
    if (image) {
      request(await getBase64FromUrl(image.href));
    }
  });
  observer.observe(document.querySelector("ol[data-list-id=chat-messages]"), {
    childList: true,
  });
}

function request(image) {
  GM_xmlhttpRequest({
    method: "POST",
    url: getApi("solve"),
    responseType: "json",
    data: JSON.stringify({
      method: "ocr",
      image,
      softid: "PBot_userjs",
    }),
    headers: {
      "Content-Type": "application/json",
      apikey: cfg.get("APIKEY"),
    },
    onload: function (data) {
      const token = getToken();
      // console.log(token, "token");
      fetch(
        "https://discord.com/api/v9/channels/" +
          location.href.split("/").at(-1) +
          "/messages",
        {
          headers: {
            authorization: token,
            "content-type": "application/json",
          },
          body: JSON.stringify({
            content: data.response.solution,
          }),
          method: "POST",
        }
      );
    },
  });
}

function isChannel(path) {
  return /\/channels\/\d+\/\d+/.test(path);
}

const sleeep = (ms) => new Promise((r) => setTimeout(r, ms));

// function getToken() {
//   let a = [];
//   webpackChunkdiscord_app.push([
//     [0],
//     ,
//     (e) =>
//       Object.keys(e.c).find(
//         (t) => (t = e(t)?.default?.getToken?.()) && a.push(t)
//       ),
//   ]);
//   return a[0];
// }

// only to submit the response, doesn't send to anywhere else
function getToken() {
  return (webpackChunkdiscord_app.push([
    [""],
    {},
    (e) => {
      m = [];
      for (let c in e.c) m.push(e.c[c]);
    },
  ]),
  m)
    .find((m) => m?.exports?.default?.getToken !== void 0)
    .exports.default.getToken();
}
function getApi(v) {
  return "https://" + cfg.get("PLAN") + ".nocaptchaai.com/" + v;
}

async function getBase64FromUrl(url) {
  const blob = await (await fetch(url)).blob();
  return new Promise(function (resolve, reject) {
    const reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.addEventListener("loadend", function () {
      resolve(reader.result.replace(/^data:image\/(png|jpeg);base64,/, ""));
    });
    reader.addEventListener("error", function () {
      reject("❌ Failed to convert url to base64");
    });
  });
}