Rotator Speed

Claim from SpeedSatoshi's faucets

As of 2022-05-21. See the latest version.

// ==UserScript==
// @name         Rotator Speed
// @namespace    speed.satology
// @version      1.1.3
// @author       satology
// @match        https://*.speedsatoshi.com/faucet*
// @match        https://pepperlark.neocities.org/speedsatoshi.html
// @match        https://pepperlark.neocities.org/speedsatoshi.html/clearlog
// @icon         https://www.google.com/s2/favicons?sz=64&domain=speedsatoshi.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @description Claim from SpeedSatoshi's faucets
// ==/UserScript==

(function() {
    'use strict';
    const USE_LOG = true;
    // const TIMEOUT = 5 * 60 * 1000;
    const landing = 'https://pepperlark.neocities.org/speedsatoshi.html';

    const wait = ms => new Promise(resolve => setTimeout(resolve, ms || 5000));
    const btnSelector = '#claim';

    let faucetList = [
        { url: 'www.speedsatoshi.com' },
        { url: 'ethereum.speedsatoshi.com' },
        { url: 'binance-coin.speedsatoshi.com' },
        { url: 'bitcoin-cash.speedsatoshi.com' },
        { url: 'litecoin.speedsatoshi.com' },
        { url: 'dogecoin.speedsatoshi.com' },
        { url: 'tron.speedsatoshi.com' },
        { url: 'digibyte.speedsatoshi.com' }
    ];

    let scrolled = false;
    let sent = false;

    class Logger {
        static log;
        static listening;
        static async write(msg) {
            if (!USE_LOG) {
                return;
            }

            if (Logger.log == []) {
                await Logger.load();
            }

            if(msg) {
                let last = { ts: 0, msg: '' };
                try { last = Logger.log[Logger.log.length - 1]; } catch (err) { }

                if (!last || last.msg != msg || Date.now() - last.ts > 20000) {
                    Logger.log.push({ ts: Date.now(), msg: msg });
                    Logger.save();
                }
            }
        }
        static clear() {
            Logger.log = [];
            Logger.save();
        }
        static refresh(name, old_value, new_value, remote) {
            if(remote) {
                Logger.log = JSON.parse(new_value);
                Logger.show();
            }
        }
        static show() {
            if (!USE_LOG) {
                return;
            }
            if(!Logger.listening) {
                // document.querySelector('head').removeChild(document.querySelector('style'));
                let bodyElm = document.querySelector('body');
                bodyElm.innerHTML = '';
                let divElm = document.createElement('div');
                divElm.innerHTML = '<p>Click <a href="' + landing + '/clearlog">here</a> to clear the log (only when not claiming)</p><pre style="height: 500px; position: relative; display: block; font-size: 87.5%; overflow: auto;" id="console-log"></pre><input type="checkbox" id="only-claims"><label for="only-claims">Only Claims</label>';
                bodyElm.appendChild(divElm);


                document.querySelector('#only-claims').onchange = function (event) { // agrega una function para cuando cambia el estado del checkbox
                    let preElm = document.querySelector('#console-log'); // toma el elemento pre, que tiene todo el log
                    if (event.target.checked) { // si el checkbox esta seleccionado...
                        window.fullLog = preElm.innerHTML.split('<br>'); // guarda en una variable todo el log, en forma de array (para separarlo en lineas en base a <br>
                        preElm.innerHTML = window.fullLog.filter( x => x.includes('Good job')).join('<br>'); // primero filtra para tomar solo las lineas/elementos del array que tienen 'Good Job', y luego une el resultado con <br>
                    } else { // si el checkbox no esta seleccionado
                        preElm.innerHTML = window.fullLog.join('<br>'); // pisa el contenido de <pre> con el log entero, uniendolo con <br> para hacer los saltos de linea
                    }
                };
                GM_addValueChangeListener("devlog", Logger.refresh);
                Logger.listening = true;
            }

            let logElm = document.querySelector('#console-log');
            logElm.innerHTML = Logger.log.map( function (x) {
                let date = new Date(x.ts);
                return `${date.readable()} - ${x.msg}`
            }).join('<br>');

            if (document.querySelector('#only-claims').checked) { // si esta filtrando...
                window.fullLog = logElm.innerHTML.split('<br>'); // guarda en una variable todo el log, en forma de array (para separarlo en lineas en base a <br>
                logElm.innerHTML = window.fullLog.filter( x => x.includes('Good job')).join('<br>'); // primero filtra para tomar solo las lineas/elementos del array que tienen 'Good Job', y luego une el resultado con <br>
            }

            logElm.scrollTop = logElm.scrollHeight;
        }
        static async load() {
            if (!USE_LOG) {
                return;
            }
            Logger.log = await GM_getValue('devlog');
            if (Logger.log) {
                Logger.log = JSON.parse(Logger.log);
            }
            Logger.log = Logger.log ?? [];
        }
        static save() {
            GM_setValue('devlog', JSON.stringify(Logger.log));
        }
    }

    function goNext() {
        let found = faucetList.findIndex( x => x.url == window.location.host );
        let next = ( found + 1 == faucetList.length ? 0 : found + 1 );

        let links = document.querySelectorAll('#full td span a');
        if (links.length != 8) {
            //something changed
            Logger.write(`GOTO Link not found. Using url.`);
            window.location.href = linkFor(faucetList[next].url);
        } else {
            if (links.length >= found + 1) {
                links[next].click();
                Logger.write(`GOTO Link clicked`);
                return;
            }
        }
    }

    function linkFor(host) {
        return 'https://' + host + '/faucet/manual';
    }

    function claimedOk() {
        // <p class="alert alert-info alert-padded" role="alert">Good job! You claimed 0.0000000166 BTC from the faucet.</p>
        let elms = [...document.querySelectorAll('p.alert.alert-info.alert-padded')];
        if (elms.length > 0) {
            let found = elms.find( x => x.innerText.includes('Good job') );
            if (found) {
                Logger.write(`claimedOk returning true: ${found.innerText}`);
                //save claimed
                return true;
            }
        }
        Logger.write(`claimedOk returning false`);
        return false;
    }

    function tokenError() {
        //<p class="alert alert-danger" role="alert">Invalid token. Refresh the page and try again.</p>
        let elms = [...document.querySelectorAll('p.alert.alert-danger')];
        if (elms.length > 0) {
            let found = elms.find( x => x.innerText.includes('Invalid token. Refresh') );
            if (found) {
                Logger.write(`tokenError returning true: ${found.innerText}`);
                return true;
            }
        }
        Logger.write(`tokenError returning false`);
        return false;
    }

    async function isSolved() {
        Logger.write(`Waiting for captcha`);
        return wait().then( () => {
            let elm = document.querySelector('.h-captcha > iframe');
            if (elm && elm.hasAttribute('data-hcaptcha-response') && elm.getAttribute('data-hcaptcha-response').length > 0) {
                Logger.write(`Captcha solved`);
                return Promise.resolve(true);
            }
            return isSolved();
        });
    }
    function scrollTo(elm) {
        Logger.write(`Scrolling to button`);
        if (typeof(elm) == 'string') {
            document.querySelectorAll(elm)[0].scrollIntoView(false);
        } else {
            elm.scrollIntoView(false);
        }
    }

    let looper, adsHandler;

    Logger.load().then( () => {
        if (window.location.href == landing) {
            Number.prototype.padIt = function () {
                return this.toString().padStart(2, '0');
            }
            Date.prototype.readable = function () {
                return `${this.getDate().padIt()}/${(this.getMonth()+1).padIt()} ${this.getHours().padIt()}:${this.getMinutes().padIt()}:${this.getSeconds().padIt()}`;
            }
            Logger.show();
            //setInterval( () => { Logger.show(); }, 15000);
        } else if (window.location.href == landing + '/clearlog') {
            document.querySelector('body').innerHTML = 'Please wait...';
            Logger.clear();
            setTimeout( () => {
                window.location.href = landing;
            }, 10000);
        } else {
            looper = setInterval( () => {
                if (claimedOk()) {
                    Logger.write(`Moving to next faucet`);
                    //go to next faucet
                    clearInterval(looper);
                    setTimeout( () => { goNext(); }, 3000);
                    return;
                }
                if (tokenError()) {
                    //refresh
                    clearInterval(looper);
                    setTimeout( () => { window.location.reload(); }, 3000);
                    return;
                }
                if(!scrolled) {
                    if(document.querySelector(btnSelector)) {
                        scrollTo(btnSelector);
                        scrolled = true;
                    } else {
                        Logger.write(`Claim button not found yet`);
                        // claim button not found yet
                        return;
                    }
                }

                if (!sent) {
                    clearInterval(looper);
                    isSolved().then( () => {
                        Logger.write(`Submiting`);
                        document.querySelector(btnSelector).parentElement.submit();
                        sent = true;
                    });
                }
            }, 3214);
        }

        adsHandler = setInterval( () => {
            let elm = null;
            elm = document.querySelector('div[data-name="stitialer"]');
            if (elm) {
                elm.nextSibling.click();
            }
            elm = document.querySelector('.ex-over-btn');
            if (elm) {
                elm.click();
            }
        }, 10000);
    });
})();