Stormgain with 2Captcha (Miner)

Solves Stormgain Miner Captcha (GeeTest) using 2Captcha service

// ==UserScript==
// @name         Stormgain with 2Captcha (Miner)
// @description  Solves Stormgain Miner Captcha (GeeTest) using 2Captcha service
// @version      0.3
// @author       satology
// @namespace    sg2c.satology.onrender.com
// @connect      2captcha.com
// @connect      miner.stormgain.com
// @grant        GM_xmlhttpRequest
// @match        https://app.stormgain.com/crypto-miner/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=stormgain.com
// ==/UserScript==

(function() {
    'use strict';
    /* Settings */
    const API_KEY = 'YOUR_API_KEY';
    const DISPLAY_UI = true; // Let's you stop the auto process (to solve it manually) and shows some log msgs afterwards
    const COUNTDOWN_SECONDS = 9; // Time to wait before auto solving
    const LOG_TO_CONSOLE = true; // Shows log in console

    let preventStartCountdown = COUNTDOWN_SECONDS;
    let btn_start;
    let statusContainer;
    let statusElm;
    let itv_start;
    let itv_countdown;

    // Old/V3
    // let snd_gt = '';
    // let snd_challenge = '';
    // V4:
    let snd_captcha_id = '';

    let api_req_id = '';

    // Old/V3 Vars:
    // let rsp_challenge = '';
    // let rsp_validate = '';
    // let rsp_seccode = '';
    // V4 vars:
    let rsp_captcha_id = '';
    let rsp_lot_number = '';
    let rsp_pass_token = '';
    let rsp_gen_time = '';
    let rsp_captcha_output = '';

    let sg_token = '';
    let sg_clientId = '';

    let api_in = function() {
        // Old/V3 version:
        // return `https://2captcha.com/in.php?key=${API_KEY}&json=1&method=geetest&gt=${snd_gt}&challenge=${snd_challenge}&pageurl=https://app.stormgain.com/crypto-miner/`;
        // V4:
        return `https://2captcha.com/in.php?key=${API_KEY}&json=1&method=geetest_v4&captcha_id=${snd_captcha_id}&pageurl=https://app.stormgain.com/crypto-miner/`;
    }
    let api_out = function() {
        return `https://2captcha.com/res.php?key=${API_KEY}&json=1&action=get&id=${api_req_id}`;
    };
    let apiResponse = '';

    function logger(title = '', msg = '') {
        if(!LOG_TO_CONSOLE) {
            return;
        }

        console.log("%c" + new Date().toISOString().slice(0, 19).replace("T", " ") + ' > ' + title, "background: yellow; font-size: large");
        console.log(msg);
    }

    function toUI(msg) {
        if (DISPLAY_UI) { document.getElementById("sg2c-msg").innerHTML = msg; }
    }

    async function start() {
        try {
            sg_token = JSON.parse(localStorage.AppStorage).JWTAccessToken;
            logger('', 'JWTAccessToken retireved');
        } catch(err) {
            logger('Unable to retrieve JWTAccessToken', err);
            toUI('Error!');
            return;
        }

        try {
            sg_clientId = [...document.scripts].filter(x => x.textContent.includes('app-config'))[0].innerText.replace("'", "").split('"personCode":')[1].split(",")[0]
            // sg_clientId = Object.keys(JSON.parse(localStorage.AppStorage).clientPrefs)[0];
            toUI('CLientID retireved');
        } catch(err) {
            logger('Unable to retrieve ClientID', err);
            toUI('Error!');
            return;
        }

        let rr = await fetch("https://miner.stormgain.com/api/v1/preactivate", {
            "headers": {
                "accept": "application/json, text/plain, */*",
                "authorization": "Token " + sg_token,
                "client-id": sg_clientId
            },
            "referrer": "https://app.stormgain.com/",
            "referrerPolicy": "strict-origin-when-cross-origin",
            "body": null,
            "method": "GET",
            "mode": "cors",
            "credentials": "omit"
        });

        let content = await rr.json();

//        if (content && content.data && content.data.success && content.data.gt && content.data.challenge) {
        if (content && content.data && content.captcha_provider == 'geetest_v4' && content.data.gt) {
            // snd_gt = content.data.gt;
            snd_captcha_id = content.data.gt;
            logger('', 'Challenge data retrieved');
        } else {
            logger('Error retrieving challenge data', content);
            toUI('Error!');
            return;
        }

        GM_xmlhttpRequest({
            method: "GET",
            url: api_in(),
            onload: function(response) {
                apiResponse = JSON.parse(this.responseText);
                if (apiResponse.status == 0) {
                    logger('Error in SG with captcha', apiResponse.error_text);
                    toUI('Error!');
                } else {
                    api_req_id = apiResponse.request;
                    logger('Captcha submitted', 'Request ID: ' + api_req_id);
                    toUI('Captcha submitted');
                    setTimeout( () => { getSolved(); }, 15000 );
                }
            },
            onerror: function(e) {
                toUI('Error!');
                logger('Error submitting captcha', e);
            }
        });
    }

    function getSolved() {
        toUI('Waiting for solution...');
        logger('', 'Retrieving solution');
        GM_xmlhttpRequest({
            method: "GET",
            url: api_out(),
            onload: function(response) {
                apiResponse = JSON.parse(this.responseText);
                logger('2C Response when retrieving', apiResponse);

                if (apiResponse.status == 0) {
                    logger('2C Message', apiResponse.request);
                    if (apiResponse.request == 'CAPCHA_NOT_READY') {
                        toUI('Captcha not ready yet...');
                        setTimeout( () => { getSolved(); }, 15000 );
                    } else if (apiResponse.request == 'ERROR_CAPTCHA_UNSOLVABLE') {
                        toUI('Refreshing for retry...');
                        setTimeout( () => { window.location.reload(); }, 2000);
                        return;
                    } else {
                        if (apiResponse.error_text) {
                            toUI('Error: ' + apiResponse.error_text);
                        }
                        if (apiResponse.request) {
                            toUI('Error: ' + apiResponse.request);
                        }
                    }
                } else {
                    // Old/V3 vars:
                    // rsp_challenge = apiResponse.request.geetest_challenge;
                    // rsp_validate = apiResponse.request.geetest_validate;
                    // rsp_seccode = apiResponse.request.geetest_seccode;

                    // V4 Vars:
                    rsp_captcha_id = apiResponse.request.captcha_id;
                    rsp_lot_number = apiResponse.request.lot_number;
                    rsp_pass_token = apiResponse.request.pass_token;
                    rsp_gen_time = apiResponse.request.gen_time;
                    rsp_captcha_output = apiResponse.request.captcha_output;

                    toUI('Results ready. Processing...');
                    //TODO: send to SG
                    // if (rsp_challenge && rsp_validate && rsp_seccode) { // <= old condition
                    if (rsp_captcha_id && rsp_lot_number && rsp_pass_token && rsp_gen_time && rsp_captcha_output) {
                        logger('2C solved the captcha', 'Sending to SG');
                        sendToSg();
                    } else {
                        logger('Something is missing in the response. Not sending it to SG');
                    }
                }
            },
            onerror: function(e) {
                logger('Unexpected error getting solution', e);
                toUI('Error!');
                //TODO: retry in X seconds
            }
        });
    }

    async function sendToSg() {
        logger('Sending to SG');
        let httpData = {
            method: "POST",
            url: "https://miner.stormgain.com/api/v1/activate",
            headers: {
                "accept": "application/json, text/plain, */*",
                "authorization": "Token " + sg_token,
                "client-id": sg_clientId,
                "content-type": "multipart/form-data; boundary=----WebKitFormBoundaryKFzlAnieQFGQSLEZ",
                "cookie": document.cookie,
                "referrer": "https://app.stormgain.com/",
                "referrerPolicy": "strict-origin-when-cross-origin",
                "mode": "cors",
                "credentials": "omit",
            },
            // Old/V3:
            // "data": "------WebKitFormBoundaryGpbvA0qBtFeR1Kuw\r\nContent-Disposition: form-data; name=\"geetest_challenge\"\r\n\r\n" + rsp_challenge + "\r\n------WebKitFormBoundaryGpbvA0qBtFeR1Kuw\r\nContent-Disposition: form-data; name=\"geetest_seccode\"\r\n\r\n" + rsp_seccode + "\r\n------WebKitFormBoundaryGpbvA0qBtFeR1Kuw\r\nContent-Disposition: form-data; name=\"geetest_validate\"\r\n\r\n" + rsp_validate + "\r\n------WebKitFormBoundaryGpbvA0qBtFeR1Kuw--\r\n",
            // Old/V4:
            // "body": "------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_lot_number\"\r\n\r\                       \r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_captcha_output\"\r\n\r\n.                         \r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_pass_token\"\r\n\r\n                      \r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_gen_time\"\r\n\r\n.         1696613876\r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ--\r\n",
            "data": "------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_lot_number\"\r\n\r\n" + rsp_lot_number + "\r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_captcha_output\"\r\n\r\n" + rsp_captcha_output + "\r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_pass_token\"\r\n\r\n" + rsp_pass_token + "\r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ\r\nContent-Disposition: form-data; name=\"geetest_gen_time\"\r\n\r\n" + rsp_gen_time + "\r\n------WebKitFormBoundaryKFzlAnieQFGQSLEZ--\r\n",
            onload: function(response) {
                logger('SG Response', response);
                apiResponse = JSON.parse(this.responseText);
                if (apiResponse.active) {
                    toUI('Success. Refreshing...');
                    logger('SG accepted the solution', 'Refreshing...');
                    setTimeout( () => { window.location.reload(); }, 2000);
                } else {
                    toUI('Error');
                    logger('Something went wrong. Check the SG Response', apiResponse);
                }
            },
            onerror: function(e) {
                logger('Error sending solution to SG', e);
                toUI('Error!');
            }
        };
        // console.log('headers', httpData.headers);
        // console.log('data', httpData.data);
        GM_xmlhttpRequest(httpData);

        return;
    }

    itv_start = setInterval( () => {
        btn_start = document.querySelector('.wrapper .activate');

        if (btn_start) {
            clearInterval(itv_start);
            if (!DISPLAY_UI) {
                start();
                return;
            }

            //load countdown/ui
            statusContainer = btn_start.parentNode.parentNode;
            statusContainer.innerHTML = `<span id="sg2c-msg" class="text-36 leading-9 font-bold text-center sg2c" style="color: #FF9900">Solving in <span id="sg2c-countdown" class="sg2c">${preventStartCountdown}</span>...</span>
            <button id="sg2c-btn" style="background-color: rgb(255, 153, 0)" class="relative inline-flex justify-center items-center flex-shrink-0 bg-accent text-dark-1 text-center select-none cursor-pointer border-none
             self-center rounded px-2 py-2 hover-shadow-big my-5 sg2c"><span class="px-4 text-15 leading-24 font-bold">Stop!, I'll do it manually!</span></button>` + statusContainer.innerHTML
            statusElm = document.getElementById('sg2c-countdown');

            document.getElementById("sg2c-btn").addEventListener("click", function() {
                clearInterval(itv_countdown);
                let elements = document.getElementsByClassName('sg2c');
                for (var element of elements) {
                    element.remove();
                }
                this.innerText = 'Navigate using the menu and come back to the Miner if the button doesn\'t work';
                //this.remove();
                return;
            });
            itv_countdown = setInterval(() => {
                preventStartCountdown = preventStartCountdown-1;
                if (preventStartCountdown < 1) {
                    clearInterval(itv_countdown);
                    document.getElementById("sg2c-btn").remove();
                    document.getElementById("sg2c-msg").innerHTML = 'Started...';
                    start();
                    return;
                }
                if (statusElm) {
                    statusElm.innerText = preventStartCountdown;
                }
            }, 1000);
        }
    }, 1000);
})();