Greasy Fork

Greasy Fork is available in English.

Auto Claim with Monitor/Manager UI: StormGain Miner + 15 Faucets + Promo Codes processing

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

当前为 2021-05-18 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Auto Claim with Monitor/Manager UI: StormGain Miner + 15 Faucets + Promo Codes processing
// @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.0.11
// @author       satology
// @namespace    satology.onrender.com
// @homepage     https://satology.onrender.com/faucets/referrals
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         MAIN FEATURES:
// @note         > Automatic hourly rolls for 15 faucets (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           You can flag promo codes to be used once every 24hs, since (as far as I know) shortlink codes are always the same and don't expire
// @note         > Simple Monitor UI on top of a website to track progress (claims, next rolls, promo codes)
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         > The idea is to release future versions with more faucets & PTC (some for FaucetPay/ExpressCrypto) and user-friendly configurations
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         IMPORTANT CONSIDERATIONS:
// @note         0. You need to enable popups on the Manager UI website to be able to open the faucets
// @note         1. Promo codes (for now) must be manually added through the Manager UI:
// @note            You can add multiple codes and will be processed for each faucet after rolling.
// @note            After adding a new promo code, it will take a minute or so to save it. Then will
// @note            try to activate it right AFTER the next scheduled roll for each faucet and roll again.
// @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         About the code:
// @note         Manager UI (monitor):
// @note         - Let's you keep track of last claimed amount, accumulated claims, total balance and next rolls.
// @note         - Controls the 'flow'. Opens a new tab to roll when needed and reads the results.
// @note         - Let's you add the promo codes and shows you the status of them for each faucet
// @note         - Everything is stored locally, but the Manager UI runs on top of a personal website with some ads
// @note           You can, of course, replace it with another URL, but please consider keeping it as a 'thank you' if you find the script useful/helpful
// @note         - HTML and CSS are basic/simple as the goal is only to show status data and I'm not a UI/UX expert
// @note         SGProcessor:
// @note         - Works on StormGain website
// @note         - Activates the miner whenever is stopped (every 4 hours)
// @note         - Saves the balance to be displayed on the Manager UI
// @note         CFProcessor:
// @note         - Works on the 15 faucets (.../free)
// @note         - Creates some random 'interaction'. You can disable interactions or adjust them a little.
// @note           Search for RandomInteractionLevel
// @note         - After clicking the roll button, waits for the countdown or reloads the page if the invisible captcha validation fails
// @note         - Stores the claimed amount, balance, rolled number and time for the Manager UI to update itself
// @note         - If the Roll button is not there, stores the countdown value to adjust the Manager UI next roll time
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         For upcoming updates:
// @note         - Keep a second window always opened to do all the navigation
// @note         - Extra antibot random actions like going back and forward from a random page in the website (FAQ, Stats, etc.)
// @note         - Display a message in the faucets UI to let the user know the process current status
// @note         - AutoLogin using local variables to store the credentials
// @note         - Enable/Disable faucet from the Manager UI
// @note         - Code refactor
// @note         - Implement FaucetPay PTC autoclicker (https://faucetpay.io/?r=1140585)
// @note         - Implement Freebitco.in autoclaim (https://freebitco.in/?r=41092365)
// @note         - Implement autonavigator for captcha faucets to automatically open and prompt for input when you can claim
// @note         - Implement auto claim for other faucets with 'weak' captcha validations
// @note         ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         Links to create a new account using my referral:
// @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_log
// @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/*
// ==/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 are viewing the faucets in another language, you will need to change them or
      * switch the faucets to English
      */
    const localeConfig = {
        stringSearches: {
            promoCodeAccepted: 'roll',
            promoCodeUsed: 'already used',
            promoCodeInvalid: 'not found',
            promoCodeInvalid2: 'only alphanumeric',
            promoCodeInvalid3: 'ended'
        }
    };

    const WebType = {
        CRYPTOSFAUCETS: 1,
        STORMGAIN: 2
    };
    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 = {
        maxRefreshAttempts: 0, // Use -1 to do unlimit attempts. Is the number of consecutive times it will try to refresh a faucet when failing to roll due to invisible captcha
                               // ("Please Refresh the page to prove you are not a robot")
                               // After reaching maxRefreshAttempts, the script will move to the next faucet, to prevent being flagged which could ban the IP for 24hs in that specific faucet.
        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
        moveAfterTimeout: true, // If set to true, after a timeout the Monitor will skip the faucet, move to the next one and refresh itself. If false, it will refresh and retry the same faucet
        postponeMinutes: 65   // Minutes to wait before retrying, after a timeout or failure due to maxRefreshAttempts
    }

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

    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) {
                    return false;
                }
                return true;
            };
            function setFlowControl(id, url, webType) {
                flowControl = {
                    id: id,
                    url: url,
                    host: helpers.getHost(url),
                    type: webType,
                    requestedTime: new Date(),
                    opened: false,
                    maxRefreshAttempts: config.maxRefreshAttempts,
                    refreshAttempts: 0,
                    maxRefreshReached: false,
                    result: {}
                };
                persistence.save('flowControl', flowControl, true);
            };
            function wasVisited(expectedId) {
                loadFlowControl();
                return flowControl.id == expectedId && flowControl.opened;
            };
            function wasMaxRefreshReached(expectedId) {
                loadFlowControl();
                return flowControl.id == expectedId && flowControl.maxRefreshReached;
            };
            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 closeWithMaxRefreshReached(delay = 0) {
                markAsMaxRefreshReached();
                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 markAsMaxRefreshReached() {
                flowControl.maxRefreshReached = true;
                persistence.save('flowControl', flowControl, true);
            };
            function canDoRefresh() {
                flowControl.refreshAttempts++;
                persistence.save('flowControl', flowControl, true);
                if (flowControl.maxRefreshAttempts == -1 || flowControl.maxRefreshAttempts >= flowControl.refreshAttempts) {
                    return true;
                } else {
                    return false;
                }
            }
            return {
                setFlowControl: setFlowControl,
                wasVisited: wasVisited,
                isOpenedByManager: isOpenedByManager,
                getCurrent: getCurrent,
                getResult: getResult,
                closeWindow: saveAndclose,
                wasMaxRefreshReached: wasMaxRefreshReached,
                closeWithMaxRefreshReached: closeWithMaxRefreshReached,
                canDoRefresh: canDoRefresh
            };
        },
        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 }
            ];

            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}]`);
                }
                shared.setFlowControl(webList[0].id, navUrl, webList[0].type);
                setTimeout(manager.resultReader, 15000);
                GM_openInTab(navUrl, 'loadInBackground');
            };

            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 if (shared.wasMaxRefreshReached(webList[0].id)) {
                    timeWaiting = 0;
                    let millisecondsDelay = helpers.getRandomMillisecondsFromMinutesRange(config.postponeMinutes, 5);
                    webList[0].nextRoll = new Date(helpers.addMilliseconds(new Date(), millisecondsDelay));

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

                    ui.log(`Skipping ${webList[0].name}: too many unsuccessful attempts to roll`);
                    update(true);
                    process();
                    return;
                } else {
                    timeWaiting += 15;

                    if (config.timeout != -1 && (timeWaiting > (config.timeout * 60)) ) {
                        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`);
                        if (config.moveAfterTimeout) {
                            let millisecondsDelay = helpers.getRandomMillisecondsFromMinutesRange(config.postponeMinutes, 5);
                            webList[0].nextRoll = new Date(helpers.addMilliseconds(new Date(), millisecondsDelay));
                            update(true);
                        }

                        window.location.reload();
                        return;
                    }

                    ui.log(`Waiting for ${webList[0].name} results...`, timeWaiting);
                    setTimeout(manager.resultReader, 15000);
                }
            };

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

                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 consecutiveRefresh = stats.countMaxRefreshReached;
                let html = ' ';

                if (consecutiveTimeout) {
                    html += `<span class="badge badge-pill badge-warning" title="${consecutiveTimeout} consecutive timeouts">${consecutiveTimeout}</span>`;
                }
                if (consecutiveRefresh) {
                    html += `<span class="badge badge-pill badge-danger" title="${consecutiveRefresh} consecutive failed attempts to roll">${consecutiveRefresh}</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
            };
            let navigationProcess;
            let countdown;
            let rollButton;
            let promotionTag;
            let timeWaiting= 0;

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

                setTimeout(CFProcessor.findCountdownOrRollButton, 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 {
                    timeWaiting += 3000;
                    setTimeout(CFProcessor.findCountdownOrRollButton, 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(CFProcessor.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 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,
                findCountdownOrRollButton: findCountdownOrRollButton,
                findPromotionTag: findPromotionTag,
                waitInteractions: waitInteractions,
                isFullyLoaded: isFullyLoaded
            };
        },
        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
            }
        }
    };


    /**
    * 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:
                            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:
                            //TODO: implement autologin
                            break;
                        default:
                            break;
                    }
                    break;
                case CFUrlType.PROMOTION:
                    CFProcessor = objectGenerator.createCFProcessor();
                    interactions = objectGenerator.createInteractions();
                    setTimeout(CFProcessor.runPromotion, helpers.randomMs(5000, 10000));
                    break;
            }

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