[satology] Auto Claim Multiple Faucets with Monitor UI

Automatic rolls and claims for 50+ crypto faucets/PTC/miners (Freebitco.in BTC, auto promo code for 15 CryptosFaucet, FaucetPay, StormGain, etc)

目前为 2021-09-16 提交的版本。查看 最新版本

// ==UserScript==
// @name         [satology] Auto Claim Multiple Faucets with Monitor UI
// @description  Automatic rolls and claims for 50+ crypto faucets/PTC/miners (Freebitco.in BTC, auto promo code for 15 CryptosFaucet, FaucetPay, StormGain, etc)
// @description  Claim free ADA, BNB, BCH, BTC, DASH, DGB, DOGE, ETH, FEY, LINK, LTC, NEO, STEAM, TRX, USDC, USDT, XEM, XRP, ZEC
// @version      1.8.2
// @author       satology
// @namespace    satology.onrender.com
// @homepage     https://satology.onrender.com/faucets/referrals

// @note         @1.8.2 : Added Dutchy faucets: monthly coin (now is TRX) and DUTCHY (used as 'energy' at their autofaucet)
// @note                  > At Dutchy, you need to set hCaptcha as default captcha. Once in a while it uses the text hCaptcha some it might timeout/take longer
// @note                  > Adjustments to OurBitco process: checking faucets countdowns at dashboard to avoid opening all faucets when possible
// @note                  > UI adjustments: added some single-faucet customization (overrides default values as open in background, timeout) More will be added

// @note         @1.8: Added OurBitco, BetFury BNB/BTC boxes, Free-doge.io
// @note                  > Ourbitco & BetFury are set to run every 30 min aprox. These values are hardcoded/not editable through the UI yet
// @note                  > Ourbitco rolls are done by solving the image captcha (not the recaptcha)

// @note         @1.7.4 : > Ok BCH removed/commented out as the site structure changed [0 mins wait but 1 to 10 sat payment]
// @note                  > Some minor changes in Bagi/Keran to handle Cloudflare hCaptcha validation
// @note                  > Bagi/Keran default/suggested withdraw mode => off/manual

// @note         - IMPORTANT -------------------------------------------------------------------------------------------------------------------------------------------------------------------
// @note                   YOU MUST HAVE A hCaptcha solver INSTALLED to claim from the faucets
// @note                   I recommend this script: https://gf.qytechs.cn/en/scripts/425854-hcaptcha-solver-automatically-solves-hcaptcha-in-browser
// @note         -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// @note         - MAIN FEATURES ------------------------------------------------------------------------------------------------------------------------------------------------
// @note         > Automatic rolls and claims for faucets/PTCs/miners
// @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. CF 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 configuration 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         ----------------------------------------------------------------------------------------------------------------------------------------------------------------
// @note         If you wanna team up or just share some ideas, you can contact me at [email protected]
// @note         ----------------------------------------------------------------------------------------------------------------------------------------------------------------

// @grant        GM_info
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        window.close
// @grant        GM_openInTab
// @icon         https://www.google.com/s2/favicons?domain=stormgain.com
// @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/
// @match        https://faucetpay.io/*
// @match        https://free-litecoin.com/*
// @match        https://www.free-ethereum.io/
// @match        https://www.free-ethereum.io/free/
// @match        https://bagi.co.in/*
// @match        https://keran.co/*
// @match        https://btc-ok.net/*
// @match        https://dash-ok.net/*
// @match        https://dgb-ok.net/*
// @match        https://doge-ok.net/*
// @match        https://eth-ok.net/*
// @match        https://ltc-ok.net/*
// @match        https://trx-ok.net/*
// @match        https://bigbtc.win/*
// @match        https://www.bestchange.com/*
// @match        https://bitking.biz/*
// @match        https://litking.biz/*
// @match        https://faucetok.net/*
// @match        https://ourbitco.in/dashboard*
// @match        https://betfury.io/boxes/all*
// @match        https://www.free-doge.io/
// @match        https://www.free-doge.io/free/
// @match        https://autofaucet.dutchycorp.space/roll.php
// @match        https://autofaucet.dutchycorp.space/coin_roll.php
// ==/UserScript==

(function() {
    'use strict';
    /*// nop@require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
*/
    /**
      * 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 K = Object.freeze({
        WebType: {
            CRYPTOSFAUCETS: 1,
            STORMGAIN: 2,
            FREEBITCOIN: 3,
            FAUCETPAY: 4,
            FREELITECOIN: 5,
            FREEETHEREUMIO: 6,
            BAGIKERAN: 7,
            OKFAUCET: 8,
            BIGBTC: 9,
            BESTCHANGE: 10,
            KINGBIZ: 11,
            OURBITCOIN: 12,
            BETFURYBOX: 13,
            FREEDOGEIO: 14,
            DUTCHYROLL: 15
        },
        CF: {
            UrlType: {
                HOME: 0,
                FREE: 1,
                CONTACTTWITTER: 2,
                PROMOTION: 3,
                STATS: 4,
                SETTINGS: 5,
                FREEROLLS: 6,
                IGNORE: 99
            },
            PromoStatus: {
                NOCODE: 0,
                PENDING: 1,
                ACCEPTED: 2,
                USEDBEFORE: 3,
                INVALID: 4,
                UNKNOWNERROR: 5,
                EXPIRED: 6
            },
            ReusableCodeSuggestions: ['55khv20st4', '90nq6mcmz2', 'lytovoap04', 'vmuph8j0c6', 'ykxlvmg9ja', 'd8fmqxjlma', 'rjnmzjs673']
        },
        RandomInteractionLevel: {
            NONE: 0,
            LOW: 1,
            MEDIUM: 2,
            HIGH: 3
        },
        Integers: {
            HS_26_IN_MILLISECONDS: 93600000, //Using 26 hs instead of 24hs
            HS_2_IN_MILLISECONDS: 7200000 //and 2hs gap retry when code is flagged as USEDBEFORE
        },
        Use_Default: -1,
        WalletType: {
            FP_MAIL: 100,
            FP_BTC: 101,
            FP_BNB: 102,
            FP_BCH: 103,
            FP_DASH: 104,
            FP_DGB: 105,
            FP_DOGE: 106,
            FP_ETH: 107,
            FP_FEY: 108,
            FP_LTC: 109,
            FP_TRX: 110,
            FP_USDT: 111,
            FP_ZEC: 112,
            BTC: 1,
            LTC: 2
        },
        ErrorType: {
            ERROR: 0,
            TIMEOUT: 1,
            NEED_TO_LOGIN: 2,
            ROLL_ERROR: 3,
            CLICK_ROLL_ERROR: 4,
            LOGIN_ERROR: 5,
            CLAIM_ERROR: 6,
            ADDRESS_ERROR: 7,
            MIN_WITHDRAW_ERROR: 8,
            IP_BAN: 9,
            IP_RESTRICTED: 10,
            IP_ERROR: 11,
            FORCE_CLOSED: 12,
            NO_FUNDS: 13,
            VERIFY_EMAIL: 14,
            NO_ADDRESS: 15
        },
        CMC: {
            BTC: '1',
            BNB: '1839'
        }
    });

    let persistence, shared, manager, ui, CFPromotions, interactions, CFHistory, SiteProcessor;

    Element.prototype.isVisible = function() {
        return !!(this.offsetWidth||this.offsetHeight||this.getClientRects().length);
    };
    Element.prototype.isUserFriendly = function(selector) {
        let e = selector ? this.querySelector(selector) : this;
        return e && e.isVisible() ? e : null;
    };
    HTMLDocument.prototype.isUserFriendly = Element.prototype.isUserFriendly;

    String.prototype.clean = function() {
        let output = "";
        for (let i = 0; i < this.length; i++) {
            if (this.charCodeAt(i) <= 127) {
                output += this.charAt(i);
            }
        }
        return output;
    };
    Array.prototype.shuffle = function () {
        let currentIndex = this.length, temporaryValue, randomIndex;

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

        return this;
    };

    let helpers = {
        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) || '_ERR';
        },
        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);
        },
        addMs: function(date, ms) {
            return date.setMilliseconds(date.getMilliseconds() + ms);
        },
        getRandomMs: function(minute, rangeDiffInPercentage) { // Now will be a random value between minute and minute + rangeDiffPercentage%; Example if minute = 30 and rangeDiffPercentage = 5 => random in the range [30, 31.5]
            const msMin = minute * 60 * 1000;
            const msMax = msMin + rangeDiffInPercentage/100 * msMin;
            return helpers.randomMs(msMin, msMax);
        },
        hsToMs: function(hours) {
            return hours * 60 * 60 * 1000;
        },
        minToMs: function(min) {
            return min * 60 * 1000;
        },
        getEmojiForPromoStatus: function(promoStatus) {
            switch (promoStatus) {
                case K.CF.PromoStatus.NOCODE:
                    return '⚪';
                    break;
                case K.CF.PromoStatus.PENDING:
                    return '⏳';
                    break;
                case K.CF.PromoStatus.ACCEPTED:
                    return '✔️';
                    break;
                case K.CF.PromoStatus.USEDBEFORE:
                    return '🕙';
                    break;
                case K.CF.PromoStatus.INVALID:
                    return '❌';
                    break;
                case K.CF.PromoStatus.EXPIRED:
                    return '📅';
                    break;
                case K.CF.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 K.CF.UrlType.FREE;
                }
                if (url.includes('/promotion/')) {
                    return K.CF.UrlType.PROMOTION;
                }
                if (url.endsWith('/contact-twitter')) {
                    return K.CF.UrlType.CONTACTTWITTER;
                }
                if (url.endsWith('/free-rolls')) {
                    return K.CF.UrlType.FREEROLLS;
                }
                if (url.endsWith('/settings')) {
                    return K.CF.UrlType.SETTINGS;
                }
                if (url.endsWith('/stats')) {
                    return K.CF.UrlType.STATS;
                }
                if (url.endsWith('/')) {
                    url = url.slice(0, -1);
                    if (url == helpers.getHost(url, true)) {
                        return K.CF.UrlType.HOME;
                    }
                }

                return K.CF.UrlType.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;
            let config = {};
            function initializeConfig() {
                // Defaults:
                config['devlog.enabled'] = false;
                config['devlog.maxLines'] = 200;
                config['defaults.timeout'] = 4;
                config['defaults.postponeMinutes'] = 65; //0: Random between min and max
                config['defaults.postponeMinutes.min'] = 65;
                config['defaults.postponeMinutes.max'] = 65;
                config['defaults.workInBackground'] = true;
                config['defaults.extraInterval'] = true;
                config['cf.autologin'] = false;
                config['cf.credentials.mode'] = 1;
                config['cf.credentials.email'] = '[email protected]';
                config['cf.credentials.password'] = 'YOURPASSWORD';
                config['cf.sleepHoursIfIpBan'] = 8;
                config['fb.activateRPBonus'] = true;
                config['fp.hoursBetweenRuns'] = 6;
                config['fp.maxTimeInMinutes'] = 15;
                config['bk.withdrawMode'] = "0";
                config['bk.hoursBetweenWithdraws'] = 4;
                config['bk.sleepMinutesIfIpBan'] = 75;
                config['bestchange.address'] = '101';
                config['bigbtc.postponeMinutes'] = '0';

                let storedData = persistence.load('config', true);
                if(storedData) {
                    for (const prop in config) {
                        if(storedData.hasOwnProperty(prop)) {
                            config[prop] = storedData[prop];
                        }
                    }
                }

                config.version = GM_info.script.version;
            };
            function getConfig() {
                return config;
            };
            function updateConfig(items) {
                items.forEach( function (item) {
                    config[item.prop] = item.value;
                });
                persistence.save('config', config, true);
            }
            function devlog(msg, elapsed = false, reset = false) {
                if(!config['devlog.enabled']) {
                    return;
                }

                let log;
                if(reset) {
                    log = [`${helpers.getPrintableTime()}|Log cleared`];
                } else {
                    log = persistence.load('devlog', true);
                    log = log ?? [];
                }

                if(msg) {
                    let previous;
                    try {
                        previous = log[log.length - 1].split('|')[1];
                    } catch {}
                    if(elapsed && (previous == msg)) {
                        log[log.length - 1] = `${helpers.getPrintableTime()}|${msg}|[Elapsed time: ${elapsed} seconds]`;
                    } else {
                        log.push(`${helpers.getPrintableTime()}|${msg}`);
                    }
                }

                if(log.length > 200) {
                    log.splice(0, log.length - 200);
                }

                persistence.save('devlog', log, true);
            };
            function getDevLog() {
                let log;
                log = persistence.load('devlog', true);
                if(log) {
                    return log;
                }
            };
            function isOpenedByManager() {
                loadFlowControl();
                if(!flowControl) {
                    return false;
                }

                if(flowControl.host != window.location.host) {
                    return false;
                }

                if(flowControl.opened && flowControl.type != K.WebType.FAUCETPAY && flowControl.type != K.WebType.BAGIKERAN && flowControl.type != K.WebType.OURBITCOIN) {
                    //if(flowControl.opened && flowControl.type != K.WebType.FAUCETPAY && flowControl.type != K.WebType.BAGIKERAN) {
                    return false;
                }
                if(flowControl.type == K.WebType.BAGIKERAN && !window.location.href.includes(flowControl.params.trackUrl)) {
                    return false;
                }

                return true;
            };
            function setFlowControl(id, url, webType, params = null) {
                flowControl = {
                    id: id,
                    changedAt: Date.now(),
                    url: url,
                    host: url.host,
                    type: webType,
                    opened: false,
                    error: false,
                    result: {}
                };
                if(params) {
                    flowControl.params = params;
                }
                saveFlowControl();
            };
            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() {
                return flowControl;
            };
            function saveAndclose(runDetails, delay = 0) {
                markAsVisited(runDetails);
                shared.devlog(`${window.location.href} closing`);
                if(delay) {
                    setTimeout(window.close, delay);
                } else {
                    window.close();
                }
            };
            function loadFlowControl() {
                flowControl = persistence.load('flowControl', true);
            };
            function saveFlowControl() {
                persistence.save('flowControl', flowControl, true);
            };
            function markAsVisited(runDetails) {
                flowControl.opened = true;
                flowControl.result = runDetails;
                saveFlowControl();
            };
            function addError(errorType, errorMessage) {
                flowControl.error = true;

                flowControl.result.errorType = errorType;
                flowControl.result.errorMessage = errorMessage;

                saveFlowControl();
            };
            function closeWithError(errorType, errorMessage) {
                addError(errorType, errorMessage);
                shared.devlog(`${window.location.href} closing with error msg`);
                window.close();
            };
            function clearFlowControl() {
                flowControl = {};
                saveFlowControl();
            };
            function clearRetries() {
                loadFlowControl();
                flowControl.retrying = false;
                saveFlowControl();
                return false;
            };
            function isRetrying() {
                if(flowControl.retrying) {
                    return true;
                }
                flowControl.retrying = true;
                saveFlowControl();
                return false;
            };
            function setProp(key, val) {
                flowControl[key] = val;
                saveFlowControl();
            };
            function getProp(key) {
                return flowControl[key];
            };
            initializeConfig();
            return {
                devlog: devlog,
                getDevLog: getDevLog,
                setFlowControl: setFlowControl,
                wasVisited: wasVisited,
                isOpenedByManager: isOpenedByManager,
                saveFlowControl: saveFlowControl,
                getCurrent: getCurrent,
                getResult: getResult,
                addError: addError,
                closeWindow: saveAndclose,
                closeWithError: closeWithError,
                updateWithoutClosing: markAsVisited,
                hasErrors: hasErrors,
                clearFlowControl: clearFlowControl,
                getConfig: getConfig,
                updateConfig: updateConfig,
                clearRetries: clearRetries,
                isRetrying: isRetrying,
                setProp: setProp,
                getProp: getProp
            };
        },
        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 workingTab;

            let webList = [];
            let userWallet = [];

            const sites = [
                { id: '1', name: 'CF ADA', cmc: '2010', coinRef: 'ADA', url: new URL('https://freecardano.com/free'), rf: '?ref=335463', type: K.WebType.CRYPTOSFAUCETS },
                { id: '2', name: 'CF BNB', cmc: '1839', coinRef: 'BNB', url: new URL('https://freebinancecoin.com/free'), rf: '?ref=161127', type: K.WebType.CRYPTOSFAUCETS },
                { id: '3', name: 'CF BTC', cmc: '1', coinRef: 'BTC', url: new URL('https://freebitcoin.io/free'), rf: '?ref=490252', type: K.WebType.CRYPTOSFAUCETS },
                { id: '4', name: 'CF DASH', cmc: '131', coinRef: 'DASH', url: new URL('https://freedash.io/free'), rf: '?ref=124083', type: K.WebType.CRYPTOSFAUCETS },
                { id: '5', name: 'CF ETH', cmc: '1027', coinRef: 'ETH', url: new URL('https://freeethereum.com/free'), rf: '?ref=204076', type: K.WebType.CRYPTOSFAUCETS },
                { id: '6', name: 'CF LINK', cmc: '1975', coinRef: 'LINK', url: new URL('https://freechainlink.io/free'), rf: '?ref=78652', type: K.WebType.CRYPTOSFAUCETS },
                { id: '7', name: 'CF LTC', cmc: '2', coinRef: 'LTC', url: new URL('https://free-ltc.com/free'), rf: '?ref=117042', type: K.WebType.CRYPTOSFAUCETS },
                { id: '8', name: 'CF NEO', cmc: '1376', coinRef: 'NEO', url: new URL('https://freeneo.io/free'), rf: '?ref=100529', type: K.WebType.CRYPTOSFAUCETS },
                { id: '9', name: 'CF STEAM', cmc: '1230', coinRef: 'STEEM', url: new URL('https://freesteam.io/free'), rf: '?ref=117686', type: K.WebType.CRYPTOSFAUCETS },
                { id: '10', name: 'CF TRX', cmc: '1958', coinRef: 'TRX', url: new URL('https://free-tron.com/free'), rf: '?ref=145047', type: K.WebType.CRYPTOSFAUCETS },
                { id: '12', name: 'CF USDT', cmc: '825', coinRef: 'USDT', url: new URL('https://freetether.com/free'), rf: '?ref=181230', type: K.WebType.CRYPTOSFAUCETS },
                { id: '11', name: 'CF USDC', cmc: '3408', coinRef: 'USDC', url: new URL('https://freeusdcoin.com/free'), rf: '?ref=100434', type: K.WebType.CRYPTOSFAUCETS },
                { id: '13', name: 'CF XEM', cmc: '873', coinRef: 'XEM', url: new URL('https://freenem.com/free'), rf: '?ref=295274', type: K.WebType.CRYPTOSFAUCETS },
                { id: '14', name: 'CF XRP', cmc: '52', coinRef: 'XRP', url: new URL('https://coinfaucet.io/free'), rf: '?ref=808298', type: K.WebType.CRYPTOSFAUCETS },
                { id: '15', name: 'StormGain', cmc: '1', url: new URL('https://app.stormgain.com/crypto-miner/'), rf: 'friend/BNS27140552', type: K.WebType.STORMGAIN },
                { id: '16', name: 'CF DOGE', cmc: '74', coinRef: 'DOGE', url: new URL('https://free-doge.com/free'), rf: '?ref=97166', type: K.WebType.CRYPTOSFAUCETS },
                { id: '17', name: 'FreeBitco.in', cmc: '1', url: new URL('https://freebitco.in/'), rf: '?r=41092365', type: K.WebType.FREEBITCOIN },
                { id: '18', name: 'FaucetPay PTC', cmc: '1', url: new URL('https://faucetpay.io/ptc'), rf: '?r=41092365', type: K.WebType.FAUCETPAY },
                { id: '19', name: 'Free-Litecoin.com', cmc: '2', url: new URL('https://free-litecoin.com/'), rf: 'login?referer=1332950', type: K.WebType.FREELITECOIN },
                { id: '20', name: 'Free-Ethereum.io', cmc: '1027', url: new URL('https://www.free-ethereum.io/'), rf: '?referer=1064662', type: K.WebType.FREEETHEREUMIO },
                { id: '21', name: 'Bagi BTC', cmc: '1', wallet: K.WalletType.FP_BTC, url: new URL('https://bagi.co.in/bitcoin/'), rf: ['?ref=53706', '?ref=63428', '?ref=54350'], type: K.WebType.BAGIKERAN },
                { id: '22', name: 'Bagi BNB', cmc: '1839', wallet: K.WalletType.FP_BNB, url: new URL('https://bagi.co.in/binance/'), rf: ['?ref=12529', '?ref=23852', '?ref=13847'], type: K.WebType.BAGIKERAN },
                { id: '23', name: 'Bagi BCH', cmc: '1831', wallet: K.WalletType.FP_BCH, url: new URL('https://bagi.co.in/bitcoincash/'), rf: ['?ref=44242', '?ref=50185', '?ref=41957'], type: K.WebType.BAGIKERAN },
                { id: '24', name: 'Bagi DASH', cmc: '131', wallet: K.WalletType.FP_DASH, url: new URL('https://bagi.co.in/dash/'), rf: ['?ref=32724', '?ref=38540', '?ref=40441'], type: K.WebType.BAGIKERAN },
                { id: '25', name: 'Bagi DGB', cmc: '109', wallet: K.WalletType.FP_DGB, url: new URL('https://bagi.co.in/digibyte/'), rf: ['?ref=22664', '?ref=27872', '?ref=29669'], type: K.WebType.BAGIKERAN },
                { id: '26', name: 'Bagi DOGE', cmc: '74', wallet: K.WalletType.FP_DOGE, url: new URL('https://bagi.co.in/dogecoin/'), rf: ['?ref=45047', '?ref=54217', '?ref=45568'], type: K.WebType.BAGIKERAN },
                { id: '27', name: 'Bagi ETH', cmc: '1027', wallet: K.WalletType.FP_ETH, url: new URL('https://bagi.co.in/ethereum/'), rf: ['?ref=24486', '?ref=27799', '?ref=24847'], type: K.WebType.BAGIKERAN },
                { id: '28', name: 'Bagi FEY', cmc: '10361', wallet: K.WalletType.FP_FEY, url: new URL('https://bagi.co.in/feyorra/'), rf: ['?ref=5049', '?ref=7433', '?ref=5318'], type: K.WebType.BAGIKERAN },
                { id: '29', name: 'Bagi LTC', cmc: '2', wallet: K.WalletType.FP_LTC, url: new URL('https://bagi.co.in/litecoin/'), rf: ['?ref=48335', '?ref=57196', '?ref=48878'], type: K.WebType.BAGIKERAN },
                { id: '30', name: 'Bagi TRX', cmc: '1958', wallet: K.WalletType.FP_TRX, url: new URL('https://bagi.co.in/tron/'), rf: ['?ref=22622', '?ref=31272', '?ref=23075'], type: K.WebType.BAGIKERAN },
                { id: '31', name: 'Bagi USDT', cmc: '825', wallet: K.WalletType.FP_USDT, url: new URL('https://bagi.co.in/tether/'), rf: ['?ref=25462', '?ref=32491', '?ref=25981'], type: K.WebType.BAGIKERAN },
                { id: '32', name: 'Bagi ZEC', cmc: '1437', wallet: K.WalletType.FP_ZEC, url: new URL('https://bagi.co.in/zcash/'), rf: ['?ref=9181', '?ref=15120', '?ref=9878'], type: K.WebType.BAGIKERAN },
                { id: '33', name: 'Keran BTC', cmc: '1', wallet: K.WalletType.FP_BTC, url: new URL('https://keran.co/BTC/'), rf: ['?ref=73729', '?ref=92353', '?ref=79321'], type: K.WebType.BAGIKERAN },
                { id: '34', name: 'Keran BNB', cmc: '1839', wallet: K.WalletType.FP_BNB, url: new URL('https://keran.co/BNB/'), rf: ['?ref=19287', '?ref=31242', '?ref=20659'], type: K.WebType.BAGIKERAN },
                { id: '35', name: 'Keran BCH', cmc: '1831', wallet: K.WalletType.FP_BCH, url: new URL('https://keran.co/BCH/'), rf: ['?ref=58232', '?ref=67326', '?ref=70759'], type: K.WebType.BAGIKERAN },
                { id: '36', name: 'Keran DASH', cmc: '131', wallet: K.WalletType.FP_DASH, url: new URL('https://keran.co/DASH/'), rf: ['?ref=45229', '?ref=53041', '?ref=55716'], type: K.WebType.BAGIKERAN },
                { id: '37', name: 'Keran DGB', cmc: '109', wallet: K.WalletType.FP_DGB, url: new URL('https://keran.co/DGB/'), rf: ['?ref=32788', '?ref=39527', '?ref=42014'], type: K.WebType.BAGIKERAN },
                { id: '38', name: 'Keran DOGE', cmc: '74', wallet: K.WalletType.FP_DOGE, url: new URL('https://keran.co/DOGE/'), rf: ['?ref=73512', '?ref=85779', '?ref=89613'], type: K.WebType.BAGIKERAN },
                { id: '39', name: 'Keran ETH', cmc: '1027', wallet: K.WalletType.FP_ETH, url: new URL('https://keran.co/ETH/'), rf: ['?ref=32226', '?ref=36427', '?ref=32676'], type: K.WebType.BAGIKERAN },
                { id: '40', name: 'Keran FEY', cmc: '10361', wallet: K.WalletType.FP_FEY, url: new URL('https://keran.co/FEY/'), rf: ['?ref=6269', '?ref=9019', '?ref=6569'], type: K.WebType.BAGIKERAN },
                { id: '41', name: 'Keran LTC', cmc: '2', wallet: K.WalletType.FP_LTC, url: new URL('https://keran.co/LTC/'), rf: ['?ref=69102', '?ref=80726', '?ref=84722'], type: K.WebType.BAGIKERAN },
                { id: '42', name: 'Keran TRX', cmc: '1958', wallet: K.WalletType.FP_TRX, url: new URL('https://keran.co/TRX/'), rf: ['?ref=49686', '?ref=46544', '?ref=34485'], type: K.WebType.BAGIKERAN },
                { id: '43', name: 'Keran USDT', cmc: '825', wallet: K.WalletType.FP_USDT, url: new URL('https://keran.co/USDT/'), rf: ['?ref=40582', '?ref=48907', '?ref=41009'], type: K.WebType.BAGIKERAN },
                { id: '44', name: 'Keran ZEC', cmc: '1437', wallet: K.WalletType.FP_ZEC, url: new URL('https://keran.co/ZEC/'), rf: ['?ref=', '?ref=18976', '?ref=12487'], type: K.WebType.BAGIKERAN },
                { id: '45', name: 'OK Btc', cmc: '1', wallet: K.WalletType.FP_BTC, url: new URL('https://btc-ok.net/'), rf: 'index.php?r=1QCD6cWJNVH4Cdnz85SQ2qtTkAwGr9fvUk', type: K.WebType.OKFAUCET },
                { id: '46', name: 'OK Dash', cmc: '131', wallet: K.WalletType.FP_DASH, url: new URL('https://dash-ok.net/'), rf: 'index.php?r=Xbyi7Fk2NRmZ32SHpDhmpGHLa4NMokhmGR', type: K.WebType.OKFAUCET },
                { id: '47', name: 'OK Dgb', cmc: '109', wallet: K.WalletType.FP_DGB, url: new URL('https://dgb-ok.net/'), rf: 'index.php?r=DSM93hgZuapnjeeDMe8spzwG9rMrw4sdua', type: K.WebType.OKFAUCET },
                { id: '48', name: 'OK Doge', cmc: '74', wallet: K.WalletType.FP_DOGE, url: new URL('https://doge-ok.net/'), rf: 'index.php?r=DDaQWmD7vY1NhtK1M5Pno7sdccmgxNUfv1', type: K.WebType.OKFAUCET },
                { id: '49', name: 'OK Eth', cmc: '1027', wallet: K.WalletType.FP_ETH, url: new URL('https://eth-ok.net/'), rf: 'index.php?r=0x7636f64a8241257b1edaf65ae943c66de87b1749', type: K.WebType.OKFAUCET },
                { id: '50', name: 'OK Ltc', cmc: '2', wallet: K.WalletType.FP_LTC, url: new URL('https://ltc-ok.net/'), rf: 'index.php?r=MEmxLqYzZdMsEswUQkqL5aawT5UsqYwYgr', type: K.WebType.OKFAUCET },
                { id: '51', name: 'OK Trx', cmc: '1958', wallet: K.WalletType.FP_TRX, url: new URL('https://trx-ok.net/'), rf: 'index.php?r=TSocuzJ6ADUoQ49v28BXN2jo3By6awwHvj', type: K.WebType.OKFAUCET },
                { id: '52', name: 'BigBtc', cmc: '1', wallet: K.WalletType.FP_BTC, url: new URL('https://bigbtc.win/'), rf: '?id=39255652', type: K.WebType.BIGBTC },
                { id: '53', name: 'BestChange', cmc: '1', wallet: K.WalletType.FP_BTC, url: new URL('https://www.bestchange.com/'), rf: ['index.php?nt=bonus&p=1QCD6cWJNVH4Cdnz85SQ2qtTkAwGr9fvUk', 'index.php?nt=bonus&p=365Z9xPiQA4iWg23kcxRAdrGfV9RGPhS5J'], type: K.WebType.BESTCHANGE },
                { id: '54', name: 'Litking.biz', cmc: '2', url: new URL('https://litking.biz/'), rf: 'signup?r=159189', type: K.WebType.KINGBIZ },
                { id: '55', name: 'Bitking.biz', cmc: '1', url: new URL('https://bitking.biz/'), rf: 'signup?r=90003', type: K.WebType.KINGBIZ },
                //                { id: '56', name: 'OK Bch', cmc: '1831', wallet: K.WalletType.FP_BCH, url: new URL('https://faucetok.net/bch/'), rf: '?r=qz742nf2c30ktehlmn0pg6quqe8yuwp3evd75y8c0k', type: K.WebType.OKFAUCET }
                { id: '57', name: 'OurBitco.in', cmc: '1', url: new URL('https://ourbitco.in/dashboard'), rf: '?r=gebcjvwpky', type: K.WebType.OURBITCOIN },
                { id: '58', name: 'BetFury BTC', cmc: '1', url: new URL('https://betfury.io/boxes/all'), rf: ['?r=608c5cfcd91e762043540fd9'], type: K.WebType.BETFURYBOX },
                { id: '59', name: 'BetFury BNB', cmc: '1839', url: new URL('https://betfury.io/boxes/all'), rf: ['?r=608c5cfcd91e762043540fd9'], type: K.WebType.BETFURYBOX },
                { id: '60', name: 'Free-Doge.io', cmc: '74', url: new URL('https://www.free-doge.io/'), rf: '?referer=6695', type: K.WebType.FREEDOGEIO },
                { id: '61', name: 'Dutchy', cmc: '-1', url: new URL('https://autofaucet.dutchycorp.space/roll.php'), rf: '?r=corecrafting', type: K.WebType.DUTCHYROLL },
                { id: '62', name: 'Dutchy Monthly Coin', cmc: '-1', url: new URL('https://autofaucet.dutchycorp.space/coin_roll.php'), rf: '?r=corecrafting', type: K.WebType.DUTCHYROLL }
            ];

            const wallet = [
                { id: '101', name: 'FaucetPay BTC (Bitcoin)', type: K.WalletType.FP_BTC },
                { id: '102', name: 'FaucetPay BNB (Binance Coin)', type: K.WalletType.FP_BNB },
                { id: '103', name: 'FaucetPay BCH (Bitcoin Cash)', type: K.WalletType.FP_BCH },
                { id: '104', name: 'FaucetPay DASH (Dash)', type: K.WalletType.FP_DASH },
                { id: '105', name: 'FaucetPay DGB (DigiByte)', type: K.WalletType.FP_DGB },
                { id: '106', name: 'FaucetPay DOGE (Dogecoin)', type: K.WalletType.FP_DOGE },
                { id: '107', name: 'FaucetPay ETH (Ethereum)', type: K.WalletType.FP_ETH },
                { id: '108', name: 'FaucetPay FEY (Feyorra)', type: K.WalletType.FP_FEY },
                { id: '109', name: 'FaucetPay LTC (Litecoin)', type: K.WalletType.FP_LTC },
                { id: '110', name: 'FaucetPay TRX (Tron)', type: K.WalletType.FP_TRX },
                { id: '111', name: 'FaucetPay USDT (Tether TRC20)', type: K.WalletType.FP_USDT },
                { id: '112', name: 'FaucetPay ZEC (Zcash)', type: K.WalletType.FP_ZEC },
                { id: '1', name: 'BTC Alternative Address', type: K.WalletType.BTC }
                //                { id: '2', name: 'LTC Address', type: K.WalletType.LTC }
            ];

            function start() {
                loader.initialize();
                ui.init(getCFlist());
                update();
                ui.refresh(null, null, userWallet);
                uiUpdatesInterval = setInterval(readUpdateValues, 10000);
                processTimer = setTimeout(manager.process, 2000);
            };

            let loader = function() {
                function initialize() {
                    setTimestamp();
                    initializeWebList();
                    initializeUserWallet();
                    initializePromotions();
                    initializeHistory();
                };
                function initializeWebList() {
                    addSites();
                    addStoredSitesData();
                };
                function addSites() {
                    sites.forEach(x => webList.push(x));
                    // Set some defaults
                    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;
                        //arr[idx].workInBackground = (element.id == '18' ? false : K.USE_DEFAULT); // deprecated

                        // TODO: review
                        if(arr[idx].type == K.WebType.BAGIKERAN) {
                            arr[idx].lastWithdraw = new Date();
                        }

                        if (element.id == '18') { //18: FPPTC
                            arr[idx].params = {};
                            arr[idx].params['defaults.workInBackground.override'] = true;
                            arr[idx].params['defaults.workInBackground'] = false;
                        }

                        //TODO: defaults, schedule, logics, queues
                        //arr[idx].params = { logics: { nextRun: K.USE_DEFAULT, balance: K.USE_DEFAULT }, sleepSchedule: K.USE_DEFAULT, queue: K.USE_DEFAULT };
                        // if(arr[idx].type == K.WebType.BETFURYBOX) {
                        //     arr[idx].params.logics.nextRun = '30 minutes +/- 10%';
                        // }
                        // if(arr[idx].type == K.WebType.OURBITCOIN) {
                        //     arr[idx].params.logics.nextRun = '32 minutes +/- 7% but RETRY NOW IF FORCE_CLOSED OR SOMETHING LIKE THAT';
                        // }
                    });
                };
                function addStoredSitesData() {
                    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();
                                }
                                if(element.lastWithdraw) {
                                    webList[idx].lastWithdraw = new Date(element.lastWithdraw);
                                }
                                //webList[idx].workInBackground = element.workInBackground ?? webList[idx].workInBackground;
                                webList[idx].params = element.params ?? webList[idx].params;
                            }
                        });
                    } else {
                        //                        webList.shuffle();
                    }
                };
                function initializeUserWallet() {
                    addWallets();
                    addStoredWalletData();
                };
                function addWallets() {
                    wallet.forEach(x => userWallet.push(x));
                    userWallet.forEach(function (element, idx, arr) {
                        arr[idx].address = '';
                    });
                };
                function addStoredWalletData() {
                    let storedData = persistence.load('userWallet', true);
                    if(storedData) {
                        storedData.forEach( function (element) {
                            let idx = userWallet.findIndex(x => x.id == element.id);
                            if(idx != -1) {
                                userWallet[idx].address = element.address ?? webList[idx].address;
                            }
                        });
                    }
                };
                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 = Date.now();
                    persistence.save('timestamp', timestamp);
                };
                function removeDisabledFaucets() {
                    webList = webList.filter(x => x.enabled);
                };
                return {
                    initialize: initialize
                };
            }();
            function readUpdateValues(forceCheck = false) {
                readPromoCodeValues();
                readModalData();

                if(status == STATUS.IDLE || forceCheck) {
                    let updateDataElement = document.getElementById('update-data');
                    let updateValues = updateDataElement.innerText.clean();

                    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.wallet.changed) {
                            updateObj.wallet.items.forEach(function (element) {
                                try {
                                    let itemIndex = userWallet.findIndex(x => x.id == element.id);
                                    userWallet[itemIndex].address = element.address;

                                    ui.log(`Wallet Address updated [${userWallet[itemIndex].name}]: ${userWallet[itemIndex].address}`);
                                } catch (err) {
                                    ui.log(`Error updating wallet/address: ${err}`);
                                }
                            });

                            document.getElementById('faucets-display-status').innerHTML = '';
                            ui.refresh(null, null, userWallet);
                            saveUserWallet();
                        }

                        if(updateObj.config.changed) {
                            try {
                                shared.updateConfig(updateObj.config.items);
                                ui.log(`Config updated. Reloading in a few seconds...`);
                                window.location.reload();
                                return;
                            } catch (err) {
                                ui.log(`Error updating config: ${err}`);
                            }

                            document.getElementById('faucets-display-status').innerHTML = '';
                        }

                        if(updateObj.site.changed) {
                            // console.log(JSON.stringify(updateObj));
                            try {
                                updateSite(updateObj.site.id, updateObj.site.items);
                            } catch (err) {
                                ui.log(`Error updating site: ${err}`);
                            }

                            document.getElementById('faucets-display-status').innerHTML = '';
                        }

                        if(updateObj.runAsap.changed || updateObj.editSingle.changed || updateObj.site.changed) {
                            document.getElementById('faucets-display-status').innerHTML = '';
                            update(true);
                            process();
                            return;
                        }
                    }
                }
                if(forceCheck) {
                    process();
                }
            };
            function updateSite(id, items) {
                let elm = webList.find(x => x.id == id);
                if (elm) {
                    elm.params = elm.params || {};
                    items.forEach( function (item) {
                        elm.params[item.prop] = item.value;
                    });
                    shared.devlog(`Saving: ${JSON.stringify(elm)}`);
                    ui.log(`Site ${elm.name} updated`);
                }
            }
            function readModalData() {
                if(document.getElementById('modal-spinner').isVisible()) {
                    let targetObject = JSON.parse(document.getElementById('target-spinner').innerHTML);
                    let target = targetObject.id;
                    if (target == 'modal-ereport') {
                        let temp = shared.getDevLog();
                        document.getElementById('log-textarea').value = temp.join('\n');
                    } else if (target == 'modal-config') {
                        ui.refresh(null, null, null, shared.getConfig());
                    } else if (target == 'modal-site') {
                        let site = webList.find(x => x.id == targetObject.siteId);
                        ui.refresh(null, null, null, null, { site: site, config: shared.getConfig() });
                    }
                    document.getElementById('modal-spinner').classList.toggle('d-none');
                    document.getElementById(target).classList.toggle('d-none'); //$('#' + target).toggleClass('d-none');
                    document.getElementById('target-spinner').innerHTML = '';
                }
            }
            function update(sortIt = true) {
                let updateRollStats = webList[0].type == K.WebType.CRYPTOSFAUCETS;
                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());
                if(updateRollStats) {
                    updateRollStatsSpan();
                }
            };
            function saveWebList() {
                const data = webList.map(function(x) {
                    let ret = {
                        id: x.id,
                        name: x.name,
                        lastClaim: x.lastClaim,
                        aggregate: x.aggregate,
                        balance: x.balance,
                        stats: x.stats,
                        nextRoll: x.nextRoll,
                        enabled: x.enabled,
                        params: x.params
                    };

                    if (x.lastWithdraw) {
                        ret.lastWithdraw = x.lastWithdraw;
                    }

                    return ret;
                });

                persistence.save('webList', data, true);
            }
            function saveUserWallet() {
                const data = userWallet.map(x => {
                    return {
                        id: x.id,
                        address: x.address
                    };});

                persistence.save('userWallet', data, true);
            }
            function process() {
                if(isObsolete()) {
                    return;
                }
                timer.stopCheck();
                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() < Date.now()) {
                    ui.log(`Opening ${webList[0].name}`);
                    clearTimeout(processTimer);
                    status = STATUS.CLAIMING;
                    open();
                } else {
                    let timeUntilNext = webList[0].nextRoll.getTime() - Date.now() + helpers.randomMs(1000, 2000);

                    // PROCESSING AGAIN LAST 'FORCE CLOSED' IN CASE WE HAVE A WINDOW OF TIME (MORE THAN TIMEOUT/2):
                    if (timeUntilNext > (shared.getConfig()['defaults.timeout'] * 60 * 1000 / 2)) {
                        let idx = -1;
                        for (let i = webList.length - 1; i >= 0; i--) {
                            if (webList[i].enabled && webList[i].stats && webList[i].stats.errors && webList[i].stats.errors.errorType == K.ErrorType.FORCE_CLOSED) {
                                idx = i;
                                break;
                            }
                        }
                        if (idx > -1) {
                            webList[idx].nextRoll = new Date(-20);
                            update(true);
                            process();
                            return;
                        }
                    }

                    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;
                try {
                    if(promoCode) {
                        navUrl = new URL('promotion/' + promoCode, webList[0].url.origin);
                        ui.log(`Opening ${webList[0].name} with Promo Code [${promoCode}]`);
                    }

                    if(Array.isArray(webList[0].rf)) {
                        navUrl = new URL(navUrl.href + webList[0].rf[helpers.randomInt(0, webList[0].rf.length - 1)]);
                    }

                    let params = webList[0].params || {};
                    if (webList[0].wallet) {
                        //TODO: VALIDATE THAT ADDRESS EXISTS AND IS VALID!!!
                        try {
                            params.address = userWallet.find(x => x.type == webList[0].wallet).address;
                        } catch {
                            shared.addError(K.ErrorType.NO_ADDRESS, 'You need to add your address to the wallet before claiming this faucet.');
                            ui.log(`Unable to launch ${webType[0].name}: Address not detected > add it to the wallet.`);
                            moveNext();
                            return;
                        }
                    }
                    if(webList[0].type == K.WebType.BESTCHANGE) {
                        params.address = shared.getConfig()['bestchange.address'] == '1' ? userWallet.find(x => x.type == 1).address : params.address;
                    }
                    if(webList[0].type == K.WebType.BAGIKERAN) {
                        params.doWithdraw = getDoWithdraw(webList[0].lastWithdraw);
                        params.doSignOut = (webList[0].wallet == K.WalletType.FP_BCH ? true : false);
                        params.trackUrl = webList[0].url;
                    }
                    params.cmc = webList[0].cmc;

                    shared.setFlowControl(webList[0].id, navUrl, webList[0].type, params);
                    setTimeout(manager.resultReader, 15000);

                    // Try to close old workingTab if still opened
                    if (workingTab && !workingTab.closed) {
                        try {
                            shared.devlog(`Tab closed from Manager`);
                            workingTab.close();
                        } catch {
                            shared.devlog(`ERROR: unable to close tab from Manager`);
                        }
                    } else {
                        shared.devlog(`No open tabs detected`);
                    }

                    timer.startCheck(webList[0].type);
                    //workingTab = GM_openInTab(navUrl.href, { active: (webList[0].workInBackground == K.USE_DEFAULT ? !shared.getConfig()['defaults.workInBackground'] : !webList[0].workInBackground) });
                    workingTab = GM_openInTab(navUrl.href, { active: !getCustomOrDefaultVal('defaults.workInBackground', useOverride('defaults.workInBackground')) });
                } catch(err) {
                    ui.log(`Error opening tab: ${err}`)
                }
            };

            function getDoWithdraw(lastWithdraw) {
                switch (shared.getConfig()['bk.withdrawMode']) {
                    case "0":
                        return false;
                        break;
                    case "2":
                        return true;
                        break;
                    case "1":
                        if(lastWithdraw == null) {
                            return true;
                        }
                        return (lastWithdraw && ( (Date.now() - lastWithdraw.getTime()) > helpers.hsToMs(shared.getConfig()['bk.hoursBetweenWithdraws'])));
                        break;
                    default:
                        return false;
                }
                return false;
            }

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

                //TODO: review workingTab.closed as it's redirected @ OurBitco.in
                if ((webList[0].type == K.WebType.FAUCETPAY || webList[0].type == K.WebType.OURBITCOIN) && workingTab && !workingTab.closed
                    //if (webList[0].type == K.WebType.FAUCETPAY && workingTab && !workingTab.closed
                    && timeWaiting < shared.getConfig()['fp.maxTimeInMinutes'] * 60) {
                    timeWaiting += 15;
                    ui.log(`Waiting for ${webList[0].name} results...`, timeWaiting);
                    setTimeout(manager.resultReader, 15000);
                    return;
                }

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

                    if (result) {
                        updateWebListItem(result);

                        if ( (webList[0].type == K.WebType.CRYPTOSFAUCETS) &&
                            ( (result.claimed) || (result.promoStatus && result.promoStatus != K.CF.PromoStatus.ACCEPTED) )) {
                            let promoCode = CFPromotions.hasPromoAvailable(webList[0].id);
                            if (promoCode) {
                                timeWaiting = 0;
                                update(false);
                                open(promoCode);
                                return;
                            }
                        }

                        if ( webList[0].type == K.WebType.BAGIKERAN && shared.getCurrent().params.doWithdraw && !result.withdrawnAmount) {
                            if(!result.withdrawing) {
                                shared.updateWithoutClosing({ withdrawing: true });
                                update(false);
                                timeWaiting = 0;
                            }

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

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

                                if(sleepIfBan()) {
                                    return;
                                }

                                moveNext();
                                return;
                            }

                            timeWaiting += 15;
                            ui.log(`Waiting for ${webList[0].name} withdraw...`, timeWaiting);
                            setTimeout(manager.resultReader, 15000);
                            return;
                        }
                    } else {
                        ui.log(`Unable to read last run result, for ID: ${webList[0].id} > ${webList[0].name}`);
                    }

                    timeWaiting = 0;
                    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: ${helpers.getEnumText(K.ErrorType,webList[0].stats.errors.errorType)} ${webList[0].stats.errors.errorMessage}`);

                        if(sleepIfBan()) {
                            return;
                        }

                    }

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

                    moveNext();
                    return;
                }
            };

            function sleepIfBan() {
                if( (webList[0].stats.errors.errorType == K.ErrorType.IP_BAN && shared.getConfig()['cf.sleepHoursIfIpBan'] > 0)
                   || ( (webList[0].stats.errors.errorType == K.ErrorType.IP_RESTRICTED || webList[0].stats.errors.errorType == K.ErrorType.IP_BAN) && shared.getConfig()['bk.sleepMinutesIfIpBan'] > 0) ) {
                    if(webList[0].type == K.WebType.CRYPTOSFAUCETS) {
                        webList.filter(x => x.enabled && x.type == K.WebType.CRYPTOSFAUCETS)
                            .forEach( function(el) {
                            el.nextRoll = new Date(helpers.addMs(new Date(), helpers.getRandomMs(shared.getConfig()['cf.sleepHoursIfIpBan'] * 60, 2)));
                        });
                    }

                    if(webList[0].type == K.WebType.BAGIKERAN) {
                        webList.filter(x => x.enabled && x.type == K.WebType.BAGIKERAN && x.url.host == webList[0].url.host)
                            .forEach( function(el) {
                            el.nextRoll = new Date(helpers.addMs(new Date(), helpers.getRandomMs(shared.getConfig()['bk.sleepMinutesIfIpBan'], 2)));
                        });
                    }

                    shared.clearFlowControl();
                    update(true);
                    timeWaiting = 0;
                    readUpdateValues(true);
                    return true;
                }
                return false;
            }

            function getCustomOrDefaultVal(param, useOverride = false) {
                let val;

                if (useOverride) {
                    if (webList[0].params && webList[0].params.hasOwnProperty(param)) {
                        val = webList[0].params[param];
                        if (val != -1) {
                            return val;
                        }
                    }
                }

                return shared.getConfig()[param];
            }

            function useOverride(param) {
                let overrideFlag = param  + '.override';

                return webList[0].params && webList[0].params[overrideFlag];
            }

            function moveNext() {
                let userOverride = useOverride('defaults.postponeMinutes');

                let mode = getCustomOrDefaultVal('defaults.postponeMinutes', useOverride);
                let min = getCustomOrDefaultVal('defaults.postponeMinutes.min', useOverride);
                let max = getCustomOrDefaultVal('defaults.postponeMinutes.max', useOverride);

                let minutes = (mode == 0) ? helpers.randomInt(min, max) : mode;
                let msDelay = helpers.getRandomMs(minutes, 5);

                webList[0].nextRoll = new Date(helpers.addMs(new Date(), msDelay));
                shared.clearFlowControl();
                update(true);
                timeWaiting = 0;
                readUpdateValues(true);
            }

            function hasTimedOut() {
                let val = getCustomOrDefaultVal('defaults.timeout', useOverride('defaults.timeout')) * 60;
                return (timeWaiting > val);
                // return shared.getConfig()['defaults.timeout'] != -1 && (timeWaiting > (shared.getConfig()['defaults.timeout'] * 60));
            };

            function updateWebListItem(result) {
                if (result.withdrawing) {
                    return;
                }

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

                if (result.withdrawnAmount && result.withdrawnAmount > 0) {
                    webList[0].lastWithdraw = new Date();
                    webList[0].balance += result.withdrawnAmount;
                    webList[0].lastClaim = 0;
                    webList[0].aggregate = 0;
                    return;
                }

                if (result.claimed) {
                    try {
                        result.claimed = parseFloat(result.claimed);
                    } catch { }
                    if(!isNaN(result.claimed)) {
                        webList[0].lastClaim = result.claimed;
                        webList[0].aggregate += result.claimed;
                        //                        if (!result.balance) {
                        //                            webList[0].balance = webList[0].aggregate;
                        //                        }
                    }
                }
                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 = document.getElementById('promo-code-new');
                let promoDataStr = promoCodeElement.innerText.clean();
                let promoDisplayStatus = document.getElementById('promo-display-status');
                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 = '';
                            document.getElementById('promo-text-input').value = '';
                            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 = document.getElementById('rolls-span');
                rollsSpanElement.innerText = CFHistory.getRollsMeta().join(',');
            };

            function getCFlist() {
                let items;
                items = webList.filter(f => f.type === K.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;
            };
            function closeWorkingTab() {
                workingTab.close();
            };
            return{
                init:start,
                process: process,
                resultReader: resultReader,
                getFaucetsForPromotion: getCFlist,
                readPromoCodeValues: readPromoCodeValues,
                closeWorkingTab: closeWorkingTab
            };
        },
        createUi: function() {

            let injectables = {
                managerJs: function () {

                    window.myBarChart = null;

                    window.sendErrorReport = function sendErrorReport() {
                        try {
                            let header = new Headers();
                            header.append("Content-Type", "application/json");
                            let description = document.getElementById("log-message").value;
                            let log = document.getElementById("log-textarea").value.split('\n');
                            let content = {"description":description, "log":log};
                            let opt = { method: "POST", header, mode: "cors", body: JSON.stringify(content) };
                            fetch("https://1d0103ec5a621b87ea27ffed3c072796.m.pipedream.net", opt).then(response => {
                                console.log(response);
                            }).catch(err => {
                                console.error("[error] " + err.message);
                            });
                        } catch { }
                    };

                    window.loadDlg = function loadDlg(id, siteId = null) {
                        document.querySelectorAll(".modal-content").forEach(x => x.classList.add('d-none'));
                        //                $(".modal-content").addClass("d-none");
                        if (id == "modal-ereport" || id == "modal-config" || id == "modal-site") {
                            document.getElementById("target-spinner").innerHTML = JSON.stringify({id: id, siteId: siteId});
                            document.getElementById("modal-spinner").classList.remove("d-none");
                            return;
                        } else {
                            document.getElementById(id).classList.remove("d-none");
                        }
                    };

                    window.savePromoCode = function savePromoCode() {
                        var promoText = document.getElementById("promo-text-input");
                        var promoCode = document.getElementById("promo-code-new");
                        var promoDisplayStatus = document.getElementById("promo-display-status");
                        var promoDaily = document.getElementById("promo-daily");
                        var promoObject = { action: "ADD", code: promoText.value.trim(), repeatDaily: promoDaily.checked };
                        promoCode.innerHTML =JSON.stringify(promoObject);
                        promoDisplayStatus.innerHTML = "Adding &nbsp&quot;<b>" + promoObject.code + "</b>&quot;...<br>This could take around a minute. Please wait..."
                    };

                    window.removePromoCode = function removePromoCode(id, code) {
                        var promoCode = document.getElementById("promo-code-new");
                        var promoDisplayStatus = document.getElementById("promo-display-status");
                        var promoObject = { action: "REMOVE", id: id, code: code };
                        promoCode.innerHTML =JSON.stringify(promoObject);
                        promoDisplayStatus.innerHTML = "Removing code &nbsp&quot;<b>" + code + "</b>&quot;...<br>This could take around a minute. Please wait..."
                    };

                    window.getUpdateObject = function getUpdateObject() {
                        let updateObject;
                        var updateData = document.getElementById("update-data");
                        if (updateData.innerHTML != "") {
                            updateObject = JSON.parse(updateData.innerHTML);
                        } else {
                            updateObject = { runAsap: { ids: [], changed: false}, editSingle: { changed: false, items: [] }, wallet: { changed: false, items: []}, config: { changed: false, items: []}, site: { changed: false, id: null, items: []} };
                        }
                        return updateObject;
                    };

                    window.editList = function editList() {
                        document.querySelectorAll("#schedule-table-body td.em-input").forEach(function (x) {
                            let val = x.innerHTML;
                            x.innerHTML = "<input type=\'text\' data-original=\'" + val + "\' value=\'" + val + "\' />";
                        });
                        document.querySelectorAll("#schedule-table-body td.edit-status").forEach(function (x) {
                            let activeSwitch = x.querySelector("input");
                            x.classList.remove("d-none");
                        });
                        document.querySelectorAll(".em-only").forEach(x => x.classList.remove("d-none"));
                        document.querySelectorAll(".em-hide").forEach(x => x.classList.add("d-none"));
                    };

                    window.editListSave = function editListSave() {
                        let updateObject = getUpdateObject();
                        document.querySelectorAll("#schedule-table-body tr").forEach(function (row) {
                            let textInputCell = row.querySelector(".em-input");
                            let textInput = textInputCell.querySelector("input");
                            let activeSwitch = row.querySelector("td.edit-status input");
                            let single = { id: row.dataset.id, displayName: textInput.dataset.original, enabled: activeSwitch.dataset.original };
                            textInputCell.innerHTML = textInput.value;
                            if(textInput.dataset.original != textInput.value) {
                                single.displayName = textInput.value;
                            }
                            if(activeSwitch.dataset.original != Boolean(activeSwitch.checked)) {
                                single.enabled = Boolean(activeSwitch.checked);
                            }
                            if(textInput.dataset.original != textInput.value || activeSwitch.dataset.original != Boolean(activeSwitch.checked)) {
                                updateObject.editSingle.items.push(single);
                                updateObject.editSingle.changed = true;
                            }
                        });
                        if(updateObject.editSingle.changed) {
                            document.getElementById("update-data").innerHTML = JSON.stringify(updateObject);
                            document.getElementById("faucets-display-status").innerHTML = "Data will be updated as soon as possible...";
                        }

                        document.querySelectorAll(".em-only").forEach(x => x.classList.add("d-none"));
                        document.querySelectorAll(".em-hide").forEach(x => x.classList.remove("d-none"));
                    };

                    window.editListCancel = function editListCancel() {
                        document.querySelectorAll("#schedule-table-body td.em-input input").forEach(function(x) {
                            x.parentNode.innerHTML = x.dataset.original;
                        });
                        document.querySelectorAll(".em-only").forEach(x => x.classList.add("d-none"));
                        document.querySelectorAll(".em-hide").forEach(x => x.classList.remove("d-none"));
                    };

                    window.editWallet = {
                        save: function() {
                            let updateObject = getUpdateObject();
                            document.querySelectorAll("#wallet-table-body tr").forEach( function(row) {
                                let textInput = row.querySelector(".em-input input");
                                if(textInput.dataset.original != textInput.value) {
                                    let single = { id: row.dataset.id, address: textInput.value.trim() };
                                    updateObject.wallet.items.push(single);
                                    updateObject.wallet.changed = true;
                                }
                            });
                            if(updateObject.wallet.changed) {
                                document.getElementById("update-data").innerHTML = JSON.stringify(updateObject);
                                document.getElementById("faucets-display-status").innerHTML = "Wallet will be updated as soon as possible...";
                            }
                        },
                        toggleJson: function(val) {
                            if (document.querySelector('#wallet-json').isVisible()) {
                                if(val != 'cancel') {
                                    editWallet.fromJson();
                                }
                            } else {
                                editWallet.toJson();
                            }
                            document.querySelector('.footer-json').classList.toggle('d-none');
                            document.querySelector('.footer-table').classList.toggle('d-none');
                            document.querySelector('#wallet-table').classList.toggle('d-none');
                            document.querySelector('#wallet-json').classList.toggle('d-none');
                        },
                        toJson: function() {
                            let j = [];
                            document.querySelectorAll('#wallet-table-body tr').forEach(function (row) {
                                j.push({ id: row.dataset.id, address: row.querySelector('.em-input input').value });
                            });
                            document.querySelector('#wallet-json').value = JSON.stringify(j);
                        },
                        fromJson: function() {
                            let j = JSON.parse(document.querySelector('#wallet-json').value);
                            document.querySelectorAll('#wallet-table-body tr').forEach(function (row) {
                                let element = j.find(x => x.id == row.dataset.id);
                                if (element) {
                                    row.querySelector('.em-input input').value = element.address;
                                }
                            });
                        },
                        cancel: function() {
                            document.querySelectorAll("#wallet-table-body .em-input input").forEach( function(x) {
                                x.value = x.dataset.original;
                            });
                        }
                    };

                    window.editConfig = {
                        save: function() {
                            let updateObject = getUpdateObject();
                            document.querySelectorAll("#modal-config [data-original][data-prop]").forEach(function(elm) {
                                let single = { prop: elm.dataset.prop, value: elm.dataset.value };
                                if(elm.dataset.original != elm.value && (elm.type == "select-one" || elm.type == "text" || elm.type == "password" || elm.type == "number") ) {
                                    single.value = elm.value;
                                    updateObject.config.items.push(single);
                                    updateObject.config.changed = true;
                                } else if (elm.type == "checkbox" && ((elm.dataset.original == "0" && elm.checked) || (elm.dataset.original == "1" && !elm.checked)) ) {
                                    single.value = elm.checked;
                                    updateObject.config.items.push(single);
                                    updateObject.config.changed = true;
                                }
                            });
                            if(updateObject.config.changed) {
                                document.getElementById("update-data").innerHTML = JSON.stringify(updateObject);
                                document.getElementById("faucets-display-status").innerHTML = "Config will be updated as soon as possible...";
                            }
                        },
                        cancel: function() {
                            document.querySelectorAll("#modal-config [data-original][data-prop]").forEach(function(elm) {
                                if(elm.type == "select-one" || elm.type == "text" || elm.type == "password" || elm.type == "number") {
                                    elm.value = elm.dataset.original;
                                } else if (elm.type == "checkbox") {
                                    elm.checked = (elm.dataset.original == "1" ? true : false)
                                }
                            });
                        }
                    };

                    window.editSite = {
                        save: function() {
                            let updateObject = getUpdateObject();
                            updateObject.site.id = document.querySelector("#faucet-name").dataset.id;
                            document.querySelectorAll("#modal-site [data-original][data-site-prop]").forEach(function(elm) {
                                let single = { prop: elm.dataset.siteProp, value: elm.dataset.value };
                                // console.log("For " + elm.dataset.siteProp + ": original is " + elm.dataset.original + ",value is " + elm.dataset.value + " and checked is " + elm.checked);
                                if(elm.dataset.original != elm.value && (elm.type == "select-one" || elm.type == "text" || elm.type == "password" || elm.type == "number") ) {
                                    single.value = elm.value;
                                    updateObject.site.items.push(single);
                                    updateObject.site.changed = true;
                                } else if (elm.type == "checkbox" && ((elm.dataset.original == "0" && elm.checked) || (elm.dataset.original == "1" && !elm.checked)) ) {
                                    single.value = elm.checked;
                                    updateObject.site.items.push(single);
                                    updateObject.site.changed = true;
                                }
                            });
                            // console.log("@updateObject.site.changed: " + updateObject.site.changed);
                            if(updateObject.site.changed) {
                                document.getElementById("update-data").innerHTML = JSON.stringify(updateObject);
                                document.getElementById("faucets-display-status").innerHTML = "Site will be updated as soon as possible...";
                            }
                        },
                        cancel: function() {
                            document.querySelectorAll("#modal-site [data-original][data-site-prop]").forEach(function(elm) {
                                if(elm.type == "select-one" || elm.type == "text" || elm.type == "password" || elm.type == "number") {
                                    elm.value = elm.dataset.original;
                                } else if (elm.type == "checkbox") {
                                    elm.checked = (elm.dataset.original == "1" ? true : false)
                                }
                            });
                        }
                    };

                    window.editEreport = {
                        save: function() {
                            sendErrorReport();
                        },
                        cancel: function() {
                        }
                    };

                    window.modalSave = function modalSave(content) {
                        switch(content) {
                            case "wallet":
                                editWallet.save();
                                break;
                            case "ereport":
                                editEreport.save();
                                break;
                            case "config":
                                editConfig.save();
                                break;
                            case "site":
                                editSite.save();
                                break;
                        }
                        // if(content == "wallet") {
                        //     editWallet.save();
                        // } else if (content == "ereport") {
                        //     editEreport.save();
                        // } else if (content == "config") {
                        //     editConfig.save();
                        // }
                    };

                    window.modalCancel = function modalCancel(content) {
                        if(content == "wallet") {
                            editWallet.cancel();
                        } else if ("ereport") {
                            editEreport.cancel();
                        }
                        document.querySelectorAll("modal-content").forEach(x => x.classList.add("d-none"));
                    };

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

                    window.removeAllPromos = function removeAllPromos() {
                        var promoCode = document.getElementById("promo-code-new");
                        var promoDisplayStatus = document.getElementById("promo-display-status");
                        var promoObject = { action: "REMOVEALLPROMOS" };
                        promoCode.innerHTML =JSON.stringify(promoObject);
                        promoDisplayStatus.innerHTML = "Removing all promotion codes...<br>This could take around a minute. Please wait..."
                    };

                    window.openStatsChart = function openStatsChart() {
                        if(myBarChart) { myBarChart.destroy(); }
                        let statsFragment = document.getElementById("stats-fragment");
                        if (statsFragment.style.display === "block") { statsFragment.style.display = "none"; document.getElementById("stats-button").innerText = "Lucky Number Stats"; } else {
                            statsFragment.style.display = "block"; document.getElementById("stats-button").innerText = "Close Stats";
                            var canvas = document.getElementById("barChart");
                            var ctx = canvas.getContext("2d");
                            var dataSpan = document.getElementById("rolls-span");
                            var data = {
                                labels: ["0000-9885", "9886-9985", "9986-9993", "9994-9997", "9998-9999", "10000"],
                                datasets: [ { fill: false, backgroundColor: [ "#990000", "#660066", "#000099", "#ff8000", "#ffff00", "#00ff00"],
                                             data: dataSpan.innerText.split(",") } ] };
                            var options = { plugins: { legend: { display: false } }, title: { display: true, text: "Rolled Numbers", position: "top" }, rotation: -0.3 * Math.PI };
                            myBarChart = new Chart(ctx, { type: "pie", data: data, options: options });
                        }
                    };
                }
            };

            let logLines = ['', '', '', '', ''];
            function init(cfFaucets) {
                appendCSS();
                appendJavaScript();
                appendHtml();
                createPromoTable(cfFaucets);
                document.querySelector('.page-title h1').innerHTML = 'Auto Claim';
            };
            function appendCSS() {
                let css = document.createElement('style');
                css.innerHTML = `
                td.em-input {
                  padding-top: 0;
                  padding-bottom: 0;
                }
                `;
                document.head.appendChild(css);
            };
            function appendJavaScript() {
                addJS_Node (null, null, injectables.managerJs);
            };
            function appendHtml() {
                let html ='';
//                 html += '<div class="fixed-bottom" style="display: table;line-height: 42px;padding: 5px 30px;font-weight: 700;font-size: 17px;">';

//                 if (shared.getConfig()['devlog.enabled']) {
//                     html += '<div><a class="btn m-2 anchor btn-outline-primary align-middle" data-toggle="modal" data-target="#modal-dlg" onclick="loadDlg(\'modal-ereport\')"><i class="fa fa-bug"></i> View Log</a></div>';
//                 }
//                 html += '<div><a class="btn m-2 anchor btn-outline-primary align-middle" data-toggle="modal" data-target="#modal-dlg" onclick="loadDlg(\'modal-config\')"><i class="fa fa-cog"></i> Edit Config</a></div>';

//                 html += '</div>';

                html += '<div class="modal fade" id="modal-dlg" tabindex="-1" role="dialog" data-backdrop="static" aria-hidden="true">';
                html += ' <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" role="document">';

                // MODAL CONTENTS
                //[Loading]
                html += '<div class="modal-content bg-beige" id="modal-spinner"><div class="modal-body"><div class="d-flex justify-content-center"><span id="target-spinner" class="d-none"></span><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>Loading data</div></div></div>';

                //Error report
                html += '  <div class="modal-content bg-beige d-none" id="modal-ereport">';
                html += '   <div class="modal-header"><h5 class="modal-title">Submit an Error</h5></div>';
                html += '    <div class="modal-body">';
                html += '     <div class="alert alert-danger">Don\'t send private information as data might be publicly access.</div>';
                html += '      <textarea rows="4" id="log-message" class="form-control" placeholder="PLEASE do not send logs without describing here the issue you are facing..."></textarea>';
                html += '      <label for="log-textarea">Log</label>';
                html += '      <textarea rows="10" id="log-textarea" class="form-control"></textarea>';
                html += '    </div>';
                html += '    <div class="modal-footer"><a class="btn m-2 anchor btn-outline-danger align-middle" onclick="modalCancel(\'ereport\')" data-dismiss="modal"><i class="fa fa-times-circle"></i> Cancel</a>';
                html += '    <a class="btn m-2 anchor btn-outline-success align-middle" onclick="modalSave(\'ereport\')" data-dismiss="modal"><i class="fa fa-paper-plane"></i> Send</a></div>';
                html += '  </div>';

                //Wallet
                html += '  <div class="modal-content bg-beige d-none" id="modal-wallet">';
                html += '   <div class="modal-header"><h5 class="modal-title">Your Addresses</h5></div>';
                html += '    <div class="modal-body">';
                html += '     <div><table class="table table-striped" id="wallet-table">';
                html += '          <thead><tr><th class="">Name</th><th class="">Address</th></tr></thead>';
                html += '          <tbody class="overflow-auto" id="wallet-table-body"></tbody></table><textarea rows="14" id="wallet-json" class="d-none w-100">asdasdasd</textarea>';
                html += '     </div>';
                html += '    </div>';
                html += '<div class="modal-footer">';
                html += '<div class="footer-json d-none">';
                html += '<a class="btn m-2 anchor btn-outline-danger align-middle" onclick="editWallet.toggleJson(\'cancel\')"><i class="fa fa-times-circle"></i> Cancel</a>';
                html += '<a class="btn m-2 anchor btn-outline-primary align-middle" onclick="editWallet.toggleJson()"><i class="fa fa-edit"></i> Confirm</a></div>';
                html += '<div class="footer-table"><a class="btn m-2 anchor btn-outline-primary align-middle" onclick="editWallet.toggleJson()"><i class="fa fa-edit"></i> Edit as JSON</a>';
                html += '<a class="btn m-2 anchor btn-outline-danger align-middle" onclick="modalCancel(\'wallet\')" data-dismiss="modal"><i class="fa fa-times-circle"></i> Cancel</a>';
                html += '<a class="btn m-2 anchor btn-outline-success align-middle" onclick="modalSave(\'wallet\')" data-dismiss="modal"><i class="fa fa-check-circle"></i> Save</a></div></div>';
                html += '   </div>';

                //Edit Site / single faucet
                html += '  <div class="modal-content bg-beige d-none" id="modal-site">';
                html += '    <div class="modal-header"><h5 class="modal-title">Edit <span id="faucet-name" data-id=""></span> Configuration</h5></div>';
                html += '    <div class="modal-body">';
                html += '     <div class="alert alert-warning">Override Settings for the selected faucet.<br>This is a WIP. More fields will be added and faucet-specific configurations will be moved here.</div>';
                html += '     <div class="row">';
                html += '       <div class="col-md-6 col-sm-12">';
                //html += '         <div class="card m-1"><div class="card-header"><label class="switch"><input type="checkbox" data-site-prop="defaults.workInBackground"><span class="slider round"></span></label> Override</div><div class="card-body px-4">';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;"><label class="switch"><input type="checkbox" data-site-prop="defaults.workInBackground.override" data-original="1" data-com.bitwarden.browser.user-edited="yes"><span class="slider round"></span></label> Override Work Mode</div>';
                html += '           <div class="card-body px-4">';
                html += '             <div><label class="switch"><input type="checkbox" data-site-prop="defaults.workInBackground"><span class="slider round"></span></label> Open tab in background</div>';
                html += '         </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;"><label class="switch"><input type="checkbox" data-site-prop="defaults.timeout.override" data-original="1" data-com.bitwarden.browser.user-edited="yes"><span class="slider round"></span></label> Override Timeout</div>';
                html += '           <div class="card-body px-4">';
                html += '             <label class="control-label">Timeout after:</label>';
                html += '             <select class="custom-select" data-site-prop="defaults.timeout">';
                html += '              <option value="-1">Never</option><option value="1">1 minute</option><option value="2">2 minutes</option><option value="4">4 minutes</option><option value="5">5 minutes</option><option value="10">10 minutes</option><option value="20">20 minutes</option>';
                html += '             </select>';
                html += '         </div></div>';
                html += '       </div>';
                html += '       <div class="col-md-6 col-sm-12">';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;"><label class="switch"><input type="checkbox" data-site-prop="defaults.postponeMinutes.override" data-original="1" data-com.bitwarden.browser.user-edited="yes"><span class="slider round"></span></label> Override Postpone</div>';
                html += '           <div class="card-body px-4">';
                html += '             <label class="control-label">After timeout/error, claim unable to read countdown, postpone for:</label>';
                html += '             <select class="custom-select" data-site-prop="defaults.postponeMinutes">';
                html += '              <option value="0">Random between...</option><option value="15">15 minutes</option><option value="30">30 minutes</option><option value="35">35 minutes</option><option value="45">45 minutes</option><option value="65">65 minutes</option><option value="90">90 minutes</option><option value="120">120 minutes</option>';
                html += '             </select>';
                html += '             <table><tr><td><input type="number" data-site-prop="defaults.postponeMinutes.min" min="1" value="15" step="5" class="form-control"></td><td>and</td>';
                html += '                        <td><input type="number" data-site-prop="defaults.postponeMinutes.max" value="65" step="5" class="form-control"></td><td>minutes</td></tr></table>';
                html += '         </div></div>';
                html += '       </div>';
                html += '     </div>';
                html += '    </div>';
                html += '<div class="modal-footer"><a class="btn m-2 anchor btn-outline-danger align-middle" onclick="modalCancel(\'site\')" data-dismiss="modal"><i class="fa fa-times-circle"></i> Cancel</a>';
                html += '<a class="btn m-2 anchor btn-outline-success align-middle" onclick="modalSave(\'site\')" data-dismiss="modal"><i class="fa fa-check-circle"></i> Save</a></div>';
                html += '   </div>';

                //Config
                html += '<div class="modal-content bg-beige d-none" id="modal-config">';
                html += '  <div class="modal-header"><h5 class="modal-title">Settings</h5></div>';
                html += '  <div class="modal-body">';
                html += '     <div class="alert alert-danger alert-dismissible fade show">This form does not upload data. Values are added to a span, then read by the script and locally stored by Tampermonkey using GM_setValue.<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>';
                html += '     <div class="alert alert-danger alert-dismissible fade show">Time values are estimated and will be randomly modified by +/-2% aprox.<br>The script will trigger a reload of the page after updating the data.<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>';
                html += '     <div class="row">';
                html += '     <div class="col-md-6 col-sm-12">';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">Defaults</div>';
                html += '           <div class="card-body px-4">';
                //html += '       <div class="card m-1" data-level="defaults"><div class="card-body px-4">';
                //html += '          <h6>Defaults</h6>';
                html += '           <div><label class="switch"><input type="checkbox" data-prop="defaults.workInBackground"><span class="slider round"></span></label> Open tabs in background</div>';
                html += '           <div><label class="switch"><input type="checkbox" data-prop="defaults.extraInterval"><span class="slider round"></span></label> Use extra timer to detect ad redirects faster</div>';
                html += '           <label class="control-label">Timeout after:</label>';
                html += '           <input type="number" data-prop="defaults.timeout" min="2" value="5" step="1" class="form-control">';
                // html += '           <select class="custom-select" data-prop="defaults.timeout">';
                // html += '            <option value="-1">Never</option><option value="1">1 minute</option><option value="2">2 minutes</option><option value="4">4 minutes</option><option value="5">5 minutes</option><option value="10">10 minutes</option><option value="20">20 minutes</option>';
                // html += '           </select>';
                html += '           <label class="control-label">After timeout/error, claim unable to read countdown, postpone for:</label>';
                html += '           <select class="custom-select" data-prop="defaults.postponeMinutes">';
                html += '            <option value="0">Random between...</option><option value="15">15 minutes</option><option value="30">30 minutes</option><option value="35">35 minutes</option><option value="45">45 minutes</option><option value="65">65 minutes</option><option value="90">90 minutes</option><option value="120">120 minutes</option>';
                html += '           </select>';
                html += '           <table><tr><td><input type="number" data-prop="defaults.postponeMinutes.min" min="1" value="15" step="5" class="form-control"></td><td>and</td>';
                html += '                      <td><input type="number" data-prop="defaults.postponeMinutes.max" value="65" step="5" class="form-control"></td><td>minutes</td></tr></table>';
                // html += '           <select class="custom-select" data-prop="defaults.postponeMinutes">';
                // html += '            <option value="15">15 minutes</option><option value="30">30 minutes</option><option value="45">45 minutes</option><option value="65">65 minutes</option><option value="90">90 minutes</option><option value="120">2 hours</option>';
                // html += '           </select>';
                html += '       </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">Logging</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>Logging</h6>';
                html += '          <div><label class="switch"><input type="checkbox" data-prop="devlog.enabled" ><span class="slider round"></span></label> Store log (enables the \'Log\' button)</div>';
                html += '           <label class="control-label">Max log size in lines:</label>';
                html += '           <input type="number" data-prop="devlog.maxLines" min="100" step="100" class="form-control">';
                // html += '           <select class="custom-select" data-prop="devlog.maxLines">';
                // html += '            <option value="50">50 lines</option><option value="100">100 lines</option><option value="200">200 lines</option><option value="500">500 lines</option><option value="1000">1000 lines</option><option value="2000">2000 lines</option>';
                // html += '           </select>';
                html += '       </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">BigBtc</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>BigBtc</h6>';
                html += '           <label class="control-label">Claim every:</label>';
                html += '           <select class="custom-select" data-prop="bigbtc.postponeMinutes">';
                html += '            <option value="-1">Default</option><option value="0">Randomize</option><option value="14">14 minutes</option><option value="35">35 minutes</option><option value="50">50 minutes</option><option value="70">70 minutes</option><option value="90">90 minutes</option><option value="115">115 minutes</option>';
                html += '           </select>';
                html += '       </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">Bagi/Keran</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>Bagi/Keran</h6>';
                html += '           <label class="control-label">Auto withdraw:</label>';
                html += '           <select class="custom-select" data-prop="bk.withdrawMode">';
                html += '            <option value="0">Disabled</option><option value="1">Once every X hours</option><option value="2">After each successful claim</option>';
                html += '           </select>';
                html += '           <label class="control-label">Hours (X) between withdraws:</label>';
                html += '           <select class="custom-select" data-prop="bk.hoursBetweenWithdraws">';
                html += '            <option value="0">Disabled</option><option value="2">2</option><option value="4">4</option><option value="6">6</option><option value="8">8</option><option value="12">12</option><option value="24">24</option>';
                html += '           </select>';
                html += '           <label class="control-label">Time to wait If IP is restricted:</label>';
                html += '           <select class="custom-select" data-prop="bk.sleepMinutesIfIpBan">';
                html += '            <option value="0">Disabled</option><option value="45">45 minutes</option><option value="60">1hr</option><option value="75">1hr 15min</option><option value="90">1hr 30min</option><option value="120">2hrs</option><option value="180">3hrs</option><option value="240">4hrs</option>';
                html += '           </select>';
                html += '       </div></div>';
                html += '     </div>';
                html += '     <div class="col-md-6 col-sm-12">';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">CryptosFaucets</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>CryptosFaucets</h6>';
                html += '          <div><label class="switch"><input type="checkbox" data-prop="cf.autologin" ><span class="slider round"></span></label> Autologin when neccessary</div>';
                html += '           <select class="custom-select" data-prop="cf.credentials.mode">';
                html += '            <option value="1">Use Email and Password</option><option value="2">Filled by 3rd party software/extension</option>';
                html += '           </select>';
                html += '           <label class="control-label">E-Mail</label>';
                html += '           <input maxlength="200" type="text" data-prop="cf.credentials.email" required="required" class="form-control" placeholder="Email address..."/>';
                html += '           <label class="control-label">Password</label>';
                html += '           <input maxlength="200" type="password" data-prop="cf.credentials.password" required="required" class="form-control" placeholder="Password..."/>';
                html += '           <label class="control-label">Hours to wait If IP is banned:</label>';
                html += '           <select class="custom-select" data-prop="cf.sleepHoursIfIpBan">';
                html += '            <option value="0">Disabled</option><option value="2">2</option><option value="4">4</option><option value="8">8</option><option value="16">16</option><option value="24">24</option><option value="26">26</option>';
                html += '           </select>';
                html += '       </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">CryptosFaucets</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>FreeBitco.in</h6>';
                html += '          <div><label class="switch"><input type="checkbox" data-prop="fb.activateRPBonus" checked><span class="slider round"></span></label> Activate RP Bonus</div>';
                html += '       </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">BestChange</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>BestChange</h6>';
                html += '           <label class="control-label">BTC Address:</label>';
                html += '           <select class="custom-select" data-prop="bestchange.address">';
                html += '            <option value="101">Faucet Pay BTC</option><option value="1">BTC Alt Address</option>';
                html += '           </select>';
                html += '       </div></div>';
                html += '         <div class="card m-1"><div class="card-header" style="font-weight: 500; line-height: 1.2; font-size: 1rem;">FaucetPay PTC</div>';
                html += '           <div class="card-body px-4">';
                // html += '       <div class="card m-1"><div class="card-body px-4">';
                // html += '          <h6>FaucetPay PTC</h6>';
                html += '           <label class="control-label">Hours between runs:</label>';
                html += '           <select class="custom-select" data-prop="fp.hoursBetweenRuns">';
                html += '            <option value="1">1</option><option value="2">2</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="8">8</option><option value="12">12</option><option value="24">24</option>';
                html += '           </select>';
                html += '           <label class="control-label">Max duration per run:</label>';
                html += '           <select class="custom-select" data-prop="fp.maxTimeInMinutes">';
                html += '            <option value="5">5 minutes</option><option value="10">10 minutes</option><option value="15">15 minutes</option><option value="30">30 minutes</option>';
                html += '           </select>';
                html += '       </div></div>';
                html += '     </div>';
                html += '     </div>';
                html += '  </div>';
                html += '<div class="modal-footer"><a class="btn m-2 anchor btn-outline-danger align-middle" onclick="modalCancel(\'config\')" data-dismiss="modal"><i class="fa fa-times-circle"></i> Cancel</a>';
                html += '<a class="btn m-2 anchor btn-outline-success align-middle" onclick="modalSave(\'config\')" data-dismiss="modal"><i class="fa fa-check-circle"></i> Save</a></div>';
                html += '   </div>';
                //END OF MODAL CONTENTS

                html += '</div>';
                html += '</div>';


                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 text-white justify-content-center">';
                if (shared.getConfig()['devlog.enabled']) {
                    html += '<a class="btn m-2 anchor btn-outline-primary align-middle" data-toggle="modal" data-target="#modal-dlg" onclick="loadDlg(\'modal-ereport\')"><i class="fa fa-bug"></i> Log</a>';
                }
                html += '<a class="btn m-2 anchor btn-outline-primary align-middle" data-toggle="modal" data-target="#modal-dlg" onclick="loadDlg(\'modal-config\')"><i class="fa fa-cog"></i> Settings</a>';
                html += '<a class="btn m-2 anchor btn-outline-primary align-middle" data-toggle="modal" data-target="#modal-dlg" onclick="loadDlg(\'modal-wallet\')"><i class="fa fa-wallet"></i> Wallet</a>';
                html += '</div><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 class="em-hide"><a class="btn m-2 anchor btn-outline-primary align-middle" data-toggle="modal" data-target="#modal-dlg" onclick="loadDlg(\'modal-wallet\')"><i class="fa fa-wallet"></i> Wallet</a><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-4 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" list="promoCode_list" placeholder="Type a Promo code...">';
                html += '<datalist id="promoCode_list">';
                K.CF.ReusableCodeSuggestions.forEach( function(x) { html += '<option>' + x + '</option>' });
                html += '</datalist>';
                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-3 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>';

                let wrapper = document.createElement('div');
                wrapper.innerHTML = html.trim();

                let target = document.getElementById('referral-table');
                target.parentNode.insertBefore(wrapper, target);
                document.getElementById('schedule-container').appendChild( createScheduleTable() );

                let discord = document.createElement('div');
                discord.innerHTML = '<a class="btn m-2 btn-primary" href="https://discord.gg/23s9fDgHqe" target="_blank"><div class="">discord</div></a>';
                document.querySelector('.navbar-nav').prepend(discord);
            };
            function createPromoTable(faucets) {
                let table = document.createElement('table');
                let inner = '';
                table.classList.add('table', 'table-striped', 'table-dark');
                table.setAttribute('id','promo-table');

                inner += '<caption style="text-align: -webkit-center;">⏳ Pending ✔️ Accepted 🕙 Used Before ❌ Invalid code ❗ Unknown error ⚪ No code</caption>';
                inner += '<thead><tr><th class="">Code</th><th class="">Added</th>';

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

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

                table.innerHTML = inner
                document.getElementById('promo-container').appendChild( table );
            };
            function createScheduleTable() {
                let table = document.createElement('table');
                let inner;
                table.classList.add('table', 'table-striped', 'table-dark');
                table.setAttribute('id','schedule-table');

                inner = '<thead><tr>';
                inner += '<th scope="col" class="edit-status d-none em-only" style="">Active</th><th class="">Next Roll</th><th class=""></th><th class="">Name</th><th class="">Last Claim</th>';
                inner += '<th class="">Aggregate</th><th class="">Balance</th><th class="" id="converted-balance-col">FIAT</th>';
                inner += '<th scope="col" class="">Msgs</th><th scope="col" class="em-hide" style="">Actions</th></tr></thead><tbody id="schedule-table-body"></tbody>';
                table.innerHTML = inner;

                return table;
            };
            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="';
                    if (data[i].balance) {
                        if(typeof data[i].balance == 'string') {
                            tableBody += data[i].balance.split(' ')[0];
                        } else {
                            tableBody += data[i].balance.toFixed(8);
                        }
                    }
                    tableBody += '">';
                    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">' + helpers.getPrintableTime(data[i].nextRoll) + '</td>';
                    tableBody +='<td class="align-middle text-left">';
                    //tableBody +='<td class="align-middle text-left" style="width: 20%">';
                    if (Array.isArray(data[i].rf)) {
                        tableBody +='<a class="edit-site" title="Visit site" target="_blank" href="' + data[i].url.href + data[i].rf[helpers.randomInt(0, data[i].rf.length -1)] + '"><i class="fa fa-external-link-alt"></i></a> ';
                    } else {
                        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 += '</td>';
                    tableBody +='<td class="align-middle em-input text-left" data-field="displayName">' + data[i].name + '</td>';
                    // tableBody +='<td class="align-middle text-left">';
                    // //tableBody +='<td class="align-middle text-left" style="width: 20%">';
                    // if (Array.isArray(data[i].rf)) {
                    //     tableBody +='<a class="edit-site" title="Visit site" target="_blank" href="' + data[i].url.href + data[i].rf[helpers.randomInt(0, data[i].rf.length -1)] + '"><i class="fa fa-external-link-alt"></i></a> ';
                    // } else {
                    //     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].type == K.WebType.BAGIKERAN ? data[i].url.host + data[i].url.pathname : data[i].url.host) + '</td>';
                    if (data[i].id == '61') {
                        tableBody +='<td class="align-middle">' + data[i].lastClaim.toFixed(0) + '</td>';
                        tableBody +='<td class="align-middle">' + data[i].aggregate.toFixed(0) + '</td>';
                    } else {
                        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">';
                    if (data[i].balance) {
                        if(typeof data[i].balance == 'string') {
                            tableBody += data[i].balance.split(' ')[0];
                        } else {
                            tableBody += data[i].balance.toFixed(8);
                        }
                    }
                    tableBody + '</td>';
                    tableBody +='<td class="align-middle fiat-conversion"></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 +='<td class="align-middle justify-content-center em-hide">';
                    tableBody +='<a class="px-2 edit-site" title="Run ASAP" href="javascript:updateValues(\'runAsap\', { id: ' + data[i].id + ' })" onclick=""><i class="fa fa-redo"></i></a>';
                    tableBody +='<a class="px-2 edit-site" title="Edit..." data-toggle="modal" data-target="#modal-dlg" href="#" onclick="loadDlg(\'modal-site\', \'' + data[i].id + '\')"><i class="fa fa-edit"></i></a>';
                    tableBody +='</td></tr>';
                }

                //                $('#schedule-table-body').html(tableBody);
                document.getElementById('schedule-table-body').innerHTML = 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}">${helpers.getEnumText(K.ErrorType, 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>';
                }

                document.getElementById('promo-table-body').innerHTML = tableBody;
            };
            function loadWalletTable(data) {
                let tableBody = '';

                for(let i=0, all = data.length; i < all; i++) {
                    tableBody += '<tr class="align-middle" data-id="'+ data[i].id + '">';
                    tableBody += '<td class="align-middle">' + data[i].name + '</td>';
                    tableBody += '<td class="align-middle em-input"><input type="text" class="w-100" data-field="address" data-original="' + data[i].address + '" value="' + data[i].address + '"></td>';
                    tableBody += '</tr>';
                }

                document.getElementById('wallet-table-body').innerHTML = tableBody;
            };
            function loadConfigData(data) {
                for (const prop in data) {
                    let element = document.querySelector('[data-prop="' + prop + '"]');
                    if(element) {
                        if(element.type == 'select-one' || element.type == 'text' || element.type == 'password' || element.type == 'number') {
                            element.dataset.original = data[prop];
                            element.value = data[prop];
                        } else if (element.type == 'checkbox') {
                            element.dataset.original = (data[prop] ? "1" : "0");
                            element.checked = data[prop];
                        }
                    }
                }

                let elPostpone = document.querySelector('[data-prop="defaults.postponeMinutes"]');
                let elPostponeMin = document.querySelector('[data-prop="defaults.postponeMinutes.min"]');
                let elPostponeMax = document.querySelector('[data-prop="defaults.postponeMinutes.max"]');
                let elCredentialsAutologin = document.querySelector('[data-prop="cf.autologin"]');
                let elCredentialsMode = document.querySelector('[data-prop="cf.credentials.mode"]');
                let elCredentialsEmail = document.querySelector('[data-prop="cf.credentials.email"]');
                let elCredentialsPassword = document.querySelector('[data-prop="cf.credentials.password"]');
                let elWithdrawMode = document.querySelector('[data-prop="bk.withdrawMode"]');
                let elHoursBetweenWithdraws = document.querySelector('[data-prop="bk.hoursBetweenWithdraws"]');
                let elDevlogEnabled = document.querySelector('[data-prop="devlog.enabled"]');
                let elDevlogMaxLines = document.querySelector('[data-prop="devlog.maxLines"]');

                elPostponeMin.disabled = (elPostpone.value > "0");
                elPostponeMax.disabled = (elPostpone.value > "0");
                if (elPostponeMin.disabled && elPostponeMax.disabled) {
                    elPostponeMin.value = elPostpone.value;
                    elPostponeMax.value = elPostpone.value;
                }

                elCredentialsMode.disabled = !elCredentialsAutologin.checked;

                elCredentialsEmail.disabled = ( (!elCredentialsAutologin.checked || elCredentialsMode.value == "2") ? true : false);
                elCredentialsPassword.disabled = ( (!elCredentialsAutologin.checked || elCredentialsMode.value == "2") ? true : false);

                elHoursBetweenWithdraws.disabled = ( (elWithdrawMode.value == "0" || elWithdrawMode.value == "2") ? true : false);

                elDevlogMaxLines.disabled = !elDevlogEnabled.checked;

                elPostpone.onchange = function (e) {
                    document.querySelector('[data-prop="defaults.postponeMinutes.min"]').disabled = e.target.value > 0;
                    document.querySelector('[data-prop="defaults.postponeMinutes.max"]').disabled = e.target.value > 0;
                    if (e.target.value > 0) {
                        document.querySelector('[data-prop="defaults.postponeMinutes.min"]').value = e.target.value;
                        document.querySelector('[data-prop="defaults.postponeMinutes.max"]').value = e.target.value;
                    }
                }

                elCredentialsAutologin.onchange = function (e) {
                    document.querySelector('[data-prop="cf.credentials.mode"]').disabled = !e.target.checked;
                    if (elCredentialsMode.value == "2") {
                        document.querySelector('[data-prop="cf.credentials.email"]').disabled = true;
                        document.querySelector('[data-prop="cf.credentials.password"]').disabled = true;
                    } else {
                        document.querySelector('[data-prop="cf.credentials.email"]').disabled = false;
                        document.querySelector('[data-prop="cf.credentials.password"]').disabled = false;
                    }
                }

                elCredentialsMode.onchange = function (e) {
                    if (e.target.value == "2") {
                        document.querySelector('[data-prop="cf.credentials.email"]').disabled = true;
                        document.querySelector('[data-prop="cf.credentials.password"]').disabled = true;
                    } else {
                        document.querySelector('[data-prop="cf.credentials.email"]').disabled = false;
                        document.querySelector('[data-prop="cf.credentials.password"]').disabled = false;
                    }
                }

                elWithdrawMode.onchange = function (e) {
                    if (e.target.value == "0" || e.target.value == "2") {
                        document.querySelector('[data-prop="bk.hoursBetweenWithdraws"]').disabled = true;
                    } else {
                        document.querySelector('[data-prop="bk.hoursBetweenWithdraws"]').disabled = false;
                    }
                }

                elDevlogEnabled.onchange = function (e) {
                    document.querySelector('[data-prop="devlog.maxLines"]').disabled = !e.target.checked;
                }

            };
            function loadSiteData(site, config) {
                // console.log(JSON.stringify(site));
                document.querySelector('#faucet-name').innerHTML = site.name;
                document.querySelector('#faucet-name').dataset.id = site.id;
                let data = site.params || {};

                const overridableDefaults = ['defaults.workInBackground', 'defaults.timeout', 'defaults.postponeMinutes', 'defaults.postponeMinutes.min', 'defaults.postponeMinutes.max'];
                const defaults = Object.keys(config).filter(key => overridableDefaults.includes(key)).reduce((obj, key) => {
                    obj[key] = config[key];
                    return obj;
                }, {});
                // console.log(defaults);

                for (const prop in defaults) {
                    let overrideElement = document.querySelector('[data-site-prop="' + prop + '.override"]');
                    if (overrideElement) {
                        overrideElement.dataset.original = (data[prop + '.override'] ? "1" : "0");
                        overrideElement.checked = data[prop + '.override'];
                    }

                    let element = document.querySelector('[data-site-prop="' + prop + '"]');
                    if(element) {
                        // console.log('Adding prop: ' + prop);
                        if(element.type == 'select-one' || element.type == 'text' || element.type == 'password' || element.type == 'number') {
                            element.dataset.original = data[prop] ?? defaults[prop];
                            element.value = data[prop] ?? defaults[prop];
                        } else if (element.type == 'checkbox') {
                            element.dataset.original = ((data[prop] ?? defaults[prop]) ? "1" : "0");
                            // element.dataset.original = (defaults[prop] ? "1" : "0");
                            element.checked = data[prop] ?? defaults[prop];
                            // element.checked = defaults[prop];
                        }
                        element.disabled = true;
                    }
                }

                let elWorkInBackgroundOverride = document.querySelector('[data-site-prop="defaults.workInBackground.override"]');
                let elWorkInBackground = document.querySelector('[data-site-prop="defaults.workInBackground"]');
                let elTimeoutOverride = document.querySelector('[data-site-prop="defaults.timeout.override"]');
                let elTimeout = document.querySelector('[data-site-prop="defaults.timeout"]');
                let elPostponeOverride = document.querySelector('[data-site-prop="defaults.postponeMinutes.override"]');
                let elPostpone = document.querySelector('[data-site-prop="defaults.postponeMinutes"]');
                let elPostponeMin = document.querySelector('[data-site-prop="defaults.postponeMinutes.min"]');
                let elPostponeMax = document.querySelector('[data-site-prop="defaults.postponeMinutes.max"]');

                elWorkInBackground.disabled = !elWorkInBackgroundOverride.checked;
                elWorkInBackgroundOverride.onchange = function (e) {
                    document.querySelector('[data-site-prop="defaults.workInBackground"]').disabled = !e.target.checked;
                }

                elTimeout.disabled = !elTimeoutOverride.checked;
                elTimeoutOverride.onchange = function (e) {
                    document.querySelector('[data-site-prop="defaults.timeout"]').disabled = !e.target.checked;
                }

                elPostpone.disabled = !elPostponeOverride.checked;
                elPostponeMin.disabled = !elPostponeOverride.checked || (elPostpone.value > "0");
                elPostponeMax.disabled = !elPostponeOverride.checked || (elPostpone.value > "0");
                elPostponeOverride.onchange = function (e) {
                    let mode = document.querySelector('[data-site-prop="defaults.postponeMinutes"]');
                    mode.disabled = !e.target.checked;
                    document.querySelector('[data-site-prop="defaults.postponeMinutes.min"]').disabled = !e.target.checked || mode.value > 0;
                    document.querySelector('[data-site-prop="defaults.postponeMinutes.max"]').disabled = !e.target.checked || mode.value > 0;
                    // if (e.target.checked && e.target.value > 0) {
                    //     document.querySelector('[data-site-prop="defaults.postponeMinutes.min"]').value = e.target.value;
                    //     document.querySelector('[data-site-prop="defaults.postponeMinutes.max"]').value = e.target.value;
                    // }
                }
                elPostpone.onchange = function (e) {
                    document.querySelector('[data-site-prop="defaults.postponeMinutes.min"]').disabled = e.target.value > 0;
                    document.querySelector('[data-site-prop="defaults.postponeMinutes.max"]').disabled = e.target.value > 0;
                    if (e.target.value > 0) {
                        document.querySelector('[data-site-prop="defaults.postponeMinutes.min"]').value = e.target.value;
                        document.querySelector('[data-site-prop="defaults.postponeMinutes.max"]').value = e.target.value;
                    }
                }

                elPostponeMin.onchange = function (e) {
                    let max = document.querySelector('[data-site-prop="defaults.postponeMinutes.max"]');
                    if (e.target.value > max.value) {
                        max.value = e.target.value;
                    }
                }
                elPostponeMax.onchange = function (e) {
                    let min = document.querySelector('[data-site-prop="defaults.postponeMinutes.min"]');
                    if (e.target.value < min.value) {
                        min.value = e.target.value;
                    }
                }


                return;
            };
            function refresh(scheduleData, promotionData, walletData, configData, siteData) {
                if (scheduleData) {
                    loadScheduleTable(scheduleData);
                }
                if (promotionData) {
                    loadPromotionTable(promotionData);
                }
                if (walletData) {
                    loadWalletTable(walletData);
                }
                if (configData) {
                    loadConfigData(configData);
                }
                if (siteData) {
                    loadSiteData(siteData.site, siteData.config);
                }
            };
            function log(msg, elapsed = false) {
                if(shared.getConfig()['devlog.enabled']) { shared.devlog(msg, elapsed) };
                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);
                    }
                    document.getElementById('console-log').innerHTML = 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 == K.CF.PromoStatus.ACCEPTED && (Date.now() - faucet.execTimeStamp.getTime()) > K.Integers.HS_26_IN_MILLISECONDS)
                       || (faucet.status == K.CF.PromoStatus.USEDBEFORE && (Date.now() - faucet.execTimeStamp.getTime()) > K.Integers.HS_2_IN_MILLISECONDS)) {
                        faucet.status = K.CF.PromoStatus.PENDING;
                    }
                }
                return faucet.status ?? K.CF.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 = K.CF.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 == K.CF.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 = K.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 K.RandomInteractionLevel.NONE:
                        maxActions = 0;
                        break;
                    case K.RandomInteractionLevel.LOW:
                        maxActions = helpers.randomInt(2, 4);
                        break;
                    case K.RandomInteractionLevel.MEDIUM:
                        maxActions = helpers.randomInt(5, 8);
                        break;
                    case K.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(run, helpers.randomMs(5000, 10000));
                    return;
                } else if (hasPopup()) {
                    closePopup();
                    setTimeout(run, helpers.randomMs(5000, 10000));
                } else {
                    if(isMinerActive()) {
                        processRunDetails();
                    } else {
                        activateMiner();
                    }
                }
            };
            function hasPopup() {
                return document.querySelectorAll('.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 {
                    document.querySelector('svg.flex.w-8.h-8.fill-current').parentElement.click();
                } catch { }
            };
            function isLoading() {
                return document.getElementById('loader-logo') ? true : false;
            };
            function isMinerActive() {
                timerSpans = document.querySelector('.mb-8 .wrapper .mb-1 span:last-child');

                if(timerSpans) {
                    shared.devlog(`SG: Miner is active`);
                    return true;
                } else {
                    shared.devlog(`SG: Miner is inactive`);
                    return false;
                }
                return (!!timerSpans);
            };
            function activateMiner() {
                const activateButton = document.querySelector('.mb-8 .wrapper button');
                if (activateButton) {
                    activateButton.click();
                    shared.devlog(`SG: Activate miner clicked`);
                    setTimeout(run, helpers.randomMs(10000, 20000));
                } else {
                    processRunDetails()
                }
            };

            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.innerText.split(':');
                    if (timeLeft[0] == 'Synchronizing') {
                        //should retry to load the value
                    }
                    shared.devlog(`SG Countdown timeLeft spans:`);
                    shared.devlog(timeLeft);

                    if(timeLeft.length === 3) {
                        mins = parseInt(timeLeft[0]) * 60 + parseInt(timeLeft[1]);
                    }
                } catch (err) { shared.devlog(`SG Error reading countdown: ${err}`); }
                return mins;
            };
            function readBalance() {
                let balance = "";
                try {
                    balance = document.querySelector('span.text-accent').innerText + " BTC";
                    //                    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 init() {
                let urlType = helpers.cf.getUrlType(window.location.href);
                switch(urlType) {
                    case K.CF.UrlType.FREE:
                        if(localeConfig.setToEnglish) {
                            let refValue = document.querySelectorAll('.nav-item a')[4].innerHTML;
                            if (refValue != 'Settings') {
                                window.location.href = '/set-language/en';
                            }
                        }
                        addJS_Node (null, null, overrideSelectNativeJS_Functions);
                        interactions = objectGenerator.createInteractions();
                        run();
                        break;

                    case K.CF.UrlType.PROMOTION:
                        interactions = objectGenerator.createInteractions();
                        runPromotion();
                        break;

                    case K.CF.UrlType.HOME:
                        if (shared.getConfig()['cf.autologin']) {
                            addJS_Node (null, null, overrideSelectNativeJS_Functions);
                            doLogin();
                        } else {
                            shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
                        }
                        break;

                    case K.CF.UrlType.CONTACTTWITTER:
                        shared.closeWithError(K.ErrorType.IP_BAN, '');
                        break;
                    default:
                        break;
                }
                return;
            }

            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) {
                    document.getElementById('process-status').innerHTML = 'Interacting';
                    timeWaiting = 0;
                    interact();
                } else {
                    timeWaiting = -1;
                    document.getElementById('process-status').innerHTML = 'Waiting for document fully loaded';
                    setTimeout(isFullyLoaded, helpers.randomMs(45000, 55000));
                }
            };
            function runPromotion() {
                navigationProcess = NavigationProcess.PROCESSING_PROMOTION
                displayStatusUi();
                setTimeout(findPromotionTag, helpers.randomMs(1000, 3000));
            };
            function findCountdownOrRollButton() {
                if( isCountdownVisible() && !isRollButtonVisible() ) {
                    timeWaiting = 0;
                    processRunDetails();
                } else if ( !isCountdownVisible() && isRollButtonVisible() ) {
                    timeWaiting = 0;
                    setTimeout(isFullyLoaded, helpers.randomMs(1000, 5000));
                } else {
                    if (timeWaiting/1000 > shared.getConfig()['defaults.timeout'] * 60) {
                        shared.closeWithError(K.ErrorType.TIMEOUT, '');
                        return;
                    }

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

                if (timeWaiting/1000 > shared.getConfig()['defaults.timeout'] * 60) {
                    shared.closeWithError(K.ErrorType.TIMEOUT, '');
                    return;
                }

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

                interactions.start(selectables);
                setTimeout(waitInteractions, helpers.randomMs(2000, 4000));
            }
            function waitInteractions() {
                if(interactions.completed()) {
                    roll();
                } else {
                    setTimeout(waitInteractions, helpers.randomMs(2000, 4000));
                }
            }
            function isCountdownVisible() {
                countdown = document.querySelectorAll('.timeout-wrapper');
                return (countdown.length > 0 && countdown[0].isVisible());
            };
            function isRollButtonVisible() {
                rollButton = document.querySelectorAll('.main-button-2.roll-button.bg-2');
                return (rollButton.length > 0 && rollButton[0].isVisible());
            };
            function roll() {
                document.getElementById('process-status').innerHTML = 'Roll triggered';
                rollButton[0].click();
                setTimeout(findCountdownOrRollButton, helpers.randomMs(2000, 3000));
            }
            function isPromotionTagVisible() {
                let pTag = document.querySelector('p');
                if (pTag) {
                    promotionTag = pTag;
                    return true;
                }
                return false;
            };
            function findPromotionTag() {
                if( isPromotionTagVisible() ) {
                    processRunDetails();
                } else {
                    setTimeout(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 == K.CF.PromoStatus.ACCEPTED) {
                        result.nextRoll = helpers.addMinutes(new Date(-20), "0");
                    }
                }
                shared.closeWindow(result);
            };
            function readCountdown() {
                let minsElement = document.querySelector('.timeout-container .minutes .digits');
                let mins = "0";
                if (minsElement) {
                    mins = minsElement.innerHTML;
                }
                if (mins) {
                    return helpers.addMinutes(new Date(), mins.toString());
                } else {
                    return null;
                }
            };
            function readClaimed() {
                let claimed = 0;
                try {
                    claimed = document.querySelector('.result').innerHTML;
                    claimed = claimed.trim();
                    claimed = claimed.slice(claimed.lastIndexOf(" ") + 1);
                } catch(err) { }
                return claimed;
            };
            function readRolledNumber() {
                let number = 0;
                try {
                    number = [...document.querySelectorAll('.lucky-number')].map(x => x.innerText).join('');
                    number = parseInt(number);
                } catch(err) { }
                return number;
            };
            function readBalance() {
                let balance = "";
                try {
                    balance = document.querySelector('.navbar-coins.bg-1 a').innerText;
                } catch(err) { }
                return balance;
            };
            function readPromoStatus() {
                let promoStatus = K.CF.PromoStatus.UNKNOWNERROR;
                try {
                    if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeAccepted) > 0) {
                        return K.CF.PromoStatus.ACCEPTED;
                    } else if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeUsed) > 0) {
                        return K.CF.PromoStatus.USEDBEFORE;
                    } else if(promotionTag.innerHTML.indexOf(localeConfig.stringSearches.promoCodeExpired) > 0) {
                        return K.CF.PromoStatus.EXPIRED;
                    } else if(localeConfig.stringSearches.promoCodeInvalid.findIndex(x => promotionTag.innerHTML.indexOf(x) > -1) == -1) {
                        return K.CF.PromoStatus.INVALID;
                    }
                } catch ( err ) { }
                return promoStatus;
            };
            function validatePromoString() {

            };
            function readPromoCode() {
                var urlSplit = window.location.href.split('/');
                return urlSplit[urlSplit.length - 1];
            };
            function displayStatusUi() {
                let wrapper = document.createElement('div');
                wrapper.innerHTML = '<div class="withdraw-button bg-2" style="top:30%; z-index:1500;" href="#">⚙️ <span id="process-status">Processing</span></div>';
                document.querySelector( 'body' ).prepend( wrapper.firstChild );
            };
            return {
                init: init
            };
        },
        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 countdownMinutes;
            let timeout = new Timeout(this.maxSeconds);
            let captcha = new HCaptchaWidget();

            function run() {
                setTimeout(findCountdownOrRollButton, helpers.randomMs(12000, 15000));
            };
            function findCountdownOrRollButton() {
                if ( isCountdownVisible() ) {
                    timeout.restart();
                    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 (shared.getConfig()['fb.activateRPBonus']) {
                        if (!document.getElementById('bonus_container_free_points')) {
                            document.querySelector('a.rewards_link').click();
                            activateBonus(0);
                        }
                    }

                    /* For 'Play without captcha accounts' */
                    if (!captcha.isUserFriendly) {
                        clickRoll()
                    } else {
                        captcha.isSolved().then(() => { clickRoll(); });
                    }
                } else {
                    setTimeout(findCountdownOrRollButton, helpers.randomMs(12000, 15000));
                }
            };
            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.isVisible()) {
                    return true;
                }
                return false;
            };
            function isRollButtonVisible() {
                return document.getElementById('free_play_form_button').isVisible();
            };
            function clickRoll() {
                try {
                    document.getElementById('free_play_form_button').click();
                    setTimeout(processRunDetails, helpers.randomMs(3000, 10000));
                } catch (err) {
                    shared.closeWithError(K.ErrorType.CLICK_ROLL_ERROR, err);
                }
            };
            function processRunDetails() {
                if (document.getElementById('winnings').isVisible()) {
                    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 (document.querySelector('.free_play_result_error').isVisible()) {
                    shared.closeWithError(K.ErrorType.ROLL_ERROR, document.querySelector('.free_play_result_error').innerHTML);
                    return;
                }

                if(document.getElementById('free_play_error').isVisible()) {
                    shared.closeWithError(K.ErrorType.ROLL_ERROR, document.querySelector('.free_play_error').innerHTML);
                    return;
                }

                if (document.getElementById('same_ip_error').isVisible()) {
                    shared.closeWithError(K.ErrorType.ROLL_ERROR, document.getElementById('same_ip_error').innerHTML);
                    return;
                }

                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')) {
                    let closeBtn = document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_box_close')
                    if (closeBtn.isVisible()) {
                        closeBtn.click();
                    }
                } else if (document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_success')) {
                    let closeBtn = document.querySelector('#reward_point_redeem_result_container_div .reward_point_redeem_result_box_close')
                    if (closeBtn.isVisible()) {
                        closeBtn.click();
                        document.querySelector('#free_play_link_li a').click();
                        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').click();
                    setTimeout(findCountdownOrRollButton, helpers.randomMs(10000, 12000));
                    return;
                }
                setTimeout(activateBonus.bind(null, i), 5000);
            };
            return {
                run: run
            };
        },
        createFPProcessor: function() {
            let timeout = new Timeout(this.maxSeconds);
            let captcha = new HCaptchaWidget();

            function init() {
                if(window.location.href.includes('ptc/view')) {
                    addDuration();
                    ptcSingle();
                } else if (window.location.href.includes('ptc')) {
                    ptcList();
                } else if (window.location.href.includes('account/login')) {
                    tryLogin();
                } else if (window.location.href.includes('page/user-admin')) {
                    window.location.href = 'https://faucetpay.io/ptc';
                }
                return;
            }

            function tryLogin() {
                let username = document.querySelector('input[name="user_name"');
                let password = document.querySelector('input[name="password"');
                let captcha = document.querySelector('.h-captcha > iframe');
                let btn = document.querySelector('button[type="submit"');
                if (username && password && btn && username.value != '' && password.value != '') {
                    //WAIT FOR CAPTCHA => THEN CLICK BTN
                    if ( captcha && captcha.getAttribute('data-hcaptcha-response').length > 0 ) {
                        btn.click();
                    } else {
                        setTimeout(tryLogin, helpers.randomMs(9000, 11000));
                    }
                } else {
                    shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
                }
            }

            function addDuration() {
                let duration = document.querySelector('#duration');
                if(duration && !isNaN(duration.innerText)) {
                    timeout.restart(parseInt(duration.innerText));
                } else {
                    setTimeout(addDuration, 10000);
                }
            }

            function ptcList() {
                let result;
                let runMsgDiv = document.querySelector('.alert.alert-info');
                if (runMsgDiv) {
                    let runMsg = runMsgDiv.innerHTML;
                    if (runMsg.includes('invalid captcha')) {
                        // Warn? Usually a error if ptcList is refreshed
                    } else if (runMsg.includes('Good job')) {
                        // "Good job! You have been credited with 0.00000001 BTC."
                        try {
                            let idx = runMsg.search(/\d/);
                            let claimed = parseFloat(runMsg.slice(idx, idx + 10));
                            result = shared.getResult();
                            result.claimed = (result.claimed ?? 0) + claimed;
                            result.nextRoll = helpers.addMs(new Date(), helpers.getRandomMs(shared.getConfig()['fp.hoursBetweenRuns'] * 60, 2)); // Wait hoursBetweenRuns +/- 1%
                            shared.updateWithoutClosing(result);
                        } catch { }
                    }
                }

                if ([...document.querySelectorAll('b')].filter(x => x.innerText.includes('Whoops!')).length > 0) {
                    result = shared.getResult();
                    result.nextRoll = helpers.addMs(new Date(), helpers.getRandomMs(shared.getConfig()['fp.hoursBetweenRuns'] * 60, 2)); // Wait hoursBetweenRuns +/- 2%
                    shared.closeWindow(result);
                    return;
                }

                let adButtons = [...document.querySelectorAll('button')].filter(x => x .innerHTML.includes('VISIT AD FOR'));

                if (adButtons.length > 0) {
                    adButtons[adButtons.length-1].click();
                    return;
                }

                setTimeout(ptcList, helpers.randomMs(10000, 12000));
            }

            function ptcSingle() {
                if(document.querySelector('input[name="complete"]').isVisible()) {
                    captcha.isSolved().then(() => { clickClaim(); });
                } else if (document.querySelector('body').innerText.toLowerCase().includes('ad does not exist')) {
                    window.location.href = 'https://faucetpay.io/ptc';
                } else {
                    setTimeout(ptcSingle, helpers.randomMs(5000, 6000));
                }
            }

            function clickClaim() {
                let input = document.querySelector('input[name="complete"]');
                input.focus();
                input.onclick = '';
                input.click();
                //force close with timeout in case it's still opened
                setTimeout(shared.closeWithError.bind(null, 'TIMEOUT', 'Timed out after clicking a CLAIM button.'), helpers.minToMs(shared.getConfig()['defaults.timeout']));
            }

            return {
                init: init
            };
        },
        createBagiKeranProcessor: function() {
            let timeout = new Timeout(this.maxSeconds);
            let captcha = new HCaptchaWidget({selector: '.h-captcha > iframe, .hcaptcha > iframe, .g-recaptcha > iframe'});
            let elements = {
                errorDivs: function() {
                    return document.querySelectorAll('.alert.alert-danger');
                },
                warningDivs: function() {
                    return document.querySelectorAll('.alert.alert-warning');
                },
                successDivs: function() {
                    return document.querySelectorAll('.alert.alert-success');
                },
                blockCloudflare: function() {
                    return document.querySelector('h1[data-translate="block_headline"]');
                },
                errorCloudflare: function() {
                    if (document.title != 'Please Wait... | Cloudflare' && !document.querySelector('#cf-hcaptcha-container')) {
                        return document.querySelector('#cf-error-details p');
                    } else {
                        return null;
                    }
                },
                openLoginModalButton: function() {
                    return document.getElementById('submit');
                },
                modal: function() {
                    return document.querySelector('#myModal.show');
                },
                addressInput: function() {
                    return document.querySelector('input[name="address"]');
                },
                submitButton: function() {
                    return document.querySelector('#myModal button[type="submit"]');
                },
                openClaimModalButton: function() {
                    return document.querySelector('form button[type="submit"]');
                },
                openWithdrawModal: function() {
                    return document.getElementById('submit');
                },
                linkWithdrawMinNotReached: function() {
                    return document.querySelector('a.btn.btn-primary.btn-block');
                }
            };

            function init() {
                if(shared.getCurrent().params.doSignOut) { // Unexpected behavior with BCH @ Bagi: seems it didn't login when the previous run was TRX/USDT so it saved the roll with that address and you couldn't withdraw:
                    //Before you can receive payments at FaucetPay.io with this address you must link it to an account. Create an account at FaucetPay.io and link your address, then come back and claim again
                    shared.devlog(`${window.location.href} signing out`);
                    shared.getCurrent().params.doSignOut = false;
                    shared.saveFlowControl();
                    window.location.href = (new URL('signout.php', window.location.href)).href;;
                    return;
                }
                if (window.location.href.includes('captha.php')) {
                    setTimeout(runCaptchaPage, helpers.randomMs(1000, 2000));
                    return;
                } else if (window.location.href.includes('withdraw.php')) {
                    setTimeout(runWithdraw, helpers.randomMs(1000, 2000));
                    return;
                } else {
                    setTimeout(run, helpers.randomMs(1000, 2000));
                    return;
                }
            }

            function run() {
                readAlerts();
                processIndex();
            };

            function runCaptchaPage() {
                readAlerts();
                shared.clearRetries();
                processCaptchaPage();
            }

            function runWithdraw() {
                readAlerts();
                shared.clearRetries();
                processWithdraw();
            }

            function readAlerts() {
                let elm;

                elements.warningDivs().forEach(function (elem) {
                    if (elem && elem.innerText.includes('already claimed')) { // "You have already claimed in the last 60 minutes.<br>You can claim again in 59 minutes.<br>"
                        let result = {};
                        try {
                            let mins = elem.innerText.split('\n')[1].replace(/\D/g, '');
                            result.nextRoll = helpers.addMinutes(new Date(), mins);
                        } catch {
                            result.nextRoll = helpers.addMinutes(new Date(), "60");
                        }

                        if (shared.getCurrent().params.doWithdraw) {
                            shared.updateWithoutClosing(result);
                            window.location.href = (new URL('withdraw.php', window.location.href)).href;
                        } else {
                            shared.closeWindow(result);
                        }
                        return;
                    }
                });

                elements.successDivs().forEach(function (elem) {
                    if (elem) {
                        if (elem.innerText.includes('claimed successfully')) { // "You've claimed successfully 2 Satoshi BTC." ...
                            let result = {};
                            result.nextRoll = helpers.addMinutes(new Date(), "60");
                            result.claimed = 0;

                            try {
                                let val = elem.innerText.split('\n')[0].replace(/\D/g, '');
                                if (typeof val == 'string') {
                                    val = +val;
                                }
                                if (Number.isInteger(val)) {
                                    val = val / 100000000;
                                }

                                result.claimed = val;
                            } catch { }

                            try {
                                let mins = elem.innerText.split('\n')[1].replace(/\D/g, '');
                                result.nextRoll = helpers.addMinutes(new Date(), mins);
                            } catch { }

                            if (shared.getCurrent().params.doWithdraw) {
                                shared.updateWithoutClosing(result);
                                let link = elem.querySelector('a');
                                if (link && link.innerText.includes('withdraw')) {
                                    link.click();
                                    return;
                                } else {
                                    window.location.href = (new URL('withdraw.php', window.location.href)).href;;
                                }
                            }
                            shared.closeWindow(result);
                            return;
                        } else if (elem.innerText.includes('was sent to')) { //2 satoshi was sent to <a target="_blank" href="https://faucetpay.io/page/user-admin">your account at FaucetPay.io</a>
                            let result = {};
                            result.withdrawnAmount = 0;
                            let val = elem.innerHTML.split(' ')[0];
                            if (typeof val == 'string') {
                                val = +val;
                            }
                            if (Number.isInteger(val)) {
                                val = val / 100000000;
                            }

                            result.withdrawnAmount = val;
                            shared.closeWindow(result);
                            return;
                        }
                    }
                });

                elm = elements.blockCloudflare();
                if (elm) {
                    // "Sorry, you have been blocked"
                    shared.closeWithError(K.ErrorType.IP_BAN, document.title + ' | ' + elm.innerText);
                    return;
                }

                elm = elements.errorCloudflare();
                if (elm) {
                    // "Access denied | bagi.co.in used Cloudflare to restrict access" @document.title
                    shared.closeWithError(K.ErrorType.IP_RESTRICTED, document.title + ' | ' + elm.innerText);
                    return;
                }

                elements.errorDivs().forEach(function (elem) {
                    if (elem) {
                        if (elem.innerText.toLowerCase().includes('vpn/proxy/tor')) { // "VPN/Proxy/Tor is not allowed on this faucet." ...
                            shared.closeWithError(K.ErrorType.IP_ERROR, elem.innerText);
                            return;
                        } else if (elem.innerText.toLowerCase().includes('look valid')) { // The Bitcoin Address doesn't look valid
                            //invalid address
                            shared.closeWithError(K.ErrorType.ADDRESS_ERROR, elem.innerText);
                            return;
                        } else if (elem.innerText.toLowerCase().includes('login not valid')) { // Login Not Valid, Please reLogin
                            // TODO: FIX => ITS NOT WORKING BECAUSE B/K REALOADS ITSELF TOO MANY TIMES
                            // FORCE 1 RETRY as sometimes it might work
                            if(shared.isRetrying()) {
                                shared.devlog(`${window.location.href} login retry failed`);
                                shared.closeWithError(K.ErrorType.LOGIN_ERROR, elem.innerText);
                            } else {
                                shared.devlog(`${window.location.href} will retry to login`);
                            }
                            return;
                        } else if (elem.innerText.toLowerCase().includes('claim not valid')) { // Claim not Valid, Please reClaim. Try again
                            // FORCE 1 RETRY
                            if(shared.isRetrying()) {
                                shared.devlog(`${window.location.href} claim retry failed`);
                                shared.closeWithError(K.ErrorType.CLAIM_ERROR, elem.innerText);
                            } else {
                                shared.devlog(`${window.location.href} will retry to claim`);
                            }
                            return;
                        } else {
                            // Unknown issue
                            shared.closeWithError(K.ErrorType.ERROR, elem.innerText);
                            return;
                        }
                    }
                });

                elm = elements.linkWithdrawMinNotReached();
                if (elm) {
                    if(elm.innerText.toLowerCase().includes('minimum withdraw')) { // Minimum Withdraw is ...
                        shared.closeWithError(K.ErrorType.MIN_WITHDRAW_ERROR, elm.innerText);
                        return;
                    }
                }
            }

            function processIndex() {
                if (elements.modal() && elements.addressInput() && elements.submitButton()) { // 2. Fill address & click Login
                    if(elements.addressInput().value != '') {
                        shared.devlog('Clicking LOGIN');
                        elements.submitButton().click(); // shoud redirect but check for timeout
                    } else {
                        elements.addressInput().value = shared.getCurrent().params.address;
                    }
                    setTimeout(run, helpers.randomMs(2000, 4000));
                    return;
                }

                if (elements.openLoginModalButton()) { // 1. Click the Get Started Button
                    elements.openLoginModalButton().click();
                    timeout.restart();
                    setTimeout(processIndex, helpers.randomMs(1000, 3000));
                    return;
                }

                if (elements.openClaimModalButton()) { // Claim Bitcoin Button
                    elements.openClaimModalButton().click();
                    timeout.restart();
                    setTimeout(processIndex, helpers.randomMs(2000, 4000));
                    return;
                }

                setTimeout(run, helpers.randomMs(2000, 4000));
            };

            function processCaptchaPage() {
                if(elements.modal()) {
                    captcha.isSolved().then(() => {
                        if(elements.submitButton()) {
                            elements.submitButton().click(); // should redirect
                            return;
                        }
                    });
                    return;
                }

                if (elements.openLoginModalButton()) { // 1. Click the Claim Button to open the modal w/the hCaptcha
                    elements.openLoginModalButton().click();
                    timeout.restart();
                    setTimeout(processCaptchaPage, helpers.randomMs(1000, 3000));
                    return;
                }
                setTimeout(runCaptchaPage, helpers.randomMs(2000, 4000));
            }

            function processWithdraw() {
                if(elements.modal()) {
                    captcha.isSolved().then(() => {
                        if(elements.submitButton()) {
                            elements.submitButton().click(); // should redirect
                            return;
                        }
                    });
                    return;
                }

                if (elements.openWithdrawModal()) { // 1. Click the Withdraw to FaucetPay submit button to open the modal w/the hCaptcha
                    elements.openWithdrawModal().click();
                    timeout.restart();
                    setTimeout(processWithdraw, helpers.randomMs(2000, 4000));
                    return;
                }
                setTimeout(runWithdraw, helpers.randomMs(2000, 4000));
            }

            return {
                init: init
            };
        },
        createOkFaucetProcessor: function() {
            let timeout = new Timeout(this.maxSeconds);
            let countdownMinutes;
            let captcha = new HCaptchaWidget();

            let selectElement = {
                addressInput: function() {
                    return document.querySelector('input[type="text"]');
                },
                rollButton: function() {
                    return document.querySelector('input[type="submit"');
                },
                countdown: function() { // "You have to wait\n60 minutes"
                    let successDivs = document.querySelectorAll(".alert.alert-success");
                    if(successDivs.length == 1 && successDivs[0].isVisible()) {
                        return parseInt(successDivs[0].innerText.replace(/\D/g, ''));
                    }
                    return null;
                },
                rolledNumber: function() {
                    let successDivs = document.querySelectorAll(".alert.alert-success");
                    if(successDivs && successDivs.length > 1 && successDivs[0].isVisible()) {
                        return parseInt(successDivs[0].innerText);
                    } else {
                        return null;
                    }
                },
                claimedAmount: function() {
                    let successDivs = document.querySelectorAll(".alert.alert-success");
                    if(successDivs && successDivs.length > 1 && successDivs[0].isVisible()) {
                        let val = parseInt(successDivs[1].innerText.replace(/\D/g, ''));
                        if (Number.isInteger(val)) {
                            val = val / 100000000;
                        }

                        return val;
                    } else {
                        return null;
                    }
                },
                error: function () {
                    let errorDiv = document.querySelector(".alert.alert-danger");
                    if(errorDiv) {
                        if (errorDiv.innerText.toLowerCase().includes('not have sufficient funds')) {
                            shared.closeWithError(K.ErrorType.NO_FUNDS, errorDiv.innerText);
                        } else {
                            shared.closeWithError(K.ErrorType.ERROR, errorDiv.innerText);
                        }
                    } else {
                        return null;
                    }
                }
            };

            function init() {
                run();
            }

            function run() {
                try {
                    setTimeout(findResultCountdownOrRollButton, helpers.randomMs(12000, 15000));
                } catch (err) {
                    shared.closeWithErrors(K.ErrorType.ERROR, err);
                }
            };
            function findResultCountdownOrRollButton() {
                selectElement.error();

                if ( selectElement.countdown() ) {
                    shared.devlog(`Ok: countdown found`);
                    timeout.restart();
                    let result = {};
                    result.nextRoll = helpers.addMinutes(new Date(), selectElement.countdown().toString());

                    shared.closeWindow(result);
                    return;
                }

                if ( isRollButtonVisible() ) {
                    startRoll();
                    return;
                }

                if (selectElement.claimedAmount()) {
                    processRunDetails();
                    return;
                }
                setTimeout(findResultCountdownOrRollButton, helpers.randomMs(10000, 12000));
            };
            function startRoll() {
                shared.devlog(`Ok: rollbutton found`);
                let addressInput = selectElement.addressInput();
                if (addressInput && addressInput.value != shared.getCurrent().params.address) {
                    addressInput.value = shared.getCurrent().params.address;
                    shared.devlog(`Ok: address filled`);
                }

                captcha.isSolved().then(() => { clickRoll(); });
            };
            function isHCaptchaVisible() {
                let hCaptchaFrame = document.querySelector('.h-captcha > iframe');
                if (hCaptchaFrame && hCaptchaFrame.isVisible()) {
                    return true;
                }
                return false;
            };
            function isRollButtonVisible() {
                let rollButton = selectElement.rollButton();
                return rollButton && rollButton.isVisible();
            };
            function clickRoll() {
                try {
                    shared.devlog('Clicking roll button');
                    selectElement.rollButton().click();
                    return;
                } catch (err) {
                    shared.closeWithError(K.ErrorType.CLICK_ROLL_ERROR, err);
                }
            };
            function processRunDetails() {
                shared.devlog(`Ok: claimedAmount found`);
                let claimedAmount = selectElement.claimedAmount();
                let rolledNumber = selectElement.rolledNumber();

                if (claimedAmount && rolledNumber) {
                    let result = {};
                    result.claimed = claimedAmount;
                    result.rolledNumber = rolledNumber;
                    result.nextRoll = helpers.addMinutes(new Date(), "60");

                    shared.closeWindow(result);
                    return;
                }

                setTimeout(processRunDetails, helpers.randomMs(5000, 6000));
            };

            return {
                init: init
            };

        },
        createBigBtcProcessor: function() {
            let timeout = new Timeout(this.maxSeconds);
            let countdownMinutes;
            let captcha = new HCaptchaWidget();
            let selectElement = {
                loadingDiv: function() {
                    let loading = document.querySelector('#loading');
                    if (loading && loading.isVisible()) {
                        return true;
                    } else {
                        return false;
                    }
                },
                addressInput: function() {
                    return document.querySelector('#login input[name="address"]');
                },
                loginButton: function() {
                    return document.querySelector('#login input[type="submit"]');
                },
                claimButton: function() {
                    return document.getElementById('claimbutn');
                },
                countdown: function() { // "You have to wait\n60 minutes"
                    let cd = document.getElementById('countdown');
                    if(cd && cd.isVisible()) {
                        return parseInt(cd.innerText);
                    }
                    return null;
                },
                claimedAmount: function() {
                    let elm = document.querySelector('.alert.alert-success.pulse'); //"Yuppie! You won 2 satoshi!"
                    if(elm && elm.isVisible()) {
                        let val = parseInt(elm.innerText.replace(/\D/g, ''));
                        if (Number.isInteger(val)) {
                            val = val / 100000000;
                        }

                        return val;
                    } else {
                        return null;
                    }
                },
                balance: function() {
                    let elm = document.querySelector('a b');
                    if (elm && elm.isVisible()) {
                        let val = parseInt(elm.innerText.replace(',', ''));
                        if (Number.isInteger(val)) {
                            val = val / 100000000;
                        }

                        return val;
                    } else {
                        return null;
                    }
                },
                error: function () {
                    return null;
                }
            };

            function init() {
                if (window.location.href.includes('/faucet')) {
                    setTimeout(runFaucet, helpers.randomMs(12000, 14000));
                    return;
                } else {
                    setTimeout(run, helpers.randomMs(3000, 5000));
                    return;
                }
            }

            function run() {
                try {
                    setTimeout(waitIfLoading, helpers.randomMs(12000, 15000));
                } catch (err) {
                    shared.closeWithErrors(K.ErrorType.ERROR, err);
                }
            };
            function doLogin() {
                let address = selectElement.addressInput();
                if(address && address.value != shared.getCurrent().params.address) {
                    address.value = shared.getCurrent().params.address;
                } else {
                    selectElement.loginButton().click();
                    return;
                }
                setTimeout( doLogin , helpers.randomMs(1000, 2000));
            };
            function waitIfLoading() {
                if ( !selectElement.loadingDiv() ) {
                    shared.devlog(`BigBtc: doing log in`);
                    doLogin();
                    return;
                } else {
                    shared.devlog(`BigBtc: waiting for login form`);
                }

                setTimeout(waitIfLoading, helpers.randomMs(5000, 7000));
            };
            function getDelayedNext() {
                let cfg = shared.getConfig()['bigbtc.postponeMinutes'];
                let delay = 60;
                switch(cfg) {
                    case "-1":
                        delay = shared.getConfig()['defaults.postponeMinutes'];
                        break;
                    case "0":
                        delay = helpers.randomInt(12, 115);
                        break;
                    default:
                        delay = cfg;
                        break;
                }
                let millisecondsDelay = helpers.getRandomMs(delay, 5);
                shared.devlog(`BigBt Delayed: ${delay}`);
                return helpers.addMs(new Date(), millisecondsDelay);
            }
            function runFaucet() {
                let claimedAmount = selectElement.claimedAmount();
                if(claimedAmount) {
                    processRunDetails();
                    return;
                } else if (selectElement.countdown()) {
                    // need to wait
                    let result = {};
                    result.nextRoll = getDelayedNext();

                    shared.closeWindow(result);
                } else {
                    shared.devlog(`BigBtc: waiting for captcha`);
                    captcha.isSolved().then(() => { clickClaim(); });
                }
            }
            function clickClaim() {
                try {
                    shared.devlog('Clicking roll button');
                    selectElement.claimButton().click();
                    return;
                } catch (err) {
                    shared.closeWithError(K.ErrorType.CLICK_ROLL_ERROR, err);
                }
            };
            function processRunDetails() {
                shared.devlog(`BigBtc: processing results`);
                let claimedAmount = selectElement.claimedAmount();
                let balance = selectElement.balance();
                let countdown = selectElement.countdown();

                if (claimedAmount && balance) {
                    let result = {};
                    result.claimed = claimedAmount;
                    result.balance = balance;
                    result.nextRoll = getDelayedNext();

                    shared.closeWindow(result);
                    return;
                }

                setTimeout(processRunDetails, helpers.randomMs(5000, 6000));
            };

            return {
                init: init
            };
        },
        createBestChangeProcessor: function() {
            let timeout = new Timeout(this.maxSeconds);
            let countdownMinutes;
            let captcha = new HCaptchaWidget({selector: '.hcaptcha > iframe'});
            let elements = {
                captcha: function() {
                    return document.querySelector('.hcaptcha > iframe');
                },
                container: function() {
                    return document.querySelector('#info_bonus');
                },
                containerOpener: function() {
                    return document.querySelector('#tab_bonus a');
                },
                addressInput: function() {
                    return document.querySelector('#bonus_purse');
                },
                claimButton: function() {
                    return document.querySelector('#bonus_button');
                },
                countdown: function() { // Time left: mm:ss
                    let elm = document.querySelector('#bonus_button');
                    try {
                        if (elm.value) {
                            let timeLeft = elm.value.split(':');
                            if (timeLeft.length > 1) {
                                return parseInt(timeLeft[1]);
                            }
                        }
                    } catch (err) {
                        return null;
                    }
                },
                claimedAmount: function() {
                    let elm = document.querySelector("#bonus_status b");
                    try {
                        let sats = elm.innerText.replace(/\D/g, '');
                        return sats / 100000000;
                    } catch (err) {
                        return null;
                    }
                },
                balance: function() {
                    let elm = document.querySelector("#faucet_unpaid_balance b");
                    try {
                        let sats = elm.innerText.replace(/\D/g, '');
                        return sats / 100000000;
                    } catch (err) {
                        return null;
                    }
                }
            };

            function init() {
                run();
            }

            function run() {
                try {
                    if (!elements.container().isUserFriendly()) {
                        let co = elements.containerOpener();
                        if(co.isUserFriendly()) {
                            co.onclick = co.onmousedown;
                            co.click();
                        }
                    }
                    setTimeout(findCountdownOrRoll, helpers.randomMs(4000, 5000));
                } catch (err) {
                    shared.closeWithErrors(K.ErrorType.ERROR, err);
                }
            };
            function findCountdownOrRoll() {
                let countdown = elements.countdown();
                if(countdown) {
                    let result = { };
                    result.nextRoll = helpers.addMinutes(new Date(), countdown.toString());

                    shared.closeWindow(result);
                    return;
                }

                let ai = elements.addressInput();

                if (ai.isUserFriendly()) {
                    if (ai.value != shared.getCurrent().params.address) {
                        ai.value = shared.getCurrent().params.address;
                    }
                    captcha.isSolved().then(() => { clickClaim(); });
                    return;
                }

                setTimeout(findCountdownOrRoll, helpers.randomMs(10000, 12000));
            };

            function clickClaim() {
                try {
                    shared.devlog('Clicking claim button');
                    let btn = elements.claimButton();
                    if(btn.isUserFriendly()) {
                        btn.click();
                        setTimeout(processRunDetails, helpers.randomMs(4000, 8000));
                    }
                    return;
                } catch (err) {
                    shared.closeWithError(K.ErrorType.CLICK_ROLL_ERROR, err);
                }
            };

            function processRunDetails() {
                let claimedAmount = elements.claimedAmount();
                let balance = elements.balance();

                if (claimedAmount && balance) {
                    let result = {};
                    result.claimed = claimedAmount;
                    result.balance = balance;
                    result.nextRoll = helpers.addMinutes(new Date(), "60");

                    shared.closeWindow(result);
                    return;
                }

                setTimeout(processRunDetails, helpers.randomMs(5000, 6000));
            };

            return {
                init: init
            };
        },
    };

    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()) {
            shared.devlog(`${window.location.href} dismissed`);
            return;
        }
        shared.devlog(`${window.location.href} accepted`);

        let typeFromManager = shared.getCurrent().type;

        timer = new Timer(false, 20, typeFromManager);
        switch( typeFromManager ) {
            case K.WebType.STORMGAIN:
                SiteProcessor = objectGenerator.createSGProcessor();
                setTimeout(SiteProcessor.run, helpers.randomMs(10000, 20000));
                break;
            case K.WebType.CRYPTOSFAUCETS:
                SiteProcessor = objectGenerator.createCFProcessor();
                setTimeout(SiteProcessor.init, helpers.randomMs(1000, 3000));
                break;
            case K.WebType.FREEBITCOIN:
                SiteProcessor = objectGenerator.createFBProcessor();
                setTimeout(SiteProcessor.run, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.FREELITECOIN:
                SiteProcessor = new FreeLitecoin();
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.FREEETHEREUMIO:
                SiteProcessor = new FreeEthereumIo();
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.BAGIKERAN:
                SiteProcessor = objectGenerator.createBagiKeranProcessor();
                setTimeout(SiteProcessor.init, helpers.randomMs(2000, 4000));
                break;
            case K.WebType.FAUCETPAY:
                SiteProcessor = objectGenerator.createFPProcessor();
                setTimeout(SiteProcessor.init, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.OKFAUCET:
                SiteProcessor = objectGenerator.createOkFaucetProcessor();
                setTimeout(SiteProcessor.init, helpers.randomMs(4000, 6000));
                break;
            case K.WebType.BIGBTC:
                SiteProcessor = objectGenerator.createBigBtcProcessor();
                setTimeout(SiteProcessor.init, helpers.randomMs(2000, 4000));
                break;
            case K.WebType.BESTCHANGE:
                SiteProcessor = objectGenerator.createBestChangeProcessor();
                setTimeout(SiteProcessor.init, helpers.randomMs(4000, 6000));
                break;
            case K.WebType.KINGBIZ:
                SiteProcessor = new KingBiz();
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.OURBITCOIN:
                SiteProcessor = new OurBitco();
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.FREEDOGEIO:
                SiteProcessor = new FreeDogeIo();
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.BETFURYBOX:
                SiteProcessor = new BetFuryBox(helpers.getEnumText(K.CMC, shared.getCurrent().params.cmc).toLowerCase());
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            case K.WebType.DUTCHYROLL:
                SiteProcessor = new DutchyRoll();
                setTimeout(() => { SiteProcessor.init() }, helpers.randomMs(2000, 5000));
                break;
            default:
                break;
        }
    }

    // CLASSES (WIP)
    class Timeout {
        constructor(seconds) {
            this.wait = seconds || shared.getConfig()['defaults.timeout'] * 60;
            this.startedAt;
            this.interval;
            this.cb = (() => { shared.closeWithError(K.ErrorType.TIMEOUT, '') });
            this.restart();
        }

        get elapsed() {
            return Date.now() - this.startedAt;
        }

        restart(addSeconds = false) {
            if(this.interval) {
                clearTimeout(this.interval);
            }
            this.startedAt = Date.now();
            if(addSeconds) {
                this.wait = this.wait + addSeconds;
            }
            this.interval = setTimeout( () => { this.cb() }, this.wait * 1000);
        }
    }

    class Timer {
        constructor(isManager, delaySeconds, webType) {
            if(!useTimer || (webType && !Timer.webTypes().includes(webType))) {
                return;
            }
            this.delay = delaySeconds * 1000;

            if(!isManager) {
                this.tick();
                this.interval = setInterval(
                    () => { this.tick() }, this.delay);
            }
        }

        static webTypes() { return [K.WebType.FREELITECOIN, K.WebType.FREEETHEREUMIO, K.WebType.BAGIKERAN, K.WebType.BIGBTC] };

        startCheck(webType) {
            if(!useTimer || (webType && !Timer.webTypes().includes(webType))) {
                return;
            }
            persistence.save('lastAccess', Date.now());
            this.interval = setInterval(
                () => { this.isAlive() }, this.delay);
        }

        stopCheck() {
            if(!useTimer) {
                return;
            }
            clearInterval(timer.interval);
        }

        tick() {
            if(!useTimer) {
                return;
            }
            persistence.save('lastAccess', Date.now());
        }

        isAlive() {
            if(!useTimer) {
                return;
            }
            let now = Date.now();
            let newAccess = persistence.load('lastAccess');
            if(newAccess && (now - newAccess > this.delay)) {
                //Close working tab and force restart
                shared.devlog(`Timer is closing the working tab`);
                shared.addError(K.ErrorType.FORCE_CLOSED, 'Site was unresponsive or redirected');
                manager.closeWorkingTab();
            }
        }
    }


    const wait = ms => new Promise(resolve => setTimeout(resolve, ms || 3000));

    class Parsers {
        static innerText(elm) {
            try {
                return elm.innerText;
            } catch (err) { }
        }
        static trimNaNs(elm) {
            try {
                return elm.innerText.replace(/[^\d.-]/g, '');
            } catch (err) { }
        }
        static innerTextIntToFloat(elm) {
            try {
                let sats = elm.innerText.replace(/\D/g, '');
                return sats / 100000000;
            } catch (err) { }
        }
        static innerTextJoinedToInt(elm) {
            try {
                return parseInt([... elm].map( x => x.innerText).join(''));
            } catch (err) { }
        }
        static stormGainCountdown(elm) {
            try {
                let timeLeft = elm.innerText.split(':');
                if (timeLeft[0] == 'Synchronizing') {
                    //TODO: should retry to load the value
                }

                if(timeLeft.length === 3) {
                    return parseInt(timeLeft[0]) * 60 + parseInt(timeLeft[1]);
                }
            } catch (err) {
                return null;
            }
        }
        static kingBizCountdown(elm) {
            try {
                let itms = elm.querySelectorAll('.flip-clock-active .up');
                if (itms.length > 1 && itms[0].isVisible() && itms[1].isVisible()) {
                    return parseInt([itms[0].innerText, itms[1].innerText].join(''));
                }
            } catch (err) {
                return null;
            }
        }
        static bestChangeCountdown(elm) {
            try {
                if (elm.value) {
                    let timeLeft = elm.value.split(':');
                    if (timeLeft.length > 1) {
                        return parseInt(timeLeft[1]);
                    }
                }
            } catch (err) {
                return null;
            }
        }
        static freeEthereumIoClaimed(elm) {
            try {
                let line = elm.innerHTML;
                let idx = line.search(/0\./);
                return parseFloat(line.slice(idx, idx + 10));
            } catch (err) { }
        }
        static betFuryBoxClaimed(elm) {
            try {
                let val = elm.parentElement.parentElement.querySelector('.freebox__capacity-value').innerText.split(' ')[0].split('/')[1];

                if (val.length < 10) {
                    val = val/100000000;
                }
                return val;
            } catch (err) {
                return null;
            }
        }
        static dutchysClaimed(elm) { //.innerText = "You Won :101  DUTCHY + 20 XP"
            try {
                let splitted = elm.innerText.split('DUTCHY');
                return splitted[0].replace(/[^\d.-]/g, '');
            } catch (err) { shared.devlog(`@Parsers.dutchysClaimed, with element [${elm}] Error: ${err}`); }
        }
    }

    class CrawlerWidget {
        constructor(params) {
            if (!params || !params.selector) {
                throw new Error('CrawlerWidget requires a selector parameter');
            }
            this.context = this.context || document;
            Object.assign(this, params);
        }



        get isUserFriendly() {
            this.element = this.element || this.context.isUserFriendly(this.selector);
            return this.element;
        }
    }

    class ReadableWidget extends CrawlerWidget {
        constructor(params) {
            if (params && !params.parser) {
                params.parser = Parsers.innerText; //default parser
            }
            super(params);
        }

        get value() {
            if (this.isUserFriendly) {
                return this.parser(this.element);
            } else {
                throw new Error(`ReadableWidget (selector: '${this.selector}') cannot be read with the assigned parser`);
            }
        }
    }

    class ButtonWidget extends CrawlerWidget {
        click() {
            if (this.isUserFriendly) {
                this.element.click();
                return Promise.resolve(true);
            } else {
                throw new Error(`ButtonWidget (selector: '${this.selector}') cannot be clicked`);
            }
        }
    }

    class CountdownWidget extends CrawlerWidget {
        constructor(params) {
            if (params && !params.parser) {
                params.parser = Parsers.innerText; //default parser
            }
            super(params);
        }

        get timeLeft() {
            if (this.isUserFriendly) {
                return this.parser(this.element);
            } else {
                throw new Error(`CountdownWidget (selector: '${this.selector}') cannot be read`);
            }
        }
    }

    class CaptchaWidget extends CrawlerWidget {
        constructor(params) {
            super(params);
        }

        solve() { return true; }

        async isSolved() { return false; }
    }

    class HCaptchaWidget extends CaptchaWidget {
        constructor(params) {
            let defaultParams = {
                selector: '.h-captcha > iframe',
                waitMs: [1000, 5000],
                timeoutMs: 4 * 60 * 1000
            };
            for (let p in params) {
                defaultParams[p] = params[p];
            }
            super(defaultParams);
        }

        async isSolved() {
            return wait().then( () => {
                if (this.isUserFriendly && this.element.hasAttribute('data-hcaptcha-response') && this.element.getAttribute('data-hcaptcha-response').length > 0) {
                    return Promise.resolve(true);
                }
                return this.isSolved();
            });
        }
    }

    class OBCaptchaWidget extends CaptchaWidget {
        constructor() {
            let defaultParams = {
                selector: 'img[src="https://ourbitco.in/home/captcha"]',
                waitMs: [1000, 5000],
                timeoutMs: 4 * 60 * 1000
            };
            super(defaultParams);
            this._img;
            this._segments = { qty: 5, width: 21, height: 27, spacing: 2, startX: 15, startY: 13 };
            this._characters = [];
        }

        static get charList() { return JSON.parse("{\"0\":[false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,true,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,true,true,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,true,false,true,true,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,true,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false],\"1\":[false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false],\"2\":[false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false],\"3\":[false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false],\"4\":[false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true],\"5\":[false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,true,true,false,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false],\"6\":[false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,false,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false],\"7\":[false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false],\"8\":[false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,false,false,false,false,false,false],\"9\":[false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,false,true,true,true,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false],\"a\":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,false,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,true,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,false,false,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,false,false,false,true,true,true,false,false,false,false,false,true,true,true,true,true,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,true,true,true,false],\"b\":[false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,true,true,true,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false],\"c\":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,false,false,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,false,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false],\"d\":[false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,true,true,true,false],\"e\":[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,true,true,true,true,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,false,false,false,false,false,false],\"f\":[false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,false,false,false,false,false,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,false,false,false],\"g\":null,\"h\":null,\"i\":null,\"j\":null,\"k\":null,\"l\":null,\"m\":null,\"n\":null,\"o\":null,\"p\":null,\"q\":null,\"r\":null,\"s\":null,\"t\":null,\"u\":null,\"v\":null,\"w\":null,\"x\":null,\"y\":null,\"z\":null}"); }

        async isReady() {
            return wait().then( () => {
                this._img = document.querySelector(this.selector);
                if(this._img && this._img.complete) {
                    return Promise.resolve(true);
                }
                return this.isReady();
            });
        }

        async isSolved() {
            return this.isReady()
                .then( () => this.solve())
                .then( (solution) => {
                document.querySelector('#captcha-letra').value = solution;
                return Promise.resolve(true); })
                .catch(err => { shared.devlog(err); })
        }

        async solve() {
            let solution = '';
            if(this._img.complete) {
                let drawer = this.getDrawer(this._img.width, this._img.height);
                let charactersColor = this.getCharactersColor(drawer.ctx, drawer.canvas, this._img);
                this.imgToBool(drawer, charactersColor);
                if(this._characters.length == 5) {
                    for(let c in this._characters) {
                        let found = Object.entries(OBCaptchaWidget.charList).find( ([key, val]) => this.areEqual(this._characters[c], val))
                        if (found) {
                            solution += found[0];
                        }
                    }
                }
            }
            return (solution.length == this._segments.qty) ? Promise.resolve(solution) : Promise.reject(solution);
        }

        areEqual(a, b) {
            return JSON.stringify(a) == JSON.stringify(b);
        }

        getDrawer(width, height) {
            let canvas = document.createElement('canvas');
            canvas.setAttribute('width', width);
            canvas.setAttribute('height', height);
            let ctx = canvas.getContext('2d');
            return {
                canvas: canvas,
                ctx: ctx
            };
        }

        getCharactersColor(ctx, cvs, img) {
            ctx.drawImage(img, 0, 0);
            const imageData = ctx.getImageData(0, cvs.height/2, cvs.width, 1);
            const data = imageData.data;
            let minColor = 9999;
            for (let i = 0; i < data.length; i += 4) {
                let val = data[i] + data[i+1] + data[i+2];
                if (val < minColor) {
                    minColor = val;
                }
            }
            return minColor;
        }

        imgToBool(drawer, charactersColor) {
            for(let i = 0; i < this._segments.qty; i++) {
                let character = [];
                let x = this._segments.startX + i * this._segments.spacing + i * this._segments.width;
                let segmentImageData = drawer.ctx.getImageData(x, this._segments.startY, this._segments.width, this._segments.height);
                const data = segmentImageData.data;
                for (let i = 0; i < data.length; i += 4) {
                    let val = data[i] + data[i+1] + data[i+2];
                    character.push(val == charactersColor ? true : false);
                }
                this._characters.push(character);
            }
        }
    }

    class Faucet {
        constructor(elements, actions = {}) {
            this._timeout = new Timeout(this.maxSeconds);
            this._elements = elements;
            this._actions = {
                preRun: false,
                preRoll: false,
                altValidation: false,
                readClaimed: true,
                readBalance: true,
                readTimeLeft: true,
                readRolledNumber: false
            }
            this._actions = { ...this._actions, ...actions };
            this._params = shared.getCurrent().params || {};
            this._result = shared.getResult() || {};
        }

        init() {
            throw new Error('Init not implemented!');
        }

        login() {
            throw new Error('Login not implemented!'); //return NEED_TO_LOGIN
        }

        async run() {
            if (this._actions.preRun) {
                await this.preRun();
            }
            this.detectAction().then( (resolve) => {
                shared.devlog(`Action detected: ${resolve.action}`);
                switch(resolve.action) {
                    case 'doRoll':
                        if(this._actions.preRoll) {
                            this.preRoll();
                        }
                        this._elements.captcha.isSolved().then(() => { this.clickRoll() });
                        break;
                    case 'needToWait':
                        this.updateResult();
                        break;
                    default:
                        break;
                }
            });
        }

        async detectAction() {
            return wait().then( () => {
                if ( this.isCountdownVisible() ) {
                    return Promise.resolve({action: 'needToWait'});
                } else if ( this.isRollButtonVisible() ) {
                    return Promise.resolve({action: 'doRoll'});
                } else {
                    return this.detectAction();
                }
            });
        }

        preRoll() {
            throw new Error('PreRoll not implemented!');
        }

        preRun() {
            throw new Error('PreRun not implemented!');
        }

        altValidation() {
            throw new Error('AltValidation not implemented!');
        }

        isCountdownVisible() {
            return this._elements.countdownMinutes && this._elements.countdownMinutes.isUserFriendly;
        }

        isRollButtonVisible() {
            return this._elements.rollButton && this._elements.rollButton.isUserFriendly;
        }

        clickRoll() {
            try {
                shared.devlog('Clicking roll button');
                this._elements.rollButton.click();
                setTimeout(() => { this.validateRun() }, helpers.randomMs(10000, 12000));
            } catch (err) {
                shared.closeWithError(K.ErrorType.CLICK_ROLL_ERROR, err);
            }
        }

        async validateRun() {
            return wait().then( () => {
                if (this._elements.success.isUserFriendly) {
                    shared.devlog('Successful run');
                    return this.updateResult();
                } else if(this._actions.altValidation) {
                    if(this.altValidation()) {
                        shared.devlog('Alt validated');
                        return this.updateResult();
                    }
                }
                return wait(2000).then( () => { this.validateRun() });
            });
        }

        async updateResult() {
            if(this._actions.readClaimed) {
                this._result.claimed = this.readClaimed();
            }
            if(this._actions.readBalance) {
                this._result.balance = this.readBalance();
            }
            if(this._actions.readTimeLeft) {
                this._result.nextRoll = this.readNextRoll();
            }
            if(this._actions.readRolledNumber) {
                this._result.rolledNumber = this.readRolledNumber();
            }
            shared.devlog(`Result: ${JSON.stringify(this._result)}`);
            if (this._actions.updateWithoutClosing) {
                shared.updateWithoutClosing(this._result);
            } else {
                shared.closeWindow(this._result);
            }
            if (this._actions.postRun) {
                return this.postRun();
            } else {
                return true;
            }
        }

        readNextRoll() {
            //TODO: get nextRoll specific logic for the faucet: setup a default overridable value and an optional sleep timeframe
            //FaucetPay @ single: result.nextRoll = helpers.addMs(new Date(), helpers.getRandomMs(shared.getConfig()['fp.hoursBetweenRuns'] * 60, 2)); // Wait hoursBetweenRuns +/- 1%
            //this._result.nextRoll = helpers.addMs(new Date(), helpers.getRandomMs(32, 7)); // Wait hoursBetweenRuns +/- 2%
            try {
                if (this._elements.countdownMinutes) {
                    return helpers.addMinutes(new Date(), this._elements.countdownMinutes.timeLeft);
                }
            } catch (err) {
            }
            return helpers.addMinutes(new Date(), "60");
        }

        readRolledNumber() {
            let rolled = 0;
            try {
                if(this._elements.rolledNumber.isUserFriendly) {
                    rolled = this._elements.rolledNumber.value;
                }
            } catch (err) { shared.devlog(`@readRolledNumber: ${err}`); }
            return rolled;
        }

        readBalance() {
            let balance = 0;
            try {
                if(this._elements.balance.isUserFriendly) {
                    balance = this._elements.balance.value;
                }
            } catch (err) { shared.devlog(`@readBalance: ${err}`); }
            return balance;
        }

        readClaimed() {
            let claimed = this._result.claimed ?? 0;
            try {
                if(this._elements.claimed.isUserFriendly) {
                    claimed = +claimed + +this._elements.claimed.value;
                }
            } catch (err) { shared.devlog(`@readClaimed: ${err}`); }
            return claimed;
        }
    }

    class FreeEthereumIo extends Faucet {
        constructor() {
            let elements = {
                countdownMinutes: new CountdownWidget({selector: '#cislo1'}),
                rollButton: new ButtonWidget({selector: '#rollform button'}),
                balance: new ReadableWidget({selector: '#cryptovalue'}),
                claimed: new ReadableWidget({selector: '#info', parser: Parsers.freeEthereumIoClaimed}),
                rolledNumber: new ReadableWidget({selector: '#numberroll'}),
                captcha: new HCaptchaWidget(),
                success: new ReadableWidget({selector: '#info'})
            };
            let actions = {
                readRolledNumber: true
            };
            super(elements, actions);
        }

        init() {
            let url = new URL(window.location.href);
            if (url.pathname == '/free/') {
                this.run();
            } else if (url.pathname == '/') {
                shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
            }
        }
    }

    class FreeLitecoin extends Faucet {
        constructor() {
            let elements = {
                countdownMinutes: new CountdownWidget({selector: '#cislo1'}),
                rollButton: new ButtonWidget({selector: '#roll'}),
                balance: new ReadableWidget({selector: '#money'}),
                claimed: new ReadableWidget({selector: '#info', parser: Parsers.trimNaNs}),
                rolledNumber: new ReadableWidget({selector: '#numberroll'}),
                captcha: new HCaptchaWidget(),
                success: new ReadableWidget({selector: '#info'})
            };
            let actions = {
                readRolledNumber: true
            };
            super(elements, actions);
        }

        init() {
            let url = new URL(window.location.href);
            if (url.pathname == '/') {
                this.run();
            } else if (url.pathname.includes('/login')) {
                shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
            }
        }
    }

    class FreeDogeIo extends Faucet {
        constructor() {
            let elements = {
                countdownMinutes: new CountdownWidget({selector: '#cislo1'}),
                rollButton: new ButtonWidget({selector: '.btn.btn-success'}),
                balance: new ReadableWidget({selector: '#cryptovalue'}),
                claimed: new ReadableWidget({selector: '#info', parser: Parsers.freeEthereumIoClaimed}),
                rolledNumber: new ReadableWidget({selector: '#numberroll'}),
                captcha: new HCaptchaWidget(),
                success: new ReadableWidget({selector: '#info'})
            };
            let actions = {
                readRolledNumber: true
            };
            super(elements, actions);
        }

        init() {
            let url = new URL(window.location.href);
            if (url.pathname == '/free/') {
                this.run();
            } else if (url.pathname == '/') {
                shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
            }
        }
    }

    class OurBitco extends Faucet {
        constructor() {
            let elements = {
                countdownMinutes: new CountdownWidget({selector: '#minutes'}),
                rollButton: new ButtonWidget({selector: '#btn-claim'}),
                balance: new ReadableWidget({selector: '#saldo-header', parser: Parsers.trimNaNs}),
                claimed: new ReadableWidget({selector: '.exibe-v', parser: Parsers.trimNaNs}),
                rolledNumber: new ReadableWidget({selector: '#sorte span span', parser: Parsers.innerTextJoinedToInt}),
                captcha: new OBCaptchaWidget(),
                success: new ReadableWidget({selector: '.alert-success2'})
            };
            let actions = {
                preRoll: true,
                postRun: true,
                altValidation: true,
                readRolledNumber: true,
                updateWithoutClosing: true
            };
            super(elements, actions);
        }

        init() {
            let url = window.location.href;

            let subFaucets = [ { id: '60', next: Date.now() }, { id: '30', next: Date.now() }, { id: '15', next: Date.now() }, { id: '5', next: Date.now() } ];
            this._subFaucets = shared.getCurrent().subFaucets || subFaucets;
            shared.devlog(`@OurBitco init`);
            shared.devlog(`params: ${JSON.stringify(this._params)}`);

            if (url.includes('dashboard/faucet/?time=')) {
                this._currentIdx = this._subFaucets.findIndex(x => x.id == window.location.href.split('=')[1]);
                this.run();
                return;
            } else if (url.endsWith('/dashboard')) {
                this.readAllCountdowns().then( () => {
                    shared.devlog(`@Ourbitco Saving: ${JSON.stringify(this._subFaucets)}`);
                    shared.setProp('faucets', this._subFaucets);
                    let savedVal = shared.getCurrent().subFaucets || {};
                    shared.devlog(`@Ourbitco Saving: ${JSON.stringify(savedVal)}`);

                    this._currentIdx = -1;

                    this.postRun();
                });

            } else if (url == 'https://ourbitco.in/' || url.endsWith('login')) {
                //TODO: try login
                shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
            }
            return;
        }

        async readAllCountdowns() {
            return wait().then( () => {
                let cds = [...document.querySelectorAll('.countdown')];
                if (cds.length == 4 && cds.every(x => x.isUserFriendly() && [...x.querySelectorAll('.time h5')].every(y => y.isUserFriendly()))) {
                    for(let i = 0; i < cds.length; i++) {
                        let times = cds[i].querySelectorAll('.time h5');
                        if (times.length == 4) {
                            let ms = (+times[2].innerText * 60 + +times[3].innerText) * 1000;
                            shared.devlog(`@Ourbitco Miliseconds read: ${ms}`);
                            this._subFaucets[3-i].next = (ms == 0) ? Date.now() : helpers.addMs(new Date(), ms);
                            shared.devlog(`@Ourbitco Updated faucet ${this._subFaucets[3-i].id} to ${this._subFaucets[3-i].next}`);
                        }
                    }
                    return true;
                } else {
                    return readAllCountdowns();
                }
            });
        }

        preRoll() {
            let switchCaptchaBtn = document.querySelector('.btn-captcha');
            if(switchCaptchaBtn) {
                switchCaptchaBtn.click();
            }
        }

        postRun() {
            if (this._currentIdx > -1) { // just rolled a faucet
                if (this._result.nextRoll) {
                    shared.devlog(`Adding nextRoll: ${this._result.nextRoll} as ${this._result.nextRoll}`);
                    this._subFaucets[this._currentIdx].next = this._result.nextRoll;
                    shared.setProp('subFaucets', this._subFaucets);
                }
            }

            do {
                this._currentIdx++;
                let f = this._subFaucets[this._currentIdx];
                if (!f) {
                    break;
                }
                if (f.next < Date.now()) {
                    shared.devlog(`Next faucet: ${f.id}`);
                    window.location.href = 'https://ourbitco.in/dashboard/faucet/?time=' + f.id;
                    return;
                } else {
                    shared.devlog(`Skipping faucet: ${f.id}`);
                }
            } while (this._currentIdx < this._subFaucets.length);

            // close
            this._result = shared.getResult();
            let randomDelay = helpers.getRandomMs(32, 7);
            shared.devlog(`Random Delay: ${randomDelay}`);
            this._result.nextRoll = helpers.addMs(new Date(), randomDelay); // Wait hoursBetweenRuns +/- 2%
            shared.closeWindow(this._result);
            return;
        }

        altValidation() {
            let elm = document.querySelector('#minutes');
            if (elm && elm.isVisible()) {
                try {
                    if(parseInt(elm.innerText) > 0) {
                        return true;
                    }
                } catch (err) { }
            }
            elm = document.querySelector('#errormessage');
            if (elm && elm.isVisible() && elm.innerText != '') {
                shared.devlog(`Error after roll: ${elm.innerText}`);
                //TODO: review. Might cause endless loop
                window.location.reload();
            }
            return false;
        }
    }

    class BetFuryBox extends Faucet {
        constructor(coinPrefix) {
            let elements = {
                preRunButton: new ButtonWidget({selector: '#' + coinPrefix + '_free_box_withdraw_page'}),
                captcha: new HCaptchaWidget({selector: '.free_withdraw iframe'}),
                rollButton: new ButtonWidget({selector: '#free_box_withdraw_popup'}),
                success: new ReadableWidget({selector: '.popup.top'}),
                claimed: new ReadableWidget({selector: '#' + coinPrefix + '_free_box_withdraw_page', parser: Parsers.betFuryBoxClaimed})
            };
            let actions = {
                preRun: true,
                readClaimed: true,
                readBalance: false,
                readRolledNumber: false
            };
            super(elements, actions);
        }

        init() {
            let url = window.location.href;
            if (url.includes('https://betfury.io/boxes/all')) {
                this.run();
                return;
            } else {
                return;
            }
        }

        readNextRoll() {
            return helpers.addMs(new Date(), helpers.getRandomMs(32, 7)); // Wait hoursBetweenRuns +/- 2%
        }

        async preRun() {
            return wait().then( () => {
                if (document.querySelectorAll('.buttons-login').length > 0) {
                    shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
                    return;
                }
                if (this._elements.preRunButton.isUserFriendly) {
                    if (!this._elements.preRunButton.isUserFriendly.disabled) {
                        return this._elements.preRunButton.click();
                    } else {
                        this._timeout.restart();
                        //let result = {};
                        this._result.nextRoll = this.readNextRoll();

                        shared.closeWindow(this._result);
                    }
                }
                return this.preRun();
            });
        }
    }

    class KingBiz extends Faucet {
        constructor() {
            let elements = {
                countdownMinutes: new CountdownWidget({selector: '#show_countdown_clock', parser: Parsers.kingBizCountdown}),
                rollButton: new ButtonWidget({selector: 'input[value="ROLL"]:last-of-type'}),
                balance: new ReadableWidget({selector: 'li.top_balance', parser: Parsers.trimNans}),
                claimed: new ReadableWidget({selector: '#modal_header_msg', parser: Parsers.freeEthereumIoClaimed}),
                rolledNumber: new ReadableWidget({selector: '#show_roll_numbers span', parser: Parsers.innerTextJoinedToInt}),
                captcha: new HCaptchaWidget(),
                success: new ReadableWidget({selector: '#show_roll_numbers span'})
            };
            let actions = {
                readRolledNumber: true,
                preRun: true
            };
            super(elements, actions);
        }

        init() {
            let url = window.location.href;
            if (url.includes('/faucet')) {
                this.run();
                return;
            } else if (url.includes('/login')) {
                shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
                return;
            }
        }

        async preRun() {
            let dangerDivs = document.querySelectorAll('.alert.alert-danger');

            if (dangerDivs.length > 0) {
                let flag = [...dangerDivs].find(x => x.innerText.includes('you need to confirm your email'));
                let txt = dangerDivs[0].innerText;
                if (flag) {
                    return shared.closeWithError(K.ErrorType.VERIFY_EMAIL, 'You need to verify your email address at the site. Go to ' + (new URL('settings', window.location)).href + ' to send the email.');
                }
            }
        }
    }

    class DutchyRoll extends Faucet {
        constructor() {
            let elements = {
                countdownMinutes: new CountdownWidget({selector: '#timer', parser: Parsers.dutchyRollCountdown}), //TODO (parser): "26 Minutes 23"
                captcha: new HCaptchaWidget(),
                rollButton: new ButtonWidget({selector: '#claim'}), //w/booster video: '#unlockbutton'
                success: new ReadableWidget({selector: '.card.green.pulse p'}), //"You Won 0.00409070 TRX + 20 XP"
                claimed: new ReadableWidget({selector: '.card.green.pulse p', parser: Parsers.freeEthereumIoClaimed}) //"You Won 0.00409070 TRX + 20 XP"
                //balance: new ReadableWidget({selector: '.card.red.darken-3.white-text.center-align font'}) // "0.51980137  TRX"
            };
            let actions = {
                preRun: true,
                readClaimed: true,
                readBalance: false,
                readRolledNumber: false
            };
            super(elements, actions);
        }

        init() {
            let url = window.location.href;
            if (url.includes('roll.php')) {
                this._elements.success = new ReadableWidget({selector: '.card.blue.pulse'}); //.innerText = "You Won :101  DUTCHY + 20 XP"
                this._elements.claimed = new ReadableWidget({selector: '.card.blue.pulse', parser: Parsers.dutchysClaimed}); //.innerText = "You Won :101  DUTCHY + 20 XP"
                this.run();
                return;
            } else if (url.includes('coin_roll.php')) {
                this.run();
                return;
            } else if (url.includes('login.php')) {
                shared.closeWithError(K.ErrorType.NEED_TO_LOGIN, '');
                return;
            }
        }

        readNextRoll() {
            return helpers.addMs(new Date(), helpers.getRandomMs(35, 40)); // Wait hoursBetweenRuns +/- 2%
        }

        async preRun() {
            if (this._elements.captcha.isUserFriendly) {
                return true;
            } else {
                shared.closeWithError(K.ErrorType.Error, 'You need to set hCaptcha as default at Dutchy (https://autofaucet.dutchycorp.space/account.php)');
            }
        }
    }

    let timer;
    let useTimer;
    function init() {
        persistence = objectGenerator.createPersistence();
        shared = objectGenerator.createShared();
        useTimer = shared.getConfig()['defaults.extraInterval'];
        if(window.location.host === 'satology.onrender.com') {
            timer = new Timer(true, 30);
            shared.devlog('Manager Reloaded');
            manager = objectGenerator.createManager();
            CFPromotions = objectGenerator.createCFPromotions();
            ui = objectGenerator.createUi();
            CFHistory = objectGenerator.createCFHistory();

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址