Stormgain with 2Captcha (Miner)

Solves Stormgain Miner Captcha (GeeTest) using 2Captcha service

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
})();