reCAPTCHA v2 image solver - noCaptchaAi

reCaptcha Solver automated Captcha Solver bypass Ai service. Free 6000 🔥solves/month! 50x⚡ faster than 2Captcha & others

// ==UserScript==
// @name         reCAPTCHA v2 image solver - noCaptchaAi
// @name:ar      reCAPTCHA v2 image solver - noCaptchaAi حلال
// @name:ru      noCaptchaAI Решатель капчи reCAPTCHA v2 image
// @name:sh-CN   noCaptchaAI 验证码求解器
// @namespace    https://nocaptchaai.com
// @version      3.9.3
// @run-at       document-start
// @description  reCaptcha Solver automated Captcha Solver bypass Ai service. Free 6000 🔥solves/month! 50x⚡ faster than 2Captcha & others
// @description:ar تجاوز برنامج Captcha Solver الآلي لخدمة reCaptcha Solver خدمة Ai. 6000 🔥 حل / شهر مجاني! 50x⚡ أسرع من 2Captcha وغيرها
// @description:ru reCaptcha Solver автоматизирует решение Captcha Solver в обход сервиса Ai. Бесплатно 6000 🔥решений/месяц! В 50 раз⚡ быстрее, чем 2Captcha и другие
// @description:zh-CN reCaptcha Solver 自动绕过 Ai 服务的 Captcha Solver。 免费 6000 🔥解决/月! 比 2Captcha 和其他人快 50x⚡
// @author       noCaptcha AI, Diego and Subcode
// @match        http*://*/*
// @icon         https://avatars.githubusercontent.com/u/110127579
// @grant        GM_addValueChangeListener
// @grant        GM_registerMenuCommand
// @grant        GM_listValues
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @license      MIT
// ==/UserScript==
(async () => {
    const softid = "recapUser_v" + GM_info.script.version;
    const searchParams = new URLSearchParams(location.search);
    const open = XMLHttpRequest.prototype.open;

    const cfg = new config({
        APIKEY: "",
        PLAN: "free",
        LOOP: false,
        RECAPTCHA: true,
        AUTO_SOLVE: true,
        DEBUG_LOGS: false,
        CHECKBOX_AUTO_OPEN: true
    });

    const isApikeyEmpty = !cfg.get("APIKEY");
    let sitekey, wait = 666; //temp

    XMLHttpRequest.prototype.open = function () {
        if (location.href.includes("recaptcha/api2")) {
            log(location.href);
            this.addEventListener("load", runSolver);
        }
        open.apply(this, arguments);
    }

    addMenu("⚙️ Settings", cfg.open, !isApikeyEmpty);
    addMenu(isApikeyEmpty ? "Login" : "📈 Dashboard/ 💰 Buy Plan / 👛 Balance info", "https://dash.nocaptchaai.com")
    addMenu("🏠 HomePage", "https://nocaptchaai.com");
    addMenu("📄 Api Docs", "https://docs.nocaptchaai.com/category/api-methods");
    addMenu("❓ Discord", "https://discord.gg/E7FfzhZqzA");
    addMenu("❓ Telegram", "https://t.me/noCaptchaAi");

    // onChange listener for APIKEY, refreshes page when APIKEY has been updated.
    GM_addValueChangeListener("APIKEY", function (key, oldValue, newValue, remote) {
        log("The value of the '" + key + "' key has changed from '" + oldValue + "' to '" + newValue + "'");
        location = location.href;
    });

    if (location.hostname === "config.nocaptchaai.com") {
        if (searchParams.has("apikey") && searchParams.has("plan") && document.referrer === "https://dash.nocaptchaai.com/") {
            cfg.set("APIKEY", searchParams.get("apikey"));
            cfg.set("PLAN", searchParams.get("plan"));
            // Toast.fire({
            //     title: "noCaptchaAi.com \n Config Saved Successfully."
            // });
            jsNotif("noCaptchaAi.com \n Config Saved Successfully.", 5000);
            history.replaceState({}, document.title, "/");
        }

        window.addEventListener('load', function (params) {
            const template = document.getElementById("tampermonkey");
            const clone = template.content.cloneNode(true);
            const inputs = clone.querySelectorAll("input");

            for (const input of inputs) {
                const type = input.type === "checkbox" ? "checked" : "value";
                input[type] = cfg.get(input.id);
                input.addEventListener("change", function (e) {
                    // Toast.fire({
                    //     title: "Your change has been saved"
                    // });
                    jsNotif("Your change has been saved", 5000);
                    cfg.set(input.id, e.target[type])
                })
            }

            document.querySelector("h1").after(clone);

        })
    }

    let currentType = '';
    async function runSolver() {
        log("runSolver");
        if (isApikeyEmpty || !cfg.get("AUTO_SOLVE") || this.responseType === "arraybuffer" || !this.responseText) {
            return;
        }
        log("captcha loaded");

        try {
            let data = JSON.parse(this.responseText.replace(')]}\'\n', ''));
            if (this.responseURL.startsWith("https://www.google.com/recaptcha/api2/") && cfg.get("RECAPTCHA")) {
                sitekey ||= searchParams.get("k");

                if (data[0] === 'dresp') {
                    log("Dynamic response");
                } else if (data[0] === 'uvresp') {
                    log("User verify response");
                    if (data[2] != 1) {
                        data = data[7];
                    } else {
                        log("Response: captcha solved");
                        return;
                    }
                } else {
                    log("Reload response");
                }

                const type = data.at(5);
                const p = data.at(9);
                log("type: " + type);
                if (type === "audio") {
                    log("Audio not implemented");
                    //return audio("https://www.google.com/recaptcha/api2/payload/audio.mp3?p="+ p +"&k=" + sitekey);
                } else if (type === "imageselect" || type === "dynamic") {
                    currentType = type;
                    const image = await getBase64FromUrl('https://www.google.com/recaptcha/api2/payload?p=' + p + '&k=' + sitekey)
                    const target = data.at(4).at(1).at(6);
                    await solveRE(image, 33, target, type === "dynamic");

                } else if (type === "multicaptcha" || type === "tileselect") {
                    log("solve: "+type);
                    currentType = type;
                    await solveRE44();

                } else if (type === "nocaptcha") {
                    log("captcha solve done");
                    return;

                } else if (type === "doscaptcha") {
                    log("automation detected");
                    return;

                } else if (currentType == "multicaptcha") {
                    setTimeout(solveRE44, 1000);
                    return;

                }
            }

        } catch (e) {
            log("loadCatch error: " + e);
            // console.error(this.responseText);
        }

        //Check for errors in solve
        await sleep(500);

        if (isRecapError()) { //todo var uvresp to check errors
            recapReload();
        }
    }

    function recapExpired() {
        const recap = document.querySelector("#recaptcha-anchor");
        return recap?.classList.contains('recaptcha-checkbox-expired');
    }

    function recapSolved() {
        let captcha = document.querySelector("#recaptcha-anchor");
        if (captcha) {
            return captcha.getAttribute("aria-checked") === "true";
        }
        return true;
    }

    function isFrameVisible(element) {
        var style = window.getComputedStyle(element);
        if (element.offsetParent === null) {
            return false;
        } else {
            return true;
        }
    }

    function isCaptchaFrame() {
        if (document.getElementById('recaptcha-anchor')) {
            return true;
        }
        return false;
    }

    function hasCaptchas() {
        var iframes = document.getElementsByTagName('iframe');
        for (var i = 0; i < iframes.length; i++) {
            if (iframes[i].title === "reCAPTCHA") {
                return true;
            }
        }
        return false;
    }

    function msgVisibleCaptchas() {
        var iframes = document.getElementsByTagName('iframe');
        for (var i = 0; i < iframes.length; i++) {
            if (iframes[i].title === "reCAPTCHA") {
                var visible = isFrameVisible(iframes[i]);
                if (visible) {
                    iframes[i].contentWindow.postMessage('reCaptchaVisible', '*');
                } else {
                    iframes[i].contentWindow.postMessage('reCaptchaHidden', '*');
                }
            }
        }
    }

    let captchaVisible = false;
    window.addEventListener('message', function (event) {
        if (event.data === "reCaptchaVisible") {
            if (!captchaVisible) {
                log("Captcha Visible");
                captchaVisible = true;
            }
        } else if (event.data === "reCaptchaHidden") {
            if (captchaVisible) {
                log("Captcha Hidden");
                captchaVisible = false;
            }
        }
    });

    let captchaOpened = false;
    while (!(!navigator.onLine || isApikeyEmpty)) {
        await sleep(1000);

        if (!isCaptchaFrame()) {
            if (hasCaptchas()) {
                msgVisibleCaptchas();
            }
        } else if (captchaVisible) {
            if (cfg.get("CHECKBOX_AUTO_OPEN") && document.contains(document.querySelector('.recaptcha-checkbox')) && !captchaOpened) {
                log("opening recaptcha");
                captchaOpened = true;
                fireMouseEvents(document.querySelector("#recaptcha-anchor"));
            } else if (recapExpired()) {
                // check for expired captcha
                log("recaptcha expired");
                captchaOpened = false;
            } else if (recapSolved()) {
                log("recaptcha solved");
                break;
            }
        }
    }

    //Solve function for dynamic 3x3, checks previously solved indexes
    async function solveRED(target, array) {
        if (array.length === 0) {
            return recapReload();
        }

        while (document.querySelectorAll(".rc-image-tile-11, rc-imageselect-dynamic-selected").length < array.length) {
            await sleep(100);
        }
        
        log("solveRED");
        let cells = document.querySelectorAll('.rc-image-tile-wrapper img');

        const images = {}

        for (const index of array) {
            images[index] = await getBase64FromUrl(cells[index].src)
        }

        if (Object.keys(images).length == 0) {
            return recapReload();
        }

        const data = await apiSolve({
            images,
            target,
            type: 'split_33',
            method: "recaptcha2",
            softid
        }, true);

        let result = await getResult(data, true);

        if (result.solution.length == 0) {
            return submit();
        }

        for (const index of result.solution) {
            cells[index].click();
            await sleep(1000);
        }

        await solveRED(target, result.solution);
    }

    function submit() {
        fireMouseEvents(document.querySelector("#recaptcha-verify-button"));
    }

    function isRecapError() {
        try {
            // Check if error messages are shown
            let err1 = document.querySelector(".rc-imageselect-error-select-more").style.display !== 'none';
            let err2 = document.querySelector(".rc-imageselect-error-dynamic-more").style.display !== 'none';

            log("isRecapError: " + err1 + " / " + err2);
            return (err1 || err2);

        } catch (e) {
            log("isRecapError fault: " + e);
        }
        return false;
    }

    // function isSolveError() {
    //     try {
    //         // Check if try again message is shown
    //         let err = document.querySelector(".rc-imageselect-incorrect-response").style.display !== 'none';

    //         log("isSolveError: " + err);
    //         return (err);
    //     } catch (e) {
    //         log("isSolveError fault: " + e);
    //     }
    //     return false;
    // }

    function recapReload() {
        // clear errors to prevent reloading the captcha twice in a row
        document.querySelector(".rc-imageselect-error-select-more").style.display = 'none';
        document.querySelector(".rc-imageselect-error-dynamic-more").style.display = 'none';

        // press reload captcha button
        fireMouseEvents(document.querySelector("#recaptcha-reload-button"));
    }

    async function getResult(data, beta = false) {
        log("getResult: " + beta);
        switch (data.status) {
            case "new":
                log("⏳ waiting a second");
                await sleep(1000);
                data = await apiStatus(data.url)
                break;
            case "solved":
                break;
            case "skip":
                log("⚠️ Seems this a new challenge, please contact noCaptchaAi!");
                break;
            default:
                log("😨 Unknown status", data.status);
                recapReload();
        }
        return data;
    }

    //Solve function for Recaptcha Multi
    async function solveRE44() {
        log("solveRE44");
        const target = document.querySelector('.rc-imageselect-desc-no-canonical strong')?.textContent
        const cells = document.querySelectorAll('.rc-image-tile-wrapper img');
        const image = document.querySelector('.rc-image-tile-44')?.src;

        const data = await apiSolve({
            images: {
                0: await getBase64FromUrl(image)
            },
            target,
            type: '44',
            method: "recaptcha2",
            softid
        }, true)

        let result = await getResult(data, true);

        for (const index of result.solution) {
            fireMouseEvents(cells[index]);
            await sleep(wait);
        }

        submit();
    }

    //Solve function for Recaptcha 3x3
    async function solveRE(image, type, target, isDynamic) {
        log("solveRE");
        const htmlTarget = document.querySelector('.rc-imageselect-desc-no-canonical strong')?.textContent;
        log(target, htmlTarget);
        const data = await apiSolve({
            images: {
                0: image
            },
            target: target || htmlTarget,
            type,
            method: "recaptcha2",
            softid
        }, true);

        let result = await getResult(data, true);

        const cells = document.querySelectorAll('.rc-image-tile-wrapper');
        for (const index of result.solution) {
            fireMouseEvents(cells[index]);
            await sleep(wait);
        }

        if (isDynamic) {
            await sleep(5000); //Wait for new images to show up
            await solveRED(target, result.solution);
        } else {
            submit();
        }

    }

    // async function audio(url) {
    //     const arrayBuffer = await fetch(url).then(response => response.arrayBuffer());
    //     const body = new FormData();
    //     body.append("audio", new Blob([arrayBuffer], { type: "audio/mp3" }), "audio.mp3");
    //     const data = await apiFetch(body, "audio")
    //     document.querySelector("#audio-response").value = data.solution;
    // }

    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");
            });
        });
    }

    async function apiSolve(body, beta, v = "solve", method = "POST") {
        log("apiFetch: request solve");
        const options = {
            method,
            headers: {
                "Content-Type": "application/json",
                apikey: cfg.get("APIKEY")
            },
        }

        if (method !== "GET") {
            options.body = JSON.stringify(body)
        }

        const response = await fetch("https://" + (beta ? "recap" : cfg.get("PLAN")) + ".nocaptchaai.com/" + v, options)
        const data = await response.json();
        return data;
    }

    async function apiStatus(url) {
        log("apiStatus: request solve result");
        const options = {
            method: "GET",
            headers: {
                "Content-Type": "application/json",
                apikey: cfg.get("APIKEY")
            },
        }

        const response = await fetch(url, options)
        const data = await response.json();
        return data;
    }

    function addMenu(name, url, check = true) {
        if (!check) {
            return;
        }

        GM_registerMenuCommand(name, function () {
            if (typeof url === "function") {
                url();
            } else {
                GM_openInTab(url, {
                    active: true,
                    setParent: true
                });
            }
        });
    }

    function fireMouseEvents(element) {
        if (!document.contains(element)) {
            return;
        }

        for (const eventName of ["mouseover", "mousedown", "mouseup", "click"]) {
            const eventObject = document.createEvent("MouseEvents"); //todo update
            eventObject.initEvent(eventName, true, false);
            element.dispatchEvent(eventObject);
        }
    }

    function config(data) {
        let openWin;

        function get(name) {
            return GM_getValue(name, "")
        }

        function set(name, value) {
            GM_setValue(name, value);
        }

        function open() {
            const windowFeatures = {
                location: "no",
                status: "no",
                left: window.screenX,
                top: window.screenY,
                width: 500,
                height: 500
            };

            const featuresArray = Object.keys(windowFeatures).map(key => key + "=" + windowFeatures[key]);

            openWin = window.open("https://config.nocaptchaai.com/", "_blank", featuresArray.join(","));
            openWin.moveBy(Math.round((window.outerWidth - openWin.outerWidth) / 2), Math.round((window.outerHeight - openWin.outerHeight) / 2));
        }

        function close() {
            openWin?.close();
            openWin = undefined;
        }

        const storedKeys = GM_listValues();
        for (const name in data) {
            if (storedKeys.includes(name)) {
                set(name, get(name));
            } else if (data[name] !== undefined) {
                set(name, data[name]);
            } else {
                set(name, "");
            }
        }

        return { get, set, open, close };
    }

    function random(min, max) {
        return Math.floor(Math.random() * (max - min) + min);
    }

    function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }

    function log() {
        cfg.get("DEBUG_LOGS") && console.log.apply(this, arguments)
    }

    function jsNotif(message, duration) {
        const toast = document.createElement("div");
        toast.style.cssText = `
          position: fixed;
          top: 10%;
          left: 0;
          background-color: rgba(0, 0, 0, 0.8);
          border-radius: 4px;
          padding: 16px;
          color: #fff;
          font-size: calc(14px + 0.5vw);
          font-family: 'Arial', sans-serif;
          font-weight: bold;
          text-transform: uppercase;
          letter-spacing: 1px;
          z-index: 9999;
          transition: all 1s ease-in-out;
        `;
        toast.innerHTML = `${message}`;
        document.body.appendChild(toast);

        const style = document.createElement("style");
        style.innerHTML = `
          @keyframes slideIn {
            0% { transform: translateX(-100%); }
            100% { transform: translateX(0); }
          }
      
          @keyframes slideOut {
            0% { transform: translateX(0); }
            100% { transform: translateX(100%); }
          }
        `;
        document.head.appendChild(style);

        // Slide in animation
        toast.style.animation = "slideIn 1s forwards";
        toast.style.animationFillMode = "forwards";

        setTimeout(() => {
            // Slide out animation
            toast.style.animation = "slideOut 1s forwards";

            setTimeout(() => {
                document.body.removeChild(toast);
            }, 1000);
        }, duration || 3000);
    }

})();