Stormgain with 2Captcha (Miner)

Solves Stormgain Miner Captcha (GeeTest) using 2Captcha service

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
})();