[New] Faucetpay Rotator (Unified + Fast ABLinks)

Unified rotator + fast ABLinks solver + stable referral persistence

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         [New] Faucetpay Rotator (Unified + Fast ABLinks)
// @namespace    http://tampermonkey.net/
// @version      5.5
// @description  Unified rotator + fast ABLinks solver + stable referral persistence
// @author       Andrewblood + Fix + Assistant
// @match        https://faucetpay.io/*
// @match        https://claimfreecoins.io/*
// @connect      claimfreecoins.io
// @icon         https://www.google.com/s2/favicons?sz=64&domain=faucetpay.io
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // ====== CONFIG ======
    const REF_EMAIL = '[email protected]';
    const REF_KEY = 'fpr_ref'; // localStorage key
    const REF_PARAM = 'r';
    const REDIRECT_LOCK = 'fpr_redirect_lock'; // sessionStorage timestamp
    const REDIRECT_LOCK_TTL = 5000; // ms to avoid redirect loops

    // ====== USER ADDRESSES ======
    var email = REF_EMAIL;
    var bitcoin = email;
    var ethereum = email;
    var dogecoin = email;
    var litecoin = email;
    var bch = email;
    var dash = email;
    var digibyte = email;
    var tron = email;
    var tether = email;
    var feyorra = email;
    var zcash = email;
    var binance = email;
    var solana = email;
    var xrp = email;
    var polygon = email;
    var cardano = email;
    var toncoin = email;
    var stellar = email;
    var usdc = email;
    var monero = email;

    // ====== SITES LIST (base paths) ======
    const gr8sites = [
        '/bitcoin-faucet/',
        '/dogecoin-faucet/',
        '/litecoin-faucet/',
        '/tron-faucet/',
        '/bnb-faucet/',
        '/solana-faucet/',
        '/tether-faucet/',
        '/polygon-faucet/',
        '/ethereum-faucet/',
        '/bch-faucet/',
        '/dash-faucet/',
        '/zcash-faucet/',
        '/digibyte-faucet/',
        '/feyorra-faucet/',
        '/usdc-faucet/',
        '/ripple-faucet/',
        '/toncoin-faucet/',
        '/cardano-faucet/',
        '/monero-faucet/',
        '/stellar-faucet/'
    ];

    // Persist referral in localStorage so it can't be easily lost by redirects
    function ensureRefStored() {
        try {
            const stored = localStorage.getItem(REF_KEY);
            if (!stored) localStorage.setItem(REF_KEY, REF_EMAIL);
        } catch (e) { console.warn('ref store err', e); }
    }

    function getStoredRef() {
        try { return localStorage.getItem(REF_KEY) || REF_EMAIL; } catch (e) { return REF_EMAIL; }
    }

    // Add or ensure referral param present in current URL. Will replace (reload) only if necessary.
    function ensureReferralInUrl() {
        try {
            const ref = getStoredRef();
            const url = new URL(window.location.href);

            // if already contains the ref param with same value - ok
            if (url.searchParams.get(REF_PARAM) === ref) return false;

            // don't infinitely redirect: use session lock
            const lock = sessionStorage.getItem(REDIRECT_LOCK);
            const now = Date.now();
            if (lock && (now - Number(lock) < REDIRECT_LOCK_TTL)) return false;
            sessionStorage.setItem(REDIRECT_LOCK, String(now));

            url.searchParams.set(REF_PARAM, ref);

            // If the only difference is search param, we do a replace to reload with ref
            window.location.replace(url.toString());
            return true;
        } catch (e) { console.warn('ensureReferralInUrl error', e); return false; }
    }

    // Normalize a page to a base path for matching with gr8sites array
    function getBasePath(href) {
        try {
            const u = new URL(href);
            // ensure trailing slash
            let p = u.pathname;
            if (!p.endsWith('/')) p = p + '/';
            return p.toLowerCase();
        } catch (e) { return '/'; }
    }

    // Detect coin address from URL path
    function detectCoinAddress(url) {
        const coinMap = {
            "bitcoin": bitcoin, "btc": bitcoin,
            "ethereum": ethereum, "eth": ethereum,
            "doge": dogecoin, "dogecoin": dogecoin,
            "litecoin": litecoin, "ltc": litecoin,
            "bch": bch,
            "dash": dash,
            "digibyte": digibyte, "dgb": digibyte,
            "tron": tron, "trx": tron,
            "tether": tether, "usdt": tether,
            "feyorra": feyorra,
            "zcash": zcash, "zec": zcash,
            "binance": binance, "bnb": binance,
            "solana": solana,
            "ripple": xrp, "xrp": xrp,
            "polygon": polygon, "matic": polygon,
            "cardano": cardano, "ada": cardano,
            "toncoin": toncoin, "ton": toncoin,
            "stellar": stellar, "xlm": stellar,
            "usdc": usdc, "usd-coin": usdc,
            "monero": monero, "xmr": monero
        };

        const match = url.match(/\/([a-z0-9\-]+)-faucet/i);
        return match ? (coinMap[match[1]] || email) : email;
    }

    // MAIN ROTATOR + UI automation
    function runRotator() {
        // ensure referral always stored
        ensureRefStored();

        // ensure referral param present (may redirect)
        if (window.location.hostname.includes('claimfreecoins.io')) {
            if (ensureReferralInUrl()) return; // redirected, stop execution now
        }

        setTimeout(() => {
            const currentBase = getBasePath(window.location.href);

            // find index by base path
            let currentIndex = gr8sites.findIndex(p => p === currentBase);
            if (currentIndex === -1) {
                // maybe landing page or other page — try to find idx by startsWith
                currentIndex = gr8sites.findIndex(p => currentBase.indexOf(p.replace(/\/$/, '')) !== -1);
            }

            const nextIndex = (currentIndex + 1) % gr8sites.length;
            const nextHref = 'https://claimfreecoins.io' + gr8sites[nextIndex] + '?' + REF_PARAM + '=' + encodeURIComponent(getStoredRef());

            // DATE CHECK
            const dateSelector = '.card-body > table > tbody > tr:nth-child(1) > td:nth-child(3)';
            const dateElement = document.querySelector(dateSelector);
            if (dateElement) {
                const date = new Date(dateElement.innerText.replace(/-/g, '/'));
                const now = new Date();
                const hoursDifference = (now - date) / 3600000;
                if (hoursDifference > 24) {
                    window.location.replace(nextHref);
                    return;
                }
            }

            // SUCCESS / ERROR AUTOSKIP
            if (document.querySelector('div.alert.alert-danger.fade.show') || document.querySelector('div.alert.alert-success.fade.show')) {
                window.location.replace(nextHref);
                return;
            }

            // AUTO FILL ADDRESS
            var currentCoinAddress = detectCoinAddress(window.location.href);
            var nameInput = document.querySelector('#address');
            if (nameInput) {
                nameInput.value = currentCoinAddress;
                nameInput.dispatchEvent(new Event('input', { bubbles: true }));
            }

            // BUTTON 1 (claim start)
            var firstClaimButton = document.querySelector('.btn.btn-block.my-0') || document.querySelector('.btn.btn-block.btn-primary.my-2');
            if (firstClaimButton) firstClaimButton.click();

            // BUTTON 2 AFTER CAPTCHA (reCAPTCHA)
            var secondClaimButton = document.querySelector('#login');

            const checkCaptchaAndClick = setInterval(function() {
                var ReCaptchaResponse = document.querySelector('.g-recaptcha-response');
                if ((ReCaptchaResponse && ReCaptchaResponse.value.length > 1) && (secondClaimButton && secondClaimButton.offsetHeight > 1)) {
                    secondClaimButton.click();
                    clearInterval(checkCaptchaAndClick);
                }

                // if solver filled a simple captcha, it may trigger a submit button (handle in solver)
            }, 300);

            // fallback: if nothing happens for long time, go to next
            setTimeout(() => {
                // re-check if page still same and not showing captcha
                if (!document.querySelector('.g-recaptcha') && !document.querySelector('img[alt="captcha"], #captcha-image')) {
                    // skip to next
                    window.location.replace(nextHref);
                }
            }, 30 * 1000);

        }, 800);
    }

    // ====== SUPER FAST ABLinks SOLVER (replaces Tesseract) ======
    (function fastABLinks() {
        let lastImg = '';
        const watcher = setInterval(() => {
            let img = document.querySelector('img[alt="captcha"], img[src*="captcha"], #captcha-image');
            if (img && img.src && img.src !== lastImg) {
                lastImg = img.src;
                fastSolve(img);
            }
        }, 120);

        function fastSolve(img) {
            try {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = img.naturalWidth || img.width || 120;
                canvas.height = img.naturalHeight || img.height || 40;
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

                const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                const data = imgData.data;

                // simple adaptive thresholding
                let sum = 0, cnt = 0;
                for (let i = 0; i < data.length; i += 4) { sum += (data[i] + data[i+1] + data[i+2]); cnt++; }
                const avg = sum / cnt / 3;
                const thresh = Math.max(120, Math.min(160, avg));

                for (let i = 0; i < data.length; i += 4) {
                    let gray = (data[i] + data[i+1] + data[i+2]) / 3;
                    let v = gray > thresh ? 255 : 0;
                    data[i] = data[i+1] = data[i+2] = v;
                }
                ctx.putImageData(imgData, 0, 0);

                const text = readSimple(canvas);
                if (text && text.length >= 3) {
                    fillSimpleCaptcha(text);
                }

            } catch (e) { console.log('Fast ABLinks Error:', e); }
        }

        function readSimple(canvas) {
            const w = canvas.width;
            const h = canvas.height;
            const ctx = canvas.getContext('2d');
            const data = ctx.getImageData(0, 0, w, h).data;

            // vertical projection
            const cols = new Uint16Array(w);
            for (let x = 0; x < w; x++) {
                let count = 0;
                for (let y = 0; y < h; y++) {
                    const idx = (y * w + x) * 4;
                    if (data[idx] === 0) count++;
                }
                cols[x] = count;
            }

            // find segments (symbols) by gaps
            const segs = [];
            let inSeg = false, start = 0;
            for (let x = 0; x < w; x++) {
                if (!inSeg && cols[x] > 2) { inSeg = true; start = x; }
                if (inSeg && cols[x] <= 2) { inSeg = false; segs.push([start, x]); }
            }
            if (inSeg) segs.push([start, w]);

            if (segs.length === 0) return '';

            // crude mapping by widths
            const map = segs.map(s => {
                const width = s[1] - s[0];
                if (width < 8) return '1';
                if (width < 12) return '7';
                if (width < 16) return '2';
                if (width < 19) return '3';
                if (width < 22) return '8';
                if (width < 26) return 'A';
                return 'X';
            });

            // join and sanitize
            return map.join('').replace(/X/g, '').slice(0, 6);
        }

        function fillSimpleCaptcha(answer) {
            const cleaned = (answer || '').replace(/[^A-Za-z0-9]/g, '');
            if (!cleaned) return;
            const input = document.querySelector("input[name='captcha'], #captcha, input[name='code']");
            const btn = document.querySelector("button[type='submit'], input[type='submit'], #submit");
            if (input) {
                input.focus();
                input.value = cleaned;
                input.dispatchEvent(new Event('input', { bubbles: true }));
            }
            // click submit if available
            if (btn) btn.click();
        }
    })();

    // Initialize rotator after DOM ready
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', runRotator);
    else runRotator();

})();