Greasy Fork

Greasy Fork is available in English.

[satology] Auto Claim Multiple Faucets with Monitor UI

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

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

您需要先安装一款用户脚本管理器扩展,例如 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 with 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.2.1
// @author       satology
// @namespace    satology.onrender.com
// @homepage     https://satology.onrender.com/faucets/referrals

// @note         - IMPORTANT ----------------------------------------------------------------------------------------------------------------------------------------------------
// @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.2.1]  Fixed BUG that didn't let new users enable the faucets, and changed the reusable promo codes interval
// @note         [@1.2.0]  What's new?
// @note                   > Activate/Deactivate faucets (from UI)
// @note                   > Run ASAP a specific faucet (from UI)
// @note                   > Change faucets 'display' name (from the UI)
// @note                   > Reusable promo codes option (by selecting 'Daily' from UI when adding a code)
// @note                   > Remove promo codes individually
// @note                   > FIAT conversion column, filled by the website using CMC Tickers
// @note                   > Some refactor on how the data is stored
// @note                   + Run ASAP and Faucet changes are not always immediate. If the script is claiming, it will wait until it's finished to avoid data errors.
// @note                   + FIAT conversion is done from the website as I was having some issues with the event listeners. Also, the ticker doesn't seem to be
// @note                     updating itself unless you change the fiat, so I'm open to suggestions if you know of other plug-n-play tickers
// @note                   + 'Reusable' promo codes are those codes that you can run every 24hs on every CF faucet. You might know that, at least for now,
// @note                      shortlink promo codes are always the same. I recommend doing the shortlinks daily anyway just to be safe.
// @note                      Reusable codes will be reprocessed automatically after 1 day for each faucet if their status is ACCEPTED or USEDBEFORE.
// @note                      You can verify the last run datetime by placing your mouse over the emojis.
// @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         ----------------------------------------------------------------------------------------------------------------------------------------------------------------

// @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 CF 15 faucets
// @note         > Simple Monitor UI on top of a website to track progress (claims, next rolls, promo codes)

// @note         - IMPORTANT CONSIDERATIONS -------------------------------------------------------------------------------------------------------------------------------------
// @note         0. You need to enable popups on the Manager UI website to be able to open the faucets
// @note         1. 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 and change setToEnglish to false
// @note            (Search for localeStrings in the code)
// @note         2. 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         3. You can enable/disable faucets from the UI. By default they are all set to false.
// @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         4. You can change the config object to enable autologin, RP free roll bonus, etc.
// @note         5. 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         DISCLAIMER: This script is shared to help. Use at your own discretion. I've being using it for months and works fine but I cannot
// @note         guarantee that the faucets won't ban your IP or account
// @note         ----------------------------------------------------------------------------------------------------------------------------------------------------------------
// @note         Requested for upcoming updates:
// @note         - Implement FaucetPay PTC autoclicker (https://faucetpay.io/?r=1140585)
// @note         - Add bagi.co.in
// @note         ----------------------------------------------------------------------------------------------------------------------------------------------------------------

// @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', 'only alphanumeric'],
            promoCodeExpired: ['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,
        EXPIRED: 6
    };
    const RandomInteractionLevel = {
        NONE: 0,
        LOW: 1,
        MEDIUM: 2,
        HIGH: 3
    };
    const HS_26_IN_MILLISECONDS = 93600000; // Using 26 hs instead of 24hs, 
    const HS_2_IN_MILLISECONDS = 7200000;   // and 2hs gap retry when code is flagged as USEDBEFORE

    let config = {
        defaults: {
            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)
            postponeMinutes: 65,   // Minutes to wait before retrying a faucet that timed out
            focusTab: false    // If false the faucets will be opened in the background
        },
        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()) {
            if (date == null) {
                return '';
            }
            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.EXPIRED:
                    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 && !flowControl.expectedHosts.includes(currentHost)) || millisecondsDistance > 120000) {
                    return false;
                }
                return true;
            };
            function setFlowControl(id, url, webType, expectedHosts) {
                flowControl = {
                    id: id,
                    url: url,
                    host: url.host,
                    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() {
            const STATUS = {
                INITIALIZING: 0,
                IDLE: 1,
                CLAIMING: 2
            };

            let timestamp = null;
            let timeWaiting = 0;
            let uiUpdatesInterval;
            let status = STATUS.INITIALIZING;
            let processTimer;

            let webList = [];

            const sites = [
                { id: '1', name: 'CF ADA', cmc: '2010', coinRef: 'ADA', url: new URL('https://freecardano.com/free'), rf: '?ref=335463', type: WebType.CRYPTOSFAUCETS },
                { id: '2', name: 'CF BNB', cmc: '1839', coinRef: 'BNB', url: new URL('https://freebinancecoin.com/free'), rf: '?ref=161127', type: WebType.CRYPTOSFAUCETS },
                { id: '3', name: 'CF BTC', cmc: '1', coinRef: 'BTC', url: new URL('https://freebitcoin.io/free'), rf: '?ref=490252', type: WebType.CRYPTOSFAUCETS },
                { id: '4', name: 'CF DASH', cmc: '131', coinRef: 'DASH', url: new URL('https://freedash.io/free'), rf: '?ref=124083', type: WebType.CRYPTOSFAUCETS },
                { id: '5', name: 'CF ETH', cmc: '1027', coinRef: 'ETH', url: new URL('https://freeethereum.com/free'), rf: '?ref=204076', type: WebType.CRYPTOSFAUCETS },
                { id: '6', name: 'CF LINK', cmc: '1975', coinRef: 'LINK', url: new URL('https://freechainlink.io/free'), rf: '?ref=78652', type: WebType.CRYPTOSFAUCETS },
                { id: '7', name: 'CF LTC', cmc: '2', coinRef: 'LTC', url: new URL('https://free-ltc.com/free'), rf: '?ref=117042', type: WebType.CRYPTOSFAUCETS },
                { id: '8', name: 'CF NEO', cmc: '1376', coinRef: 'NEO', url: new URL('https://freeneo.io/free'), rf: '?ref=100529', type: WebType.CRYPTOSFAUCETS },
                { id: '9', name: 'CF STEAM', cmc: '1230', coinRef: 'STEEM', url: new URL('https://freesteam.io/free'), rf: '?ref=117686', type: WebType.CRYPTOSFAUCETS },
                { id: '10', name: 'CF TRX', cmc: '1958', coinRef: 'TRX', url: new URL('https://free-tron.com/free'), rf: '?ref=145047', type: WebType.CRYPTOSFAUCETS },
                { id: '11', name: 'CF USDC', cmc: '3408', coinRef: 'USDC', url: new URL('https://freeusdcoin.com/free'), rf: '?ref=100434', type: WebType.CRYPTOSFAUCETS },
                { id: '12', name: 'CF USDT', cmc: '825', coinRef: 'USDT', url: new URL('https://freetether.com/free'), rf: '?ref=181230', type: WebType.CRYPTOSFAUCETS },
                { id: '13', name: 'CF XEM', cmc: '873', coinRef: 'XEM', url: new URL('https://freenem.com/free'), rf: '?ref=295274', type: WebType.CRYPTOSFAUCETS },
                { id: '14', name: 'CF XRP', cmc: '52', coinRef: 'XRP', url: new URL('https://coinfaucet.io/free'), rf: '?ref=808298', type: WebType.CRYPTOSFAUCETS },
                { id: '15', name: 'StormGain', cmc: '1', coinRef: 'BTC', url: new URL('https://app.stormgain.com/crypto-miner/'), rf: 'friend/BNS27140552', type: WebType.STORMGAIN },
                { id: '16', name: 'CF DOGE', cmc: '74', coinRef: 'DOGE', url: new URL('https://free-doge.com/free'), rf: '?ref=97166', type: WebType.CRYPTOSFAUCETS },
                { id: '17', name: 'FreeBitco.in', cmc: '1', coinRef: 'BTC', url: new URL('https://freebitco.in/'), rf: '?r=41092365', type: WebType.FREEBITCOIN }
            ];

            function start() {
                loader.initialize();
                ui.init(getCFlist());
                update();
                uiUpdatesInterval = setInterval(readUpdateValues, 10000);
                processTimer = setTimeout(manager.process, 2000);
            };
            let loader = function() {
                function initialize() {
                    setTimestamp();
                    initializeWebList();
                    initializePromotions();
                    initializeHistory();
                };
                function initializeWebList() {
                    addSites();
                    addStoredData();
                };
                function addSites() {
                    sites.forEach(x => webList.push(x));
                    webList.forEach(function (element, idx, arr) {
                        arr[idx].enabled = false;
                        arr[idx].lastClaim = 0;
                        arr[idx].aggregate = 0;
                        arr[idx].balance = 0;
                        arr[idx].stats = {};
                        arr[idx].nextRoll = null;
                    });
                };
                function addStoredData() {
                    let storedData = persistence.load('webList', true);
                    if(storedData) {
                        storedData.forEach( function (element) {
                            let idx = webList.findIndex(x => x.id == element.id);
                            if(idx != -1) {
                                webList[idx].name = element.name ?? webList[idx].name;
                                webList[idx].lastClaim = element.lastClaim ?? webList[idx].lastClaim;
                                webList[idx].aggregate = element.aggregate ?? webList[idx].aggregate;
                                webList[idx].balance = element.balance ?? webList[idx].balance;
                                webList[idx].stats = element.stats ?? webList[idx].stats;
                                webList[idx].enabled = element.enabled ?? webList[idx].enabled;
                                if(!webList[idx].enabled) {
                                    webList[idx].nextRoll = null;
                                } else {
                                    webList[idx].nextRoll = element.nextRoll ? new Date(element.nextRoll) : new Date();
                                }
                                //webList[idx].focusTab = element.focusTab ?? webList[idx].focusTab;
                            }
                        });
                    } else {
//                        helpers.shuffle(webList);
                    }
                };
                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);
                };
                return {
                    initialize: initialize
                };
            }();
            function readUpdateValues(forceCheck = false) {
                readPromoCodeValues();

                if(status == STATUS.IDLE || forceCheck) {
                    let updateDataElement = $('#update-data')[0];
                    let updateValues = helpers.cleanString(updateDataElement.innerText);

                    if (updateValues != '') {
                        updateDataElement.innerText = '';
                        let updateObj = JSON.parse(updateValues);
                        if(updateObj.runAsap.changed) {
                            updateObj.runAsap.ids.forEach(function (element, idx, arr) {
                                try {
                                    let itemIndex = webList.findIndex(x => x.id == element)
                                    webList[itemIndex].enabled = true;
                                    webList[itemIndex].nextRoll = new Date(754000 + idx);
                                    ui.log(`${webList[itemIndex].name} updated to run ASAP`);
                                } catch (err) {
                                    ui.log(`Error setting faucet to run ASAP: ${err}`);
                                }
                            });
                        }
                        if(updateObj.editSingle.changed) {
                            updateObj.editSingle.items.forEach(function (element, idx, arr) {
                                try {
                                    let itemIndex = webList.findIndex(x => x.id == element.id);
                                    webList[itemIndex].name = element.displayName;

                                    if (webList[itemIndex].enabled != element.enabled) {
                                        webList[itemIndex].enabled = element.enabled;
                                        if(webList[itemIndex].enabled) {
                                            webList[itemIndex].nextRoll = new Date(idx);
                                        } else {
                                            webList[itemIndex].nextRoll = null;
                                        }
                                    }
                                    ui.log(`Faucet updated. New name: ${element.displayName}. Active: ${element.enabled}`);
                                } catch (err) {
                                    ui.log(`Error updating faucet data: ${err}`);
                                }
                            });
                        }

                        if(updateObj.runAsap.changed || updateObj.editSingle.changed) {
                            $('#faucets-display-status')[0].innerHTML = '';
                            update(true);
                            process();
                            return;
                        }
                    }
                }
                if(forceCheck) {
                    process();
                }
            };
            function update(sortIt = true) {
                if(sortIt) {
                    webList.sort( function(a,b) {
                        if (a === b) {
                            return 0;
                        } else if (a.nextRoll === null) {
                            return 1;
                        } else if (b.nextRoll === null) {
                            return -1;
                        } else {
                            return a.nextRoll.getTime() < b.nextRoll.getTime() ? -1 : 1;
                        }
                    });
                }

                saveWebList();
                ui.refresh(webList, CFPromotions.getAll());
                updateRollStatsSpan();
            };
            function saveWebList() {
                const data = webList.map(x => {
                    return {
                        id: x.id,
                        name: x.name,
                        lastClaim: x.lastClaim,
                        aggregate: x.aggregate,
                        balance: x.balance,
                        stats: x.stats,
                        nextRoll: x.nextRoll,
                        enabled: x.enabled
                    };});

                persistence.save('webList', data, true);
            }
            function process() {
                if(isObsolete()) {
                    return;
                }

                if(webList[0].nextRoll == null) {
                    ui.log(`All faucets are disabled. Click edit and select those you want to run...`);
                    clearTimeout(processTimer);
                    status = STATUS.IDLE;
                    return;
                }

                if(webList[0].nextRoll.getTime() < (new Date()).getTime()) {
                    ui.log(`Opening ${webList[0].name}`);
                    clearTimeout(processTimer);
                    status = STATUS.CLAIMING;
                    open();
                } else {
                    let timeUntilNext = webList[0].nextRoll.getTime() - (new Date()).getTime() + helpers.randomMs(1000, 2000);
                    ui.log(`Waiting ${(timeUntilNext/1000/60).toFixed(2)} minutes...`);
                    clearTimeout(processTimer);
                    processTimer = setTimeout(manager.process, timeUntilNext);
                    status = STATUS.IDLE;
                }
            };
            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(uiUpdatesInterval);
                    return true;
                }
                return false;
            };
            function open(promoCode) {
                let navUrl = webList[0].url;
                if(promoCode) {
                    navUrl = new URL('promotion/' + promoCode, webList[0].url.origin);
                    ui.log(`Opening ${webList[0].name} with Promo Code [${promoCode}]`);
                }

                let expectedHosts = [];
                expectedHosts.push(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.href, { active: webList[0].focusTab ?? config.defaults.focusTab });
            };

            function getPromoUrl(promoCode) {
                return new URL('promotion/' + promoCode, webList[0].origin);
            }

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

                    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.defaults.postponeMinutes, 5);
                    webList[0].nextRoll = new Date(helpers.addMilliseconds(new Date(), millisecondsDelay));
                    update(true);

                    timeWaiting = 0;
                    readUpdateValues(true);
                    return;
                }
            };

            function hasTimedOut() {
                return config.defaults.timeout != -1 && (timeWaiting > (config.defaults.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 readPromoCodeValues() {
                let promoCodeElement = $('#promo-code-new')[0];
                let promoDataStr = helpers.cleanString(promoCodeElement.innerText);
                let promoDisplayStatus = $('#promo-display-status')[0];
                if (promoDataStr == '') {
                    promoDisplayStatus.innerHTML = '';
                    return;
                }

                let promoData = JSON.parse(promoDataStr);

                if(promoData.action) {
                    switch (promoData.action) {
                        case 'ADD':
                            CFPromotions.addNew(promoData.code, promoData.repeatDaily);
                            promoCodeElement.innerText = '';
                            $('#promo-text-input').val('');
                            promoDisplayStatus.innerHTML = 'Code ' + promoData.code + ' added!';
                            ui.log(`Promo code ${promoData.code} added`);
                            ui.refresh(null, CFPromotions.getAll());
                            break;
                        case 'REMOVEALLPROMOS':
                            CFPromotions.removeAll();
                            promoCodeElement.innerText = '';
                            promoDisplayStatus.innerHTML = 'Promo codes removed!';
                            ui.log(`Promo codes removed`);
                            ui.refresh(null, CFPromotions.getAll());
                            break;
                        case 'REMOVE':
                            if(CFPromotions.remove(promoData.id, promoData.code) != -1) {
                                ui.log(`Promo code ${promoData.code} removed`);
                            } else {
                                ui.log(`Unable to remove code ${promoData.code}`);
                            }
                            promoCodeElement.innerText = '';
                            ui.refresh(null, CFPromotions.getAll());
                            break;
                    }
                }
            };

            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.coinRef
                    };});
                items.sort((a, b) => (a.name > b.name) ? 1 : -1);

                return items;
            };
            return{
                init:start,
                process: process,
                resultReader: resultReader,
                getFaucetsForPromotion: getCFlist,
                readPromoCodeValues: readPromoCodeValues
            };
        },
        createUi: function() {
            let logLines = ['', '', '', '', ''];
            function init(cfFaucets) {
                appendCSS();
                appendJavaScript();
                appendHtml();
                createPromoTable(cfFaucets);
                document.querySelector('.page-title h1').innerHTML = 'Auto Claim';
            };
            function appendCSS() {
                let css = '<style>';
                css += 'td.em-input {';
                css += 'padding-top: 0;';
                css += 'padding-bottom: 0;';
                css += '}';
                css += 'td.em-input input {';
                css += '}';
                css += '</style>';
                $('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 += 'var promoDaily = document.getElementById("promo-daily");';
                js += 'var promoObject = { action: "ADD", code: promoText.value.trim(), repeatDaily: promoDaily.checked };'
                js += 'promoCode.innerHTML =JSON.stringify(promoObject);';
                js += 'promoDisplayStatus.innerHTML = "Adding &nbsp&quot;<b>" + promoObject.code + "</b>&quot;...<br>This could take around a minute. Please wait..."';
                js += '}';
                js += 'function removePromoCode(id, code) {';
                js += 'var promoCode = document.getElementById("promo-code-new");';
                js += 'var promoDisplayStatus = document.getElementById("promo-display-status");';
                js += 'var promoObject = { action: "REMOVE", id: id, code: code };'
                js += 'promoCode.innerHTML =JSON.stringify(promoObject);';
                js += 'promoDisplayStatus.innerHTML = "Removing code &nbsp&quot;<b>" + code + "</b>&quot;...<br>This could take around a minute. Please wait..."';
                js += '}';


                js += 'function getUpdateObject() {';
                js += 'let updateObject;';
                js += 'var updateData = document.getElementById("update-data");';
                js += 'if (updateData.innerHTML != "") {';
                js += 'updateObject = JSON.parse(updateData.innerHTML);';
                js += '} else {';
                js += 'updateObject = { runAsap: { ids: [], changed: false}, editSingle: { changed: false, items: [] } };';
                js += '}';
                js += 'return updateObject;';
                js += '}';
                js += 'function editList() {';
                js += '$.each($("#schedule-table-body td.em-input"), function(x, y) {';
                js += 'let val = y.innerHTML;';
                js += 'y.innerHTML = "<input type=\'text\' data-original=\'" + val + "\' value=\'" + val + "\' />";});';
                js += '$.each($("#schedule-table-body td.edit-status"), function(x, y) {';
                js += 'let activeSwitch = y.querySelector("input");';
                js += 'y.classList.remove("d-none");';
                js += '});';
                js += '$.each($(".em-only"), (x, y) => y.classList.remove("d-none"));';
                js += '$.each($(".em-hide"), (x, y) => y.classList.add("d-none"));';
                js += '}';
                js += 'function editListSave() {';
                js += '  let updateObject = getUpdateObject();';
                js += '  $.each($("#schedule-table-body tr"), function(x, row) {';
                js += '    let textInputCell = row.querySelector(".em-input");';
                js += '    let textInput = textInputCell.querySelector("input");';
                js += '    let activeSwitch = row.querySelector("td.edit-status input");';
                js += '    let single = { id: row.dataset.id, displayName: textInput.dataset.original, enabled: activeSwitch.dataset.original };';
                js += '    textInputCell.innerHTML = textInput.value;';
                js += '    if(textInput.dataset.original != textInput.value) {';
                js += '      single.displayName = textInput.value;';
                js += '    }';
                js += '    if(activeSwitch.dataset.original != Boolean(activeSwitch.checked)) {';
                js += '      single.enabled = Boolean(activeSwitch.checked);';
                js += '    }';
                js += '    if(textInput.dataset.original != textInput.value || activeSwitch.dataset.original != Boolean(activeSwitch.checked)) {';
                js += '      updateObject.editSingle.items.push(single);';
                js += '      updateObject.editSingle.changed = true;';
                js += '    }';
                js += '  });';
                js += '  if(updateObject.editSingle.changed) {';
                js += '    document.getElementById("update-data").innerHTML = JSON.stringify(updateObject);';
                js += '    document.getElementById("faucets-display-status").innerHTML = "Data will be updated as soon as possible...";';
                js += '  }';
                js += '$.each($(".em-only"), (x, y) => y.classList.add("d-none"));';
                js += '$.each($(".em-hide"), (x, y) => y.classList.remove("d-none"));';
                js += '}';
                js += 'function editListCancel() {';
                js += '$.each($("#schedule-table-body td.em-input input"), function(x, y) {';
                js += 'let val = y.dataset.original;';
                js += 'y.parentNode.innerHTML = val;});';
                js += '$.each($(".em-only"), (x, y) => y.classList.add("d-none"));';
                js += '$.each($(".em-hide"), (x, y) => y.classList.remove("d-none"));';
                js += '}';


                js += 'function updateValues(type, values) {';
                js += 'let updateObject = getUpdateObject();';
                js += 'if (type == "runAsap") {';
                js += 'updateObject.runAsap.ids.push(values.id);';
                js += 'updateObject.runAsap.changed = true;';
                js += 'document.getElementById("update-data").innerHTML = JSON.stringify(updateObject);';
                js += 'document.getElementById("faucets-display-status").innerHTML = "Faucet will be updated to run as soon as possible...";';
                js += '}';
                js += '}';


                js += 'function removeAllPromos() {';
                js += 'var promoCode = document.getElementById("promo-code-new");';
                js += 'var promoDisplayStatus = document.getElementById("promo-display-status");';
                js += 'var promoObject = {  action: "REMOVEALL" };'
                js += 'promoCode.innerHTML =JSON.stringify(promoObject);';
                js += 'promoDisplayStatus.innerHTML = "Removing all promotion codes...<br>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="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"><span id="faucets-display-status" class="text-white"></span></div><div class="title col-3 text-right">';
                html += '<div class="em-only d-none"><a class="btn m-2 anchor btn-outline-success align-middle" onclick="editListSave()"><i class="fa fa-check-circle"></i> Save</a>';
                html += '<a class="btn m-2 anchor btn-outline-danger align-middle" onclick="editListCancel()"><i class="fa fa-times-circle"></i> Cancel</a></div>';
                html += '<div class="em-hide"><a class="btn m-2 anchor btn-outline-primary align-middle" onclick="editList()"><i class="fa fa-edit"></i> Edit</a></div>';
                html += '</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>';
                html += '<span id="update-data" style="display:none;"></span></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 mb-1">';
                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"><div class="mx-2"><label class="switch" title="Check if the code can be reused every 24hs"><input id="promo-daily" type="checkbox"><span class="slider round"></span>Daily</label></div><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>';
                html +='<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>';
                html += '<section class="fragment"><div class="container-fluid bg-dark ">';
                html += '<div class="row justify-content-center"><a class="btn  m-2 anchor btn-outline-primary" id="stats-button" onclick="openStatsChart()">CF Lucky Number Stats</a></div>';
                html +='<div class="container py-1" id="stats-fragment" style="display:none;"><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>';

                $('#referral-table').before(html);
                $('#schedule-container').append( createScheduleTable() );
            };
            function createPromoTable(faucets) {
                let tableStructure = '';
                tableStructure += '<table class="table table-striped table-dark" 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-striped table-dark" id="schedule-table"><thead><tr>';
                tableStructure += '<th scope="col" class="edit-status d-none em-only" style="">Active</th><th class="">#</th><th class="">Name</th><th class="">Site</th><th class="">Last Claim</th>';
                tableStructure += '<th class="">Aggregate</th><th class="">Balance</th><th class="" id="converted-balance-col">FIAT</th><th class="">Next Roll</th>';
                tableStructure += '<th scope="col" class="">Msgs</th><th scope="col" class="em-hide" style="">Run ASAP</th></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 + '" data-cmc="' + data.find(x => x.id == data[i].id).cmc + '" data-balance="' + (data[i].balance ? data[i].balance.split(' ')[0] : "") + '">';
                    tableBody +='<td class="align-middle edit-status d-none em-only"><label class="switch"><input type="checkbox" data-original="' + (data[i].enabled ? '1' : '0') + '" ' + (data[i].enabled ? 'checked' : ' ') + '><span class="slider round"></span></label></td>';
                    tableBody +='<td class="align-middle">' + (data[i].enabled ? (i + 1).toString() : '-') + '</td>';
                    tableBody +='<td class="align-middle em-input" data-field="displayName">' + data[i].name + '</td>';
                    tableBody +='<td class="align-middle text-left" style="width: 20%">';
                    tableBody +='<a class="edit-site" title="Visit site" target="_blank" href="' + (new URL(data[i].rf, data[i].url.origin)).href + '"><i class="fa fa-external-link-alt"></i></a> ';
                    tableBody +=data[i].url.host + '</td>';
                    tableBody +='<td class="align-middle">' + data[i].lastClaim.toFixed(8) + '</td>';
                    tableBody +='<td class="align-middle">' + data[i].aggregate.toFixed(8) + '</td>';
                    tableBody +='<td class="align-middle">' + (data[i].balance ? data[i].balance.split(' ')[0] : "") + '</td>';
                    tableBody +='<td class="align-middle fiat-conversion"></td>';
                    tableBody +='<td class="align-middle">' + helpers.getPrintableTime(data[i].nextRoll) + '</td>';
                    tableBody +='<td class="align-middle">' + addBadges(data[i].stats) + '</td>';
                    tableBody +='<td class="align-middle em-hide"><a class="edit-site" data-toggle="tooltip" data-placement="left" title="Run ASAP" href="javascript:updateValues(\'runAsap\', { id: ' + data[i].id + ' })" onclick=""><i class="fa fa-redo"></i></a></td>';
                    tableBody +='</tr>';
                }

                $('#schedule-table-body').html(tableBody);

                location.href = 'javascript:convertToFiat();';
            };

            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-promotion-id="' + data.id + '">';
                    tableBody += '<td class="align-middle text-left ' + (data.repeatDaily ? 'text-warning' : '') + '">';
                    tableBody += '<a data-toggle="tooltip" data-placement="left" title="Remove" href="javascript:removePromoCode(' + data.id + ', \'' + data.code + '\')" onclick=""><i class="fa fa-times-circle"></i></a> ';
                    tableBody += '<span  title="' + (data.repeatDaily ? 'Reusable Code' : 'One-time-only Code') + '">' + data.code + '</span></td>';
                    tableBody +='<td class="align-middle" title="' + (data.repeatDaily ? 'Reusable Code' : 'One-time-only Code') + '">' + 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);
                if (faucet.status && promo.repeatDaily) {
                    //Using 26 hs instead of 24hs, and 2hs gap retry when code is flagged as USEDBEFORE
                    if((faucet.status == PromoStatus.ACCEPTED && (Date.now() - faucet.execTimeStamp.getTime()) > HS_26_IN_MILLISECONDS)
                      || (faucet.status == PromoStatus.USEDBEFORE && (Date.now() - faucet.execTimeStamp.getTime()) > HS_2_IN_MILLISECONDS)) {
                        faucet.status = PromoStatus.PENDING;
                    }
                }
                return faucet.status ?? PromoStatus.NOCODE;
            };

            function addNew(code, repeatDaily = false) {
                let newPromo = new PromotionCode(codes.length, code, repeatDaily);
                newPromo.statusPerFaucet = manager.getFaucetsForPromotion().map(x => {
                    return {
                        id: x.id,
                    };});
                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();
            };

            function remove(id, code) {
                let idx = codes.findIndex(x => x.id == id && x.code == code);
                if(idx != -1) {
                    codes.splice(idx, 1);
                    save();
                }

                return idx;
            };

            return {
                addNew: addNew,
                removeAll: removeAll,
                remove: remove,
                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 (hasPopup()) {
                        closePopup();
                        setTimeout(SGProcessor.run, helpers.randomMs(5000, 10000));
                } else {
                    if(isMinerActive()) {
                        processRunDetails();
                    } else {
                        activateMiner();
                    }
                }
            };
            function hasPopup() {
                return $('.wrapper.grid.min-h-0.md-min-h-1-2.md-relative.md-rounded-lg.md-bg-dark-4 svg circle').length > 0;
            };
            function closePopup() {
                try {
                    $('svg.flex.w-8.h-8.fill-current')[0].parentElement.click();
                } catch { }
            };
            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.defaults.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.defaults.timeout / 1.5) * 60) {
                                    shared.closeWithError('LOGIN_ERROR', 'No credentials were provided');
                                    return;
                                }
                            }
                        }
                    }
                }

                if (timeWaiting/1000 > config.defaults.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(-20), "0");
                    }
                }
                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.promoCodeExpired) > 0) {
                        return PromoStatus.EXPIRED;
                    } else if(localeConfig.stringSearches.promoCodeInvalid.findIndex(x => promotionTag.innerHTML.indexOf(x) > -1) == -1) {
                        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() ) {
                    timeWaiting = 0;
                    countdownMinutes = +document.querySelectorAll('.free_play_time_remaining.hasCountdown .countdown_amount')[0].innerHTML + 1;
                    let result = {};
                    result.balance = readBalance();
                    result.nextRoll = helpers.addMinutes(new Date(), countdownMinutes.toString());

                    shared.closeWindow(result);
                    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.defaults.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($('#free_play_error').is(':visible')) {
                    shared.closeWithError('ROLL_ERROR', $('.free_play_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.defaults.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() {
        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();
})();