Greasy Fork

Greasy Fork is available in English.

[satology] Auto Claim Multiple Faucets with Monitor UI

Freebitco.in, StormGain Miner, 15 Faucets + Promo Codes + Autologin.

当前为 2021-06-28 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         [satology] Auto Claim Multiple Faucets with Monitor UI
// @description  Freebitco.in, StormGain Miner, 15 Faucets + Promo Codes + Autologin.
// @description  Automatic activation of StormGain Miner every 4 hours and hourly claim of free ADA, BNB, BTC, DASH, DOGE, ETH, LINK, LTC, NEO, STEAM, TRX, USDC, USDT, XEM, XRP
// @version      1.1.1
// @author       satology
// @namespace    satology.onrender.com
// @homepage     https://satology.onrender.com/faucets/referrals

// @note         ----------------------------------------I M P O R T A N T---------------------------------------------------------------------------------------
// @note                   YOU MUST HAVE A hCaptcha solver INSTALLED to claim from the faucets
// @note                   I recommend this script: http://greasyfork.icu/en/scripts/425854-hcaptcha-solver-automatically-solves-hcaptcha-in-browser
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------

// @note         ---------------------------------------- LAST UPDATES ------------------------------------------------------------------------------------------
// @note         [@1.1.0] Added Freebitco.in and autologin for @cryptosfaucets
// @note                   > You can enable autologin/credentials in the config object
// @note                   > You can enable activation of daily RPBonus for FreeBitco.in in the config object
// @note                   > Added boolean setToEnglish (DEFAULT TRUE) at localeConfig object, to change the language of the faucets to English.
// @note         ---------------------------------------- LAST UPDATES ------------------------------------------------------------------------------------------

// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         MAIN FEATURES:
// @note         > Automatic hourly rolls for 16 faucets: Freebitco.in and @CryptosFaucets (ADA, BNB, BTC, DASH, DOGE, ETH, LINK, LTC, NEO, STEAM, TRX, USDC, USDT, XEM, XRP)
// @note         > Automatic activation of StormGain Miner (free BTC every 4 hours)
// @note         > Accepts promotion codes (http://twitter.com/cryptosfaucets, free roll shortlinks) for the 15 faucets
// @note         > Simple Monitor UI on top of a website to track progress (claims, next rolls, promo codes)
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         IMPORTANT CONSIDERATIONS:
// @note         0. You need to enable popups on the Manager UI website to be able to open the faucets
// @note         2. FAUCETS WEBSITES MUST OPEN IN ENGLISH TO BE ABLE TO RECOGNIZE IF THE PROMO CODE WAS ACCEPTED
// @note            In case you don't want to have them in English, you need to change the 3 strings the code uses for validation
// @note            (Search for localeStrings in the code and replace them)
// @note         3. Autorolls will trigger ONLY when the faucet was opened by the Manager UI.
// @note            This is to allow users to navigate the websites to get the ShortLinks extra rolls, for example,
// @note            without having to stop the script.
// @note         4. No AutoLogin implemented yet, so YOU MUST BE LOGGED IN
// @note         5. You can disable faucets from the script in case you need to or you are not registered yet.
// @note            It would be great if you could use my referral links listed below if you need an account.
// @note            To disable them, just set enabled: false in the webList array, save the script & refresh the manager
// @note         6. All data stored for tracking and to be displayed is stored locally in your environment. Nothing is uploaded.

// @note         Always open to feedback. I'd be glad to hear from you if you find any bugs, have suggestions or new enhancements/features you'd like to see
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------

// @note         ----------------------------------------I M P O R T A N T---------------------------------------------------------------------------------------
// @note         [@1.0.11] Modified timeouts and removed auto refresh. YOU MUST HAVE A hCaptcha solver INSTALLED to claim from the faucets now as they've added it and it's working.
// @note                   I recommend this script: http://greasyfork.icu/en/scripts/425854-hcaptcha-solver-automatically-solves-hcaptcha-in-browser
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------

// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         Requested for upcoming updates:
// @note         - Enable/Disable faucet from the Manager UI, add extra column with conversion
// @note         - Implement FaucetPay PTC autoclicker (https://faucetpay.io/?r=1140585)
// @note         - Add bagi.co.in
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------

// @note         Links to create new accounts using my referral:
// @note         https://faucetpay.io/?r=1140585
// @note         https://freebitco.in/?r=41092365
// @note         https://app.stormgain.com/friend/BNS27140552
// @note         https://freecardano.com/?ref=335463
// @note         https://freebinancecoin.com/?ref=161127
// @note         https://freebitcoin.io/?ref=490252
// @note         https://freedash.io/?ref=124083
// @note         https://free-doge.com/?ref=97166
// @note         https://freeethereum.com/?ref=204076
// @note         https://freechainlink.io/?ref=78652
// @note         https://free-ltc.com/?ref=117042
// @note         https://freeneo.io/?ref=100529
// @note         https://freesteam.io/?ref=117686
// @note         https://free-tron.com/?ref=145047
// @note         https://freeusdcoin.com/?ref=100434
// @note         https://freetether.com/?ref=181230
// @note         https://freenem.com/?ref=295274
// @note         https://coinfaucet.io/?ref=808298

// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         If you wanna team up or just share some ideas, you can contact me at [email protected]
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        window.close
// @grant        GM_openInTab
// @icon         https://www.google.com/s2/favicons?domain=stormgain.com
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @match        https://satology.onrender.com/faucets/referrals*
// @match        https://app.stormgain.com/crypto-miner/
// @match        https://freecardano.com/*
// @match        https://freebinancecoin.com/*
// @match        https://freebitcoin.io/*
// @match        https://freedash.io/*
// @match        https://free-doge.com/*
// @match        https://freeethereum.com/*
// @match        https://freechainlink.io/*
// @match        https://free-ltc.com/*
// @match        https://freeneo.io/*
// @match        https://freesteam.io/*
// @match        https://free-tron.com/*
// @match        https://freeusdcoin.com/*
// @match        https://freetether.com/*
// @match        https://freenem.com/*
// @match        https://coinfaucet.io/*
// @match        https://freebitco.in/
// ==/UserScript==

(function() {
    'use strict';
    /**
      * Specific string values to check if a promotion code was succesfully processed (used via indexOf).
      * Defaults are set for English.
      * If you want to view the faucets in another language, you will need to change the following values
      */
    const localeConfig = {
        setToEnglish: true, // will set the faucets to English
        stringSearches: {
            promoCodeAccepted: 'roll',
            promoCodeUsed: 'already used',
            promoCodeInvalid: 'not found',
            promoCodeInvalid2: 'only alphanumeric',
            promoCodeInvalid3: 'ended'
        }
    };

    const WebType = {
        CRYPTOSFAUCETS: 1,
        STORMGAIN: 2,
        FREEBITCOIN: 3
    };
    const CFUrlType = {
        HOME: 0,
        FREE: 1,
        CONTACTTWITTER: 2,
        PROMOTION: 3,
        STATS: 4,
        SETTINGS: 5,
        FREEROLLS: 6,
        IGNORE: 99
    };
    const PromoStatus = {
        NOCODE: 0,
        PENDING: 1,
        ACCEPTED: 2,
        USEDBEFORE: 3,
        INVALID: 4,
        UNKNOWNERROR: 5
    };
    const RandomInteractionLevel = {
        NONE: 0,
        LOW: 1,
        MEDIUM: 2,
        HIGH: 3
    };

    let config = {
        timeout: 4,            // Use -1 to disable the timeout functionality. In minutes. Max time the monitor will wait for a result/roll.
                               // After timeout is reached, the Manager/Monitor will assume something is wrong
                               // (usually faucet tab being accidentally closed or failed to connect)
                               // Will trigger a refresh on the Monitor website and carry on the process
        postponeMinutes: 65,   // Minutes to wait before retrying a faucet that timed out
        cf: {
            autologin: false,
            credentials: {
                mode: 1, // POSSIBLE VALUES:
                         // 1: Use email/password saved through the script
                         // 2: Waits for the credentials to be added manually or by a third party software/extension.
                         //    Useful if you use different email/password combinations on each faucet
                email: '[email protected]',
                password: 'YOURPASSWORD'
            }
        },
        fb: {
            activateRPBonus: false // will try to activate the highest RP Roll Bonus available for the account (100, 50 25, ... points per roll),
        }
    }

    let persistence, shared, manager, ui, CFPromotions, interactions, SGProcessor, CFProcessor, CFHistory, FBProcessor;

    let helpers = {
        cleanString: function(input) {
            var output = "";
            for (var i=0; i<input.length; i++) {
                if (input.charCodeAt(i) <= 127) {
                    output += input.charAt(i);
                }
            }
            return output;
        },
        shuffle: function (array) {
            let currentIndex = array.length, temporaryValue, randomIndex;

            while (0 !== currentIndex) {
                randomIndex = Math.floor(Math.random() * currentIndex);
                currentIndex -= 1;
                temporaryValue = array[currentIndex];
                array[currentIndex] = array[randomIndex];
                array[randomIndex] = temporaryValue;
            }

            return array;
        },
        getPrintableTime: function (date = new Date()) {
            return ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2)
        },
        getPrintableDateTime: function (date) {
            if (date != null) {
                return ('0' + date.getDate()).slice(-2) + '/' + ('0' + (date.getMonth() + 1)).slice(-2) + ' ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
            } else {
                return '';
            }
        },
        getEnumText: function (enm, value) {
            return Object.keys(enm).find(key => enm[key] === value);
        },
        randomMs: function (a, b){
            return a + (b - a) * Math.random();
        },
        addMinutes: function(date, mins) {
            return date.setMinutes(date.getMinutes() + parseInt(mins) + 1);
        },
        randomInt: function(min, max) {
            return Math.floor(Math.random() * (max - min + 1) + min);
        },
        addMilliseconds: function(date, ms) {
            return date.setMilliseconds(date.getMilliseconds() + ms);
        },
        getRandomMillisecondsFromMinutesRange(minute, rangeDiffInPercentage) {
            const msCenter = minute * 60 * 1000;
            const msRangeDiff = Math.round(msCenter * rangeDiffInPercentage / 100);
            return helpers.randomMs(msCenter - msRangeDiff, msCenter + msRangeDiff);
        },
        getEmojiForPromoStatus: function(promoStatus) {
            switch (promoStatus) {
                case PromoStatus.NOCODE:
                    return '⚪';
                    break;
                case PromoStatus.PENDING:
                    return '⏳';
                    break;
                case PromoStatus.ACCEPTED:
                    return '✔️';
                    break;
                case PromoStatus.USEDBEFORE:
                    return '🕙';
                    break;
                case PromoStatus.INVALID:
                    return '❌';
                    break;
                case PromoStatus.UNKNOWNERROR:
                    return '❗';
                    break;
            }
        },
        getHost: function(url, withHttps = false) {
            if (url.includes('//')) {
                url = url.split('//')[1];
            }
            url = url.split('/')[0];
            return withHttps ? ('https://' + url) : url;
        },
        cf: {
            getUrlType: function(url) {
                if (url.endsWith('/free')) {
                    return CFUrlType.FREE;
                }
                if (url.includes('/promotion/')) {
                    return CFUrlType.PROMOTION;
                }
                if (url.endsWith('/contact-twitter')) {
                    return CFUrlType.CONTACTTWITTER;
                }
                if (url.endsWith('/free-rolls')) {
                    return CFUrlType.FREEROLLS;
                }
                if (url.endsWith('/settings')) {
                    return CFUrlType.SETTINGS;
                }
                if (url.endsWith('/stats')) {
                    return CFUrlType.STATS;
                }
                if (url.endsWith('/')) {
                    url = url.slice(0, -1);
                    if (url == helpers.getHost(url, true)) {
                        return CFUrlType.HOME;
                    }
                }

                return CFUrlType.IGNORE;
            }
        }
    }


    let objectGenerator = {
        createPersistence: function() {
            const prefix = 'autoWeb_';
            function save(key, value, parseIt = false) {
                GM_setValue(prefix + key, parseIt ? JSON.stringify(value) : value);
            };
            function load(key, parseIt = false) {
                let value = GM_getValue(prefix + key);
                if(value && parseIt) {
                    value = JSON.parse(value);
                }
                return value;
            };
            return {
                save: save,
                load: load
            };
        },
        createShared: function() {
            let flowControl;
            function isOpenedByManager(currentHost) {
                loadFlowControl();
                if(!flowControl) {
                    return false;
                }
                let millisecondsDistance = new Date() - flowControl.requestedTime;
//                if(flowControl.opened || flowControl.host != currentHost || millisecondsDistance > 120000) {
                if(flowControl.opened || (flowControl.host != currentHost && !flowControl.expectedHosts.includes(currentHost)) || millisecondsDistance > 120000) {
                    return false;
                }
                return true;
            };
            function setFlowControl(id, url, webType, expectedHosts) {
                flowControl = {
                    id: id,
                    url: url,
                    host: helpers.getHost(url),
                    type: webType,
                    requestedTime: new Date(),
                    opened: false,
                    expectedHosts: expectedHosts,
                    error: false,
                    result: {}
                };
                persistence.save('flowControl', flowControl, true);
            };
            function wasVisited(expectedId) {
                loadFlowControl();
                return flowControl.id == expectedId && flowControl.opened;
            };
            function hasErrors(expectedId) {
                return flowControl.id == expectedId && flowControl.error;
            };
            function getResult() {
                return flowControl.result;
            };
            function getCurrent() {
                let current = {};
                current.url = flowControl.url;
                current.host = flowControl.host;
                current.type = flowControl.type;
                return current;
            };
            function saveAndclose(runDetails, delay = 0) {
                markAsVisited(runDetails);
                if(delay) {
                    setTimeout(window.close, delay);
                } else {
                    window.close();
                }
            };
            function loadFlowControl() {
                flowControl = persistence.load('flowControl', true);
            };
            function markAsVisited(runDetails) {
                flowControl.opened = true;
                flowControl.result = runDetails;
                persistence.save('flowControl', flowControl, true);
            };
            function closeWithError(errorType, errorMessage) {
                flowControl.error = true;
                flowControl.result = {
                    errorType: errorType,
                    errorMessage: errorMessage
                };
                persistence.save('flowControl', flowControl, true);
                window.close();
            };
            return {
                setFlowControl: setFlowControl,
                wasVisited: wasVisited,
                isOpenedByManager: isOpenedByManager,
                getCurrent: getCurrent,
                getResult: getResult,
                closeWindow: saveAndclose,
                closeWithError: closeWithError,
                hasErrors: hasErrors
            };
        },
        createManager: function() {
            let timestamp = null;
            let timeWaiting = 0;
            let promoInterval;

            let webList = [
                { id: '1', name: 'ADA', url: 'https://freecardano.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '2', name: 'BNB', url: 'https://freebinancecoin.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '3', name: 'BTC', url: 'https://freebitcoin.io/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '4', name: 'DASH', url: 'https://freedash.io/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '5', name: 'ETH', url: 'https://freeethereum.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '6', name: 'LINK', url: 'https://freechainlink.io/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '7', name: 'LTC', url: 'https://free-ltc.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '8', name: 'NEO', url: 'https://freeneo.io/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '9', name: 'STEAM', url: 'https://freesteam.io/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '10', name: 'TRX', url: 'https://free-tron.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '11', name: 'USDC', url: 'https://freeusdcoin.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '12', name: 'USDT', url: 'https://freetether.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '13', name: 'XEM', url: 'https://freenem.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '14', name: 'XRP', url: 'https://coinfaucet.io/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '15', name: 'StormGain', url: 'https://app.stormgain.com/crypto-miner/', type: WebType.STORMGAIN, enabled: true },
                { id: '16', name: 'DOGE', url: 'https://free-doge.com/free', type: WebType.CRYPTOSFAUCETS, enabled: true },
                { id: '17', name: 'FreeBitco.in', url: 'https://freebitco.in/', type: WebType.FREEBITCOIN, enabled: true }
            ];

            function start(){
                loader.initialize();
                ui.init(getCFlist());
                update();
                promoInterval = setInterval(manager.readNewPromoCode, 5000);
                setTimeout(manager.process, 10000);
            };
            let loader = function() {
                function initialize() {
                    setTimestamp();
                    initializeWebList();
                    initializePromotions();
                    initializeHistory();
                };
                function initializeWebList() {
                    let storedData = persistence.load('webList', true);
                    if(storedData) {
                        let newOnes = addNewOnes(storedData);
                        if (newOnes) {
                            newOnes.forEach( function (element, idx, arr) {
                                storedData.push(element);
                            });
                        }

                        let disabledList = webList.filter( x => !x.enabled ).map( x => x.id );
                        storedData.forEach( function (element, idx, arr) {
                            arr[idx].nextRoll = element.nextRoll ? new Date(element.nextRoll) : new Date();
                            arr[idx].enabled = !disabledList.includes(element.id);
                        });
                        webList = storedData;
                        setup();
                    } else {
                        setup(true);
                    }
                };
                function addNewOnes(storedData) {
                    let allIds = webList.map( x => x.id );
                    let storedIds = storedData.map( x => x.id );

                    let newOnes = allIds.filter( x => !storedIds.includes(x) );

                    return webList.filter( x => newOnes.includes(x.id) );
                };
                function initializePromotions() {
                    let storedData = persistence.load('CFPromotions', true);
                    if (storedData) {
                        storedData.forEach( function (element, idx, arr) {
                            arr[idx].added = new Date(element.added);
                            arr[idx].statusPerFaucet.forEach( function (el, i, a) {
                                a[i].execTimeStamp = (el.execTimeStamp != null) ? new Date(el.execTimeStamp) : null;
                            });
                        });
                        CFPromotions.load(storedData);
                    }
                };
                function initializeHistory() {
                    CFHistory.initOrLoad();
                };
                function setTimestamp() {
                    timestamp = new Date().getTime();
                    persistence.save('timestamp', timestamp);
                };
                function removeDisabledFaucets() {
                    webList = webList.filter(x => x.enabled);
                };
                function setup(reset = false) {
                    removeDisabledFaucets();
                    if(reset) {
                        helpers.shuffle(webList);
                    }

                    let timeDistance = 0;
                    webList.forEach( function (element, idx, arr) {
                        if (reset || !element.lastClaim) {
                            arr[idx].lastClaim = 0;
                        }
                        if (reset || !element.aggregate) {
                            arr[idx].aggregate = 0;
                        }
                        if (reset || !element.balance) {
                            arr[idx].balance = 0;
                        }
                        if (reset || !element.stats) {
                            arr[idx].stats = {};
                        }
                        if (reset || !element.nextRoll) {
                            timeDistance += helpers.randomMs(10000, 15000);
                            arr[idx].nextRoll = new Date(helpers.addMilliseconds(new Date(), timeDistance));
                        }
                    });
                };
                return {
                    initialize: initialize
                };
            }();
            function update(sortIt = true) {
                if(sortIt) {
                    webList.sort((a,b) => a.nextRoll.getTime() - b.nextRoll.getTime());
                }
                persistence.save('webList', webList, true);
                ui.refresh(webList, CFPromotions.getAll());
                updateRollStatsSpan();
            };
            function process() {
                if(isObsolete()) {
                    return;
                }
                if(webList[0].nextRoll.getTime() < (new Date()).getTime()) {
                    ui.log(`Opening ${webList[0].name}`);
                    open();
                } else {
                    let timeUntilNext = webList[0].nextRoll.getTime() - (new Date()).getTime() + helpers.randomMs(1000, 2000);
                    ui.log(`Waiting ${(timeUntilNext/1000/60).toFixed(2)} minutes...`);
                    setTimeout(manager.process, timeUntilNext);
                }
            };
            function isObsolete() {
                let savedTimestamp = persistence.load('timestamp');
                if (savedTimestamp && savedTimestamp > timestamp) {
                    ui.log('<b>STOPING EXECUTION!<b> A new Manager UI window was opened. Process should continue there');
                    clearInterval(promoInterval);
                    return true;
                }
                return false;
            };
            function open(promoCode) {
                let navUrl = webList[0].url;
                if(promoCode) {
                    navUrl = getPromoUrl(promoCode);
                    ui.log(`Opening ${webList[0].name} with Promo Code [${promoCode}]`);
                }

                let expectedHosts = [];
                expectedHosts.push((new URL(navUrl)).host);
                if(webList[0].type == webList[0].type == WebType.STORMGAIN) {
                    expectedHosts.push((new URL('https://www.google.com').host));
                    expectedHosts.push((new URL('https://www.recaptcha.net').host));
                }

                shared.setFlowControl(webList[0].id, navUrl, webList[0].type, expectedHosts);
                setTimeout(manager.resultReader, 15000);
//                GM_openInTab(navUrl, 'loadInBackground');
                GM_openInTab(navUrl, { active: false});
            };

            function getPromoUrl(promoCode) {
                let url = webList[0].url;
                url = url.slice(0, url.length - 4);
                return url + "promotion/" + promoCode;
            }

            function resultReader() {
                if(isObsolete()) {
                    return;
                }

                if(shared.wasVisited(webList[0].id)) {
                    timeWaiting = 0;
                    let result = shared.getResult();

                    if (result) {
                        updateWebListItem(result);
                        if ( (webList[0].type == WebType.CRYPTOSFAUCETS) &&
                            ( (result.claimed) || (result.promoStatus && result.promoStatus != PromoStatus.ACCEPTED) )) {
                                let promoCode = CFPromotions.hasPromoAvailable(webList[0].id);
                                if (promoCode) {
                                    update(false);
                                    open(promoCode);
                                    return;
                                }
                        }
                    } else {
                        ui.log(`Unable to read last run result, for ID: ${webList[0].id} > ${webList[0].name}`);
                    }

                    update(true);
                    process();
                    return;
                } else {
                    timeWaiting += 15;
                    if (!shared.hasErrors(webList[0].id) && !hasTimedOut()) {
                        ui.log(`Waiting for ${webList[0].name} results...`, timeWaiting);
                        setTimeout(manager.resultReader, 15000);
                        return;
                        // Handle Error
                        let errorDetails = shared.getResult();
                        ui.log(`${webList[0].name} closed with error: ${errorDetails.errorType} ${errorDetails.errorMessage}`);
                        let millisecondsDelay = helpers.getRandomMillisecondsFromMinutesRange(config.postponeMinutes, 5);
                        webList[0].nextRoll = new Date(helpers.addMilliseconds(new Date(), millisecondsDelay));
                        update(true);

                        window.location.reload();
                        return;
                    }

                    if (shared.hasErrors(webList[0].id)) {
                        webList[0].stats.errors = shared.getResult();
                        ui.log(`${webList[0].name} closed with error: ${webList[0].stats.errors.errorType} ${webList[0].stats.errors.errorMessage}`);
                    }

                    if (hasTimedOut()) {
                        if(webList[0].stats.countTimeouts) {
                            webList[0].stats.countTimeouts += 1;
                        } else {
                            webList[0].stats.countTimeouts = 1;
                        }

                        ui.log(`Waited too much time for ${webList[0].name} results: triggering timeout`);
                    }

                    let millisecondsDelay = helpers.getRandomMillisecondsFromMinutesRange(config.postponeMinutes, 5);
                    webList[0].nextRoll = new Date(helpers.addMilliseconds(new Date(), millisecondsDelay));
                    update(true);

                    window.location.reload();
                    return;
                }
            };

            function hasTimedOut() {
                return config.timeout != -1 && (timeWaiting > (config.timeout * 60));
            };

            function updateWebListItem(result) {
                ui.log(`Updating data: ${JSON.stringify(result)}`);
                webList[0].stats.countTimeouts = 0;
                webList[0].stats.errors = null;

                if (result.claimed) {
                    result.claimed = parseFloat(result.claimed);
                    if(!isNaN(result.claimed)) {
                        webList[0].lastClaim = result.claimed;
                        webList[0].aggregate += result.claimed;
                    }
                }
                if(result.balance) {
                    webList[0].balance = result.balance;
                }
                if(result.nextRoll) {
                    webList[0].nextRoll = new Date(result.nextRoll);
                }
                if(result.promoStatus) {
                    CFPromotions.updateFaucetForCode(result.promoCode, webList[0].id, result.promoStatus);
                }
                if(result.rolledNumber) {
                    CFHistory.addRoll(result.rolledNumber);
                }
            };

            function readNewPromoCode() {
                let promoCodeElement = $('#promo-code-new')[0];
                let promoCode = helpers.cleanString(promoCodeElement.innerText);
                let promoDisplayStatus = $('#promo-display-status')[0];

                if (promoCode == 'REMOVEALLPROMOS' ) {
                    CFPromotions.removeAll();
                    promoCodeElement.innerText = '';
                    promoDisplayStatus.innerHTML = 'Promo codes removed!';
                    ui.refresh(null, CFPromotions.getAll());
                } else if(promoCode != '') {
                    CFPromotions.addNew(promoCode);
                    promoCodeElement.innerText = '';
                    $('#promo-text-input').val('');
                    promoDisplayStatus.innerHTML = 'Code ' + promoCode + ' added!';
                    ui.log(`Promo code ${promoCode} added`);
                    ui.refresh(null, CFPromotions.getAll());
                }
            };

            function updateRollStatsSpan() {
                let rollsSpanElement = $('#rolls-span')[0];
                rollsSpanElement.innerText = CFHistory.getRollsMeta().join(',');
            };

            function getCFlist() {
                let items;
                items = webList.filter(f => f.type === WebType.CRYPTOSFAUCETS);
                items = items.map(x => {
                    return {
                        id: x.id,
                        name: x.name
                    };});
                items.sort((a, b) => (a.name > b.name) ? 1 : -1);

                return items;
            };
            return{
                init:start,
                process: process,
                resultReader: resultReader,
                getFaucetsForPromotion: getCFlist,
                readNewPromoCode: readNewPromoCode
            };
        },
        createUi: function() {
            let logLines = ['', '', '', '', ''];
            function init(cfFaucets) {
                appendCSS();
                appendJavaScript();
                appendHtml();
                createPromoTable(cfFaucets);
            };
            function appendCSS() {
                let css = '';
                $('head').append(css);
            };
            function appendJavaScript() {
                let js = '';
                js += '<script language="text/javascript">';
                js += 'var myBarChart;';
                js += 'function savePromoCode() {';
                js += 'var promoText = document.getElementById("promo-text-input");';
                js += 'var promoCode = document.getElementById("promo-code-new");';
                js += 'var promoDisplayStatus = document.getElementById("promo-display-status");';
                js += 'promoCode.innerHTML = promoText.value.trim();';
                js += 'promoDisplayStatus.innerHTML = "Adding code&nbsp&quot;<b>" + promoCode.innerHTML + "</b>&quot;... This could take around a minute. Please wait..."';
                js += '}';
                js += 'function removeAllPromos() {';
                js += 'var promoCode = document.getElementById("promo-code-new");';
                js += 'var promoDisplayStatus = document.getElementById("promo-display-status");';
                js += 'promoCode.innerHTML = "REMOVEALLPROMOS";';
                js += 'promoDisplayStatus.innerHTML = "Removing all promotion codes... This could take around a minute. Please wait..."';
                js += '}';
                js += 'function openStatsChart() {';
                js += 'if(myBarChart) { myBarChart.destroy(); }';
                js += 'let statsFragment = document.getElementById("stats-fragment");';
                js += 'if (statsFragment.style.display === "block") { statsFragment.style.display = "none"; document.getElementById("stats-button").innerText = "Lucky Number Stats"; } else {';
                js += 'statsFragment.style.display = "block"; document.getElementById("stats-button").innerText = "Close Stats";';
                js += 'var canvas = document.getElementById("barChart");';
                js += 'var ctx = canvas.getContext("2d");';
                js += 'var dataSpan = document.getElementById("rolls-span");';
                js += 'var data = {';
                js += 'labels: ["0000-9885", "9886-9985", "9986-9993", "9994-9997", "9998-9999", "10000"],';
                js += 'datasets: [ { fill: false, backgroundColor: [ "#990000", "#660066", "#000099", "#ff8000", "#ffff00", "#00ff00"],';
                js += 'data: dataSpan.innerText.split(",") } ] };';
                js += 'var options = { plugins: { legend: { display: false } }, title: { display: true, text: "Rolled Numbers", position: "top" }, rotation: -0.3 * Math.PI };';
                js += 'myBarChart = new Chart(ctx, { type: "pie", data: data, options: options }); } }';
                js += '</script>';

                $('head').append(js);
            };
            function appendHtml() {
                let html ='';
                html += '<pre style="width:100%;" id="console-log"><b>Loading...</b></pre>';
                html += '<section id="stats-fragment" class="fragment" style="display:none;"><div class="container-fluid bg-dark "><div class="container py-1 "><div class="row align-items-center text-center justify-content-center">';
                html += '<div class="col-md-3"><canvas id="barChart"></canvas><span id="rolls-span" style="display:none;"></span></div></div></div></div></div></section>';
                html += '<section id="table-struct" class="fragment "><div class="container-fluid bg-dark "><div class="container py-1 "><div class="row mx-0"><div class="title col-3 px-0 text-white"><h2>Schedule</h2></div>';
                html += '<div class="title col-6 w-100 text-white"></div><div class="title col-3 text-right"><a class="btn  m-2 anchor btn-outline-primary" id="stats-button" onclick="openStatsChart()">Lucky Number Stats</a></div></div>';
                html += '<div class="row align-items-center text-center justify-content-end">';
                html += '<div class="col-12 order-lg-1 text-center"><div class="row justify-content-center"><div class="col table-responsive" id="schedule-container"></div></div></div></div></div></div></section>';
                html +='<section id="table-struct-promo" class="fragment "><div class="container-fluid bg-dark "><div class="container py-1 "><div class="row mx-0">';
                html +='<div class="title col-3 px-0 text-white"><h2>Promo Codes</h2></div><div class="title col-3 w-100 text-white">';
                html +='<div class="input-group my-0"><input type="text" class="form-control py-1" id="promo-text-input" placeholder="Type a Promo code...">';
                html +='<div class="input-group-append"><button class="btn btn-success" id="promo-button" onclick="savePromoCode()"><i class="fa fa-plus"></i></button>';
                html +='</div></div></div><div class="title col-4 text-white justify-content-end"><span id="promo-display-status" class="text-white"></span>';
                html +='<span id="promo-code-new" style="display:none;"></span></div><div class="title col-2 text-right"><a class="btn  m-2 anchor btn-outline-danger" id="promo-button" onclick="removeAllPromos()">Remove All</a>';
                html +='</div></div><div class="row align-items-center text-center justify-content-end"><div class="col-12 order-lg-1 text-center">';
                html +='<div class="row justify-content-center"><div class="col table-responsive" id="promo-container"></div></div></div></div></div></div></section>';

                $('#referral-table').before(html);
                $('#schedule-container').append( createScheduleTable() );
            };
            function createPromoTable(faucets) {
                let tableStructure = '';
                tableStructure += '<table class="table table-bordered text-white" id="promo-table">';
                tableStructure += '<caption style="text-align: -webkit-center;">⏳ Pending ✔️ Accepted 🕙 Used Before ❌ Invalid code ❗ Unknown error ⚪ No code</caption>';
                tableStructure += '<thead><tr><th class="">Code</th><th class="">Added</th>';

                for (let i = 0, all = faucets.length; i < all; i++) {
                    tableStructure += '<th data-faucet-id="' + faucets[i].id + '">' + faucets[i].name + '</th>';
                }

                tableStructure += '</tr></thead><tbody id="promo-table-body"></tbody></table>';

                $('#promo-container').append( tableStructure );
            };
            function createScheduleTable() {
                let tableStructure = '';
                tableStructure += '<table class="table table-bordered text-white" id="schedule-table"><thead><tr>';
                tableStructure += '<th class="hide-on-mobile">#</th><th class="">Name</th><th class="">Last Claim</th>';
                tableStructure += '<th class="hide-on-mobile">Aggregate</th><th class="hide-on-mobile">Balance</th><th class="">Next Roll</th>';
                tableStructure += '</tr></thead><tbody id="schedule-table-body"></tbody></table>';

                return tableStructure;
            };
            function loadScheduleTable(data) {
                let tableBody = '';
                for(let i=0, all = data.length; i < all; i++) {
                    tableBody += '<tr class="align-middle" data-id="' + data[i].id + '">';
                    tableBody +='<td class="align-middle hide-on-mobile">' + (i + 1).toString() + '</td>';
                    tableBody +='<td class="align-middle">' + data[i].name + '</td>';
                    tableBody +='<td class="align-middle">' + data[i].lastClaim.toFixed(8) + '</td>';
                    tableBody +='<td class="align-middle hide-on-mobile">' + data[i].aggregate.toFixed(8) + '</td>';
                    tableBody +='<td class="align-middle hide-on-mobile">' + (data[i].balance ? data[i].balance.split(' ')[0] : "") + '</td>';
                    tableBody +='<td class="align-middle">' + helpers.getPrintableTime(data[i].nextRoll) + addBadges(data[i].stats) + '</td>';
                    tableBody +='</tr>';
                }

                $('#schedule-table-body').html(tableBody);
            };
            function addBadges(stats) {
                let consecutiveTimeout = stats.countTimeouts;
                let otherErrors = stats.errors;
                let html = ' ';

                if (consecutiveTimeout) {
                    html += `<span class="badge badge-pill badge-warning" title="${consecutiveTimeout} consecutive timeouts">${consecutiveTimeout}</span>`;
                }

                if (otherErrors) {
                    html += `<span class="badge badge-pill badge-warning" title="${otherErrors.errorMessage}">${otherErrors.errorType}</span>`;
                }
                return html;
            }
            function loadPromotionTable(codes) {
                let tableBody = '';

                for(let c=0; c < codes.length; c++) {
                    let data = codes[c];
                    tableBody += '<tr data-promotiobn-id="' + data.id + '">';
                    tableBody += '<td class="align-middle">' + data.code + '</td>';
                    tableBody +='<td class="align-middle">' + helpers.getPrintableDateTime(data.added) + '</td>';

                    for(let i=0, all = data.statusPerFaucet.length; i < all; i++) {
                        tableBody +='<td class="align-middle" title="Runned @' + helpers.getPrintableDateTime(data.statusPerFaucet[i].execTimeStamp) + '">' + helpers.getEmojiForPromoStatus(data.statusPerFaucet[i].status ?? 0) + '</td>';
                    }
                    tableBody +='</tr>';
                }

                $('#promo-table-body').html(tableBody);
            };
            function refresh(scheduleData, promotionData) {
                if (scheduleData) {
                    loadScheduleTable(scheduleData);
                }
                if (promotionData) {
                    loadPromotionTable(promotionData);
                }
            };
            function log(msg, elapsed = false) {
                if(msg) {
                    let previous = logLines[0].split('&nbsp')[1];
                    if(elapsed && (previous == msg)) {
                        logLines[0] = helpers.getPrintableTime() + '&nbsp' + msg + '&nbsp[Elapsed time:&nbsp' + elapsed + '&nbspseconds]';
                    } else {
                        logLines.pop();
                        logLines.unshift(helpers.getPrintableTime() + '&nbsp' + msg);
                    }
                    $('#console-log').html(logLines.join('<br>'));
                }
            };
            return {
                init: init,
                refresh: refresh,
                loadPromotionTable: loadPromotionTable,
                log: log
            }
        },
        createCFPromotions: function() {
            let codes = [];

            function PromotionCode(id, code, repeatDaily = false) {
                this.id = id;
                this.code = code;
                this.added = new Date();
                this.statusPerFaucet = [];
                this.repeatDaily = repeatDaily;
                this.lastExecTimeStamp = null;
            };

            function getFaucetStatusInPromo(promo, faucetId) {
                let faucet = promo.statusPerFaucet.find(x => x.id == faucetId);
                return faucet.status ?? PromoStatus.NOCODE;
            };

            function addNew(code) {
                let newPromo = new PromotionCode(codes.length, code);
                newPromo.statusPerFaucet = manager.getFaucetsForPromotion();
                newPromo.statusPerFaucet.forEach(function (element, idx, arr) {
                    arr[idx].status = PromoStatus.PENDING;
                    arr[idx].execTimeStamp = null;
                });

                codes.push(newPromo);
                codes.sort((a, b) => (a.id < b.id) ? 1 : -1);
                save();
            };

            function getAll() {
                return codes;
            };

            function updateFaucetForCode(code, faucetId, newStatus) {
                let promo = codes.find(x => x.code == code);
                let faucet = promo.statusPerFaucet.find(x => x.id == faucetId);
                if(faucet) {
                    faucet.status = newStatus;
                    faucet.execTimeStamp = new Date();
                    promo.lastExecTimeStamp = faucet.execTimeStamp;
                }
                save();
            };

            function hasPromoAvailable(faucetId) {
                let resp = false;
                codes.forEach(function (promotion, idx, arr) {
                    let status = getFaucetStatusInPromo(promotion, faucetId);
                    if (status == PromoStatus.PENDING) {
                        resp = promotion.code;
                        return;
                    }
                });
                return resp;
            };

            function save() {
                persistence.save('CFPromotions', getAll(), true);
            };

            function load(data) {
                codes = data;
            };

            function removeAll() {
                codes = [];
                save();
            };

            return {
                addNew: addNew,
                removeAll: removeAll,
                getAll: getAll,
                load: load,
                updateFaucetForCode: updateFaucetForCode,
                hasPromoAvailable: hasPromoAvailable
            }
        },
        createInteractions: function(){
            let randomInteractionLevel = RandomInteractionLevel.MEDIUM;
            let maxActions = 0;
            let performedActions = -1;
            let selectableElements;
            let actions = {
                available: [
                    function() {
                        $('html, body').animate({
                            scrollTop: helpers.randomInt(0, $('html, body').get(0).scrollHeight)
                        }, {
                            complete: setTimeout(interactions.addPerformed, helpers.randomMs(100, 3000)),
                            duration: helpers.randomMs(100, 1500)
                        });
                    },
                    function() {
                        let element = interactions.selectableElements[helpers.randomInt(0, interactions.selectableElements.length - 1)];

                        try {
                            if (document.body.createTextRange) {
                                const range = document.body.createTextRange();
                                range.moveToElementText(element);
                                range.select();
                            } else if (window.getSelection) {
                                const selection = window.getSelection();
                                const range = document.createRange();
                                range.selectNodeContents(element);
                                selection.removeAllRanges();
                                selection.addRange(range);
                            }
                        } catch (err) { }

                        interactions.addPerformed();
                    }
                ]
            };

            function start(selectableElements) {
                performedActions = 0;
                switch(randomInteractionLevel) {
                    case RandomInteractionLevel.NONE:
                        maxActions = 0;
                        break;
                    case RandomInteractionLevel.LOW:
                        maxActions = helpers.randomInt(2, 4);
                        break;
                    case RandomInteractionLevel.MEDIUM:
                        maxActions = helpers.randomInt(5, 8);
                        break;
                    case RandomInteractionLevel.HIGH:
                        maxActions = helpers.randomInt(12, 16);
                        break;
                }
                interactions.selectableElements = selectableElements;
                performActions();
            }

            function performActions() {
                if(performedActions >= maxActions) {
                    return;
                }
                let delay = 0;
                for(let i = 0; i < maxActions; i++) {
                    delay += helpers.randomMs(350, 1500);
                    setTimeout(actions.available[helpers.randomInt(0, actions.available.length - 1)], delay);
                }
            }

            function addPerformed() {
                performedActions++;
            }
            function completed() {
                return (performedActions >= maxActions);
            }

            return {
                start: start,
                completed: completed,
                addPerformed: addPerformed,
                selectableElements: selectableElements
            };
        },
        createSGProcessor: function() {
            let timerSpans;
            function run() {
                if(isLoading()) {
                    setTimeout(SGProcessor.run, helpers.randomMs(5000, 10000));
                    return;
                } else {
                    if(isMinerActive()) {
                        processRunDetails();
                    } else {
                        activateMiner();
                    }
                }
            };
            function isLoading() {
                return $('#loader-logo').length;
            };
            function isMinerActive() {
                timerSpans = $('.mb-8 .wrapper .mb-1 span');
                if(timerSpans.length > 0) {
                    return true;
                } else {
                    return false;
                }
                return (timerSpans.length === 0);
            };
            function activateMiner() {
                const activateButton = document.querySelector('.mb-8 .wrapper button');
                if (activateButton) {
                    activateButton.click();
                    setTimeout(SGProcessor.processRunDetails, helpers.randomMs(10000, 20000));
                } else {
                    if(!is404Error()) {
                        SGProcessor.processRunDetails()
                    }
                }
            };

            function is404Error() {
                const h1 = document.getElementsByTagName('h1');
                if (h1.length > 0 && h1[0].innerText.includes('404')) {
                    window.location.reload();
                    return true;
                }
                return false;
            }

            function processRunDetails() {
                let result = {};
                result.nextRoll = helpers.addMinutes(new Date(), readCountdown().toString());
                result.balance = readBalance();
                shared.closeWindow(result);
            };
            function readCountdown() {
                let mins = 15;
                try {
                    let timeLeft = timerSpans.last().text().split(':');
                    if(timeLeft.length === 3) {
                        mins = parseInt(timeLeft[0]) * 60 + parseInt(timeLeft[1]);
                    }
                } catch (err) { }
                return mins;
            };
            function readBalance() {
                let balance = "";
                try {
                    balance = $('span.text-accent').first().text() + " BTC";
                } catch (err) { }
                return balance;
            };
            return {
                run: run,
                processRunDetails: processRunDetails
            };
        },
        createCFProcessor: function() {
            const NavigationProcess = {
                ROLLING: 1,
                PROCESSING_PROMOTION: 2,
                LOGIN: 3
            };
            let navigationProcess;
            let countdown;
            let rollButton;
            let promotionTag;
            let timeWaiting= 0;
            let loopingForErrors = false;

            function run() {
                navigationProcess = NavigationProcess.ROLLING;
                displayStatusUi();

                setTimeout(findCountdownOrRollButton, helpers.randomMs(2000, 5000));
            };

            function doLogin() {
                navigationProcess = NavigationProcess.LOGIN;
                displayStatusUi();

                setTimeout(findLoginForm, helpers.randomMs(2000, 5000));
            };

            function isFullyLoaded() { //Waits 55 seconds max
                if(document.readyState == 'complete' || timeWaiting == -1) {
                    $('#process-status')[0].innerHTML = 'Interacting';
                    timeWaiting = 0;
                    interact();
                } else {
                    timeWaiting = -1;
                    $('#process-status')[0].innerHTML = 'Waiting for document fully loaded';
                    setTimeout(CFProcessor.isFullyLoaded, helpers.randomMs(45000, 55000));
                }
            };
            function runPromotion() {
                navigationProcess = NavigationProcess.PROCESSING_PROMOTION
                displayStatusUi();
                setTimeout(CFProcessor.findPromotionTag, helpers.randomMs(1000, 3000));
            };
            function findCountdownOrRollButton() {
                if( isCountdownVisible() && !isRollButtonVisible() ) {
                    timeWaiting = 0;
                    processRunDetails();
                } else if ( !isCountdownVisible() && isRollButtonVisible() ) {
                    timeWaiting = 0;
                    setTimeout(CFProcessor.isFullyLoaded, helpers.randomMs(1000, 5000));
                } else {
                    if (timeWaiting/1000 > config.timeout * 60) {
                        shared.closeWithError('TIMEOUT', '');
                        return;
                    }

                    timeWaiting += 3000;
                    setTimeout(findCountdownOrRollButton, helpers.randomMs(2000, 5000));
                }
            };
            function findLoginForm() {
                if ( $('div.login-wrapper').is(':visible') ) {
                    //Other possible error is if recaptcha did not load yet... so maybe wait til the web is fully loaded for low connection issues
                    if( $('.login-wrapper .error').length > 0 && $('.login-wrapper .error')[0].innerHTML != '') {
                        let errorMessage = $('.login-wrapper .error').text();
                        shared.closeWithError('LOGIN_ERROR', errorMessage);
                        return;
                    }
                    if(!loopingForErrors) {
                        if(config.cf.credentials.mode == 1) {
                            timeWaiting = 0;
                            $('.login-wrapper input[name="email"]').val(config.cf.credentials.email);
                            $('.login-wrapper input[name="password"]').val(config.cf.credentials.password);
                            $('.login-wrapper button.login').click();
                            loopingForErrors = true;
                        } else {
                            if($('.login-wrapper input[name="email"]').val() != '' && $('.login-wrapper input[name="password"]').val() != '') {
                                $('.login-wrapper button.login').click();
                                $('#process-status')[0].innerHTML = 'Processing';
                                loopingForErrors = true;
                            } else {
                                $('#process-status')[0].innerHTML = 'Waiting for credentials...';
                                if (timeWaiting/1000 > (config.timeout / 1.5) * 60) {
                                    shared.closeWithError('LOGIN_ERROR', 'No credentials were provided');
                                    return;
                                }
                            }
                        }
                    }
                }

                if (timeWaiting/1000 > config.timeout * 60) {
                    shared.closeWithError('TIMEOUT', '');
                    return;
                }

                timeWaiting += 3000;
                setTimeout(findLoginForm, helpers.randomMs(2000, 5000));
            };
            function interact() {
                let selectables = []
                selectables = selectables.concat($('td').toArray());
                selectables = selectables.concat($('p').toArray());
                selectables = selectables.concat($('th').toArray());

                interactions.start(selectables);
                setTimeout(CFProcessor.waitInteractions, helpers.randomMs(2000, 4000));
            }
            function waitInteractions() {
                if(interactions.completed()) {
                    roll();
                } else {
                    setTimeout(CFProcessor.waitInteractions, helpers.randomMs(2000, 4000));
                }
            }
            function isCountdownVisible() {
                countdown = $('.timeout-wrapper');
                return ($(countdown).length > 0 && $(countdown[0]).is(':visible'));
            };
            function isRollButtonVisible() {
                rollButton = $('.main-button-2.roll-button.bg-2');
                return ($(rollButton).length > 0 && $(rollButton[0]).is(':visible'));
            };
            function roll() {
                $('#process-status')[0].innerHTML = 'Roll triggered';
                $(rollButton[0]).click();
                setTimeout(findCountdownOrRollButton, helpers.randomMs(2000, 3000));
            }
            function isPromotionTagVisible() {
                let pTags = $('p');
                if (pTags.length > 0) {
                    promotionTag = $('p')[0];
                    return true;
                }
                return false;
            };
            function findPromotionTag() {
                if( isPromotionTagVisible() ) {
                    processRunDetails();
                } else {
                    setTimeout(CFProcessor.findPromotionTag, helpers.randomMs(2000, 5000));
                }
            };
            function processRunDetails() {
                let result = {};
                if(navigationProcess == NavigationProcess.ROLLING) {
                    result.nextRoll = readCountdown();
                    result.claimed = readClaimed();
                    result.balance = readBalance();
                    if(result.claimed != 0) {
                        result.rolledNumber = readRolledNumber();
                    }
                    result.balance = readBalance();
                } else if (navigationProcess == NavigationProcess.PROCESSING_PROMOTION) {
                    result.promoStatus = readPromoStatus();
                    result.promoCode = readPromoCode();
                    if (result.promoStatus == PromoStatus.ACCEPTED) {
                        result.nextRoll = helpers.addMinutes(new Date(), "-120");
                    }
                }
                shared.closeWindow(result);
            };
            function readCountdown() {
                let minsElement = $('.timeout-container .minutes .digits');
                let mins = "0";
                if ($(minsElement).length > 0) {
                    mins = $(minsElement)[0].innerHTML;
                }
                if (mins) {
                    return helpers.addMinutes(new Date(), mins.toString());
                } else {
                    return null;
                }
            };
            function readClaimed() {
                let claimed = 0;
                try {
                    claimed = $('.result')[0].innerHTML;
                    claimed = claimed.trim();
                    claimed = claimed.slice(claimed.lastIndexOf(" ") + 1);
                } catch(err) { }
                return claimed;
            };
            function readRolledNumber() {
                let number = 0;
                try {
                    number = $('.lucky-number').toArray().map(x => x.innerText).join('');
                    number = parseInt(number);
                } catch(err) { }
                return number;
            };
            function readBalance() {
                let balance = "";
                try {
                    balance = $('.navbar-coins.bg-1 a').first().text();
                } catch(err) { }
                return balance;
            };
            function readPromoStatus() {
                let promoStatus = PromoStatus.UNKNOWNERROR;
                try {
                    if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeAccepted) > 0) {
                        return PromoStatus.ACCEPTED;
                    } else if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeUsed) > 0) {
                        return PromoStatus.USEDBEFORE;
                    } else if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeInvalid) > 0) {
                        return PromoStatus.INVALID;
                    } else if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeInvalid2) > 0) {
                        return PromoStatus.INVALID;
                    } else if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeInvalid3) > 0) {
                        return PromoStatus.INVALID;
                    }
                } catch ( err ) { }
                return promoStatus;
            };
            function validatePromoString() {

            };
            function readPromoCode() {
                var urlSplit = window.location.href.split('/');
                return urlSplit[urlSplit.length - 1];
            };
            function displayStatusUi() {
                $( 'body' ).prepend( '<div class="withdraw-button bg-2" style="top:30%; z-index:1500;" href="#">⚙️ <span id="process-status">Processing</span></div>' );
            };
            return {
                run: run,
                runPromotion: runPromotion,
                findPromotionTag: findPromotionTag,
                waitInteractions: waitInteractions,
                isFullyLoaded: isFullyLoaded,
                doLogin: doLogin
            };
        },
        createCFHistory: function() {
            let rollsMeta = [
                { id: 0, range: '0000-9885', count: 0 },
                { id: 1, range: '9886-9985', count: 0 },
                { id: 2, range: '9986-9993', count: 0 },
                { id: 3, range: '9994-9997', count: 0 },
                { id: 4, range: '9998-9999', count: 0 },
                { id: 5, range: '10000', count: 0 }
            ];

            function initOrLoad() {
                let storedData = persistence.load('CFHistory', true);
                if(storedData) {
                    rollsMeta = storedData;
                }
            };

            function addRoll(number) {
                switch(true) {
                    case (number <= 9885):
                        rollsMeta[0].count++;
                        break;
                    case (number <= 9985):
                        rollsMeta[1].count++;
                        break;
                    case (number <= 9993):
                        rollsMeta[2].count++;
                        break;
                    case (number <= 9997):
                        rollsMeta[3].count++;
                        break;
                    case (number <= 9999):
                        rollsMeta[4].count++;
                        break;
                    case (number == 10000):
                        rollsMeta[5].count++;
                        break;
                    default:
                        break;
                }
                save();
            };

            function getRollsMeta() {
                return rollsMeta.map(x => x.count);
            };

            function save() {
                persistence.save('CFHistory', rollsMeta, true);
            };

            return {
                initOrLoad: initOrLoad,
                addRoll: addRoll,
                getRollsMeta: getRollsMeta
            }
        },
        createFBProcessor: function() {
            let timeWaiting= 0;
            let countdownMinutes;
            function run() {
                setTimeout(findCountdownOrRollButton, helpers.randomMs(12000, 15000));
            };
            function findCountdownOrRollButton() {
                if ( isCountdownVisible() ) {
                    countdownMinutes = document.querySelectorAll('.free_play_time_remaining.hasCountdown .countdown_amount')[0] + 1;
                    timeWaiting = 0;
                    processRunDetails();
                    return;
                }

                if ( isRollButtonVisible() ) {
                    if (config.fb.activateRPBonus) {
                        if (!document.getElementById('bonus_container_free_points')) {
                            document.querySelector('a.rewards_link').click();
                            activateBonus(0);
                        }
                    }
                    if (isHCaptchaVisible()) {
                        waitForCaptcha();
                    } else {
                        clickRoll();
                    }
                }
            };
            function isCountdownVisible() {
                return document.querySelectorAll('.free_play_time_remaining.hasCountdown .countdown_amount').length > 0;
            };
            function isHCaptchaVisible() {
                let hCaptchaFrame = document.querySelector('.h-captcha > iframe');
                if (hCaptchaFrame && $(hCaptchaFrame).is(':visible')) {
                    return true;
                }
                return false;
            };
            function isRollButtonVisible() {
                return $(document.getElementById('free_play_form_button')).is(':visible');
            };
            function waitForCaptcha() {
                if ( document.querySelector('.h-captcha > iframe').getAttribute('data-hcaptcha-response').length > 0) {
                    clickRoll();
                } else {
                    if (timeWaiting/1000 > config.timeout * 60) {
                        shared.closeWithError('TIMEOUT', '');
                        return;
                    }

                    timeWaiting += 10000;
                    setTimeout(waitForCaptcha, helpers.randomMs(10000, 12000));
                }
            };
            function clickRoll() {
                try {
                    document.getElementById('free_play_form_button').click();
                    setTimeout(processRunDetails, helpers.randomMs(3000, 10000));
                } catch (err) {
                    shared.closeWithError('CLICK_ROLL_ERROR', err);
                }
            };
            function processRunDetails() {
                if ($(document.getElementById('winnings')).is(':visible')) {
                    closePopup();

                    let result = {};
                    result.claimed = readClaimed();
                    result.balance = readBalance();
                    if(result.claimed != 0) {
                        result.rolledNumber = readRolledNumber();
                        result.nextRoll = helpers.addMinutes(new Date(), "60");
                    }
                    shared.closeWindow(result);
                    return;
                }

                if ($('.free_play_result_error').is(':visible')) {
                    shared.closeWithError('ROLL_ERROR', $('.free_play_result_error')[0].innerHTML);
                    return;
                }

                if ($(document.getElementById('same_ip_error')).is(':visible')) {
                    shared.closeWithError('ROLL_ERROR', document.getElementById('same_ip_error').innerHTML);
                    return;
                }

                if (timeWaiting/1000 > config.timeout * 60) {
                    shared.closeWithError('TIMEOUT', '');
                    return;
                }

                timeWaiting += 5000;
                setTimeout(processRunDetails, helpers.randomMs(5000, 6000));
            };
            function closePopup() {
                let closePopupBtn = document.querySelector('.reveal-modal.open .close-reveal-modal');
                if (closePopupBtn) {
                    closePopupBtn.click();
                }
            };
            function readRolledNumber() {
                let rolled = 0;
                try {
                    rolled = parseInt([... document.querySelectorAll('#free_play_digits span')].map( x => x.innerHTML).join(''));
                } catch { }
                return rolled;
            };
            function readBalance() {
                let balance = 0;
                try {
                    balance = document.getElementById('balance').innerHTML;
                } catch { }
                return balance;
            };
            function readClaimed() {
                let claimed = 0;
                try {
                    claimed = document.getElementById('winnings').innerHTML;
                } catch { }
                return claimed;
            };

            function activateBonus(i) {
                if($(document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_error')).is(':visible')) {
                    let closeBtn = document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_box_close')
                    if ($(closeBtn).is(':visible')) {
                        closeBtn.click();
                    }
                } else if ($(document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_success')).is(':visible')) {
                    let closeBtn = document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_box_close')
                    if ($(closeBtn).is(':visible')) {
                        closeBtn.click();
                        document.querySelector('#free_play_link_li a');
                        setTimeout(findCountdownOrRollButton, helpers.randomMs(10000, 12000));
                        return;
                    }
                }

                try {
                    let redeemButtons = document.querySelectorAll('#free_points_rewards button');
                    redeemButtons[i].click();
                    i = i + 1;
                } catch (err) {
                }

                if(i > 4) {
                    document.querySelector('#free_play_link_li a');
                    setTimeout(findCountdownOrRollButton, helpers.randomMs(10000, 12000));
                    return;
                }
                setTimeout(activateBonus.bind(null, i), 5000);
            };
            return {
                run: run
            };
        }
    };


    /**
    * Prevents alert popups to be able to reload the faucet if invisible captcha validation fails
    */
    function overrideSelectNativeJS_Functions () {
        window.alert = function alert (message) {
            console.log (message);
        }
    }
    function addJS_Node (text, s_URL, funcToRun) {
        var scriptNode= document.createElement ('script');
        scriptNode.type= "text/javascript";
        if (text)scriptNode.textContent= text;
        if (s_URL)scriptNode.src= s_URL;
        if (funcToRun)scriptNode.textContent = '(' + funcToRun.toString() + ')()';

        var element = document.getElementsByTagName ('head')[0] || document.body || document.documentElement;
        element.appendChild (scriptNode);
    }

    function detectWeb() {
        // Now detecting based on host to handle automatic redirects (for example: login request)
        if(!shared.isOpenedByManager(window.location.host)) {
            return;
        }

        let currentFromManager = shared.getCurrent();

        if (currentFromManager.type == WebType.STORMGAIN) {
            SGProcessor = objectGenerator.createSGProcessor();
            setTimeout(SGProcessor.run, helpers.randomMs(10000, 20000));
            return;
        }

        if (currentFromManager.type == WebType.CRYPTOSFAUCETS) {
            let expectedCfUrlType = helpers.cf.getUrlType(currentFromManager.url);
            let realCfUrlType = helpers.cf.getUrlType(window.location.href);

            switch(expectedCfUrlType) {
                case CFUrlType.FREE:
                    switch(realCfUrlType) {
                        case CFUrlType.FREE:
                            if(localeConfig.setToEnglish) {
                                let refValue = $('.nav-item a')[4].innerHTML;
                                if (refValue != 'Settings') {
                                    window.location.href = '/set-language/en';
                                }
                            }
                            addJS_Node (null, null, overrideSelectNativeJS_Functions);
                            CFProcessor = objectGenerator.createCFProcessor();
                            interactions = objectGenerator.createInteractions();
                            setTimeout(CFProcessor.run, helpers.randomMs(1000, 3000));
                            break;
                        case CFUrlType.CONTACTTWITTER:
                            //TODO: mark as possibly banned
                            break;
                        case CFUrlType.HOME:
                            if (config.cf.autologin) {
                                addJS_Node (null, null, overrideSelectNativeJS_Functions);
                                CFProcessor = objectGenerator.createCFProcessor();
                                setTimeout(CFProcessor.doLogin, helpers.randomMs(1000, 3000));
                            } else {
                                shared.closeWithError('NEED_TO_LOGIN', '');
                            }
                            break;
                        default:
                            break;
                    }
                    break;
                case CFUrlType.PROMOTION:
                    CFProcessor = objectGenerator.createCFProcessor();
                    interactions = objectGenerator.createInteractions();
                    setTimeout(CFProcessor.runPromotion, helpers.randomMs(5000, 10000));
                    break;
            }

            return;
        }

        if (currentFromManager.type == WebType.FREEBITCOIN) {
            FBProcessor = objectGenerator.createFBProcessor();
            setTimeout(FBProcessor.run, helpers.randomMs(2000, 5000));
            return;
        }
    }

    function init() {
        shared = objectGenerator.createShared();
        persistence = objectGenerator.createPersistence();
        if(window.location.host === 'satology.onrender.com') {
            manager = objectGenerator.createManager();
            CFPromotions = objectGenerator.createCFPromotions();
            ui = objectGenerator.createUi();
            CFHistory = objectGenerator.createCFHistory();

            manager.init();
        } else {
            detectWeb();
        }
    }
    init();
})();