ZeroAd: Loader

Hybrid detector loader with configurable debug flags

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         ZeroAd: Loader
// @namespace    https://greasyfork.org/en/users/1483567-vujohn123
// @version      6.3.0
// @description  Hybrid detector loader with configurable debug flags
// @author       ZeroAd Team
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    if (window.__zaLoaderLoaded) return;
    window.__zaLoaderLoaded = true;

    // ============================================================
    //  CẤU HÌNH
    // ============================================================
    const DEFAULT_CONFIG = {
        LOG_LEVEL: 'info',
        SHOW_EMOJI: true,
        ENABLE_CONSOLE_SNIFFER: true,
        ENABLE_GLOBAL_TRAPS: true,
        ENABLE_MUTATION_OBSERVER: true,
        RETRY_DELAY: 100,
        MAX_RETRIES: 5,
        FALLBACK_PLATFORM: 'adinplay',
        IGNORE_PLATFORMS: [],
        DEBUG_PLATFORMS: [],
    };

    function getConfig() {
        let config = { ...DEFAULT_CONFIG };
        if (window.__zaConfig) config = { ...config, ...window.__zaConfig };
        try {
            const params = new URLSearchParams(window.location.search);
            if (params.has('za_debug') || params.has('za_log_level')) {
                config.LOG_LEVEL = params.get('za_log_level') || 'debug';
                if (params.has('za_debug')) {
                    config.LOG_LEVEL = 'debug';
                    config.ENABLE_CONSOLE_SNIFFER = true;
                    config.ENABLE_GLOBAL_TRAPS = true;
                    config.ENABLE_MUTATION_OBSERVER = true;
                }
                if (params.has('za_no_sniffer')) config.ENABLE_CONSOLE_SNIFFER = false;
                if (params.has('za_no_traps')) config.ENABLE_GLOBAL_TRAPS = false;
                if (params.has('za_no_observer')) config.ENABLE_MUTATION_OBSERVER = false;
            }
        } catch(e) {}
        return config;
    }
    const CONFIG = getConfig();

    // ============================================================
    //  LOGGING
    // ============================================================
    const LOG_LEVELS = { 'none': 0, 'error': 1, 'warn': 2, 'info': 3, 'debug': 4 };
    const currentLogLevel = LOG_LEVELS[CONFIG.LOG_LEVEL] || LOG_LEVELS.info;
    const PREFIX = '[ZeroAd:Loader]';
    const log = {
        debug: (...args) => { if (currentLogLevel >= 4) console.log(PREFIX, '🐛', ...args); },
        info: (...args) => { if (currentLogLevel >= 3) console.log(PREFIX, 'ℹ️', ...args); },
        warn: (...args) => { if (currentLogLevel >= 2) console.warn(PREFIX, '⚠️', ...args); },
        error: (...args) => { if (currentLogLevel >= 1) console.error(PREFIX, '❌', ...args); },
        success: (...args) => { if (currentLogLevel >= 3) console.log(PREFIX, '✅', ...args); }
    };

    // ============================================================
    //  PLATFORM MAP — 🆕 UPDATED with PlayHop
    // ============================================================
    const SCRIPT_MAP = {
        'poki': 579407,
        'crazygames': 579404,
        'adinplay': 579403,
        'gamevui': 579405,
        'unity': 583938,
        'gamedistribution': 583939,
        'playgama': 592446,
        'yandex': 592456,
        'y8': 592513,
        'lagged': 592657,
        'playhop': 592665,      // 🆕 PlayHop support
        'facebook': null,
        'gamemonetize': null,
        'gamepix': null,
        'kongregate': null,
        'gameflare': null,
        'ezoic': null
    };
    const FALLBACK = CONFIG.FALLBACK_PLATFORM;
    const loaded = new Set();
    const pending = new Set();

    // ============================================================
    //  DETECTION ENGINE — HYBRID
    // ============================================================
    function detectPlatforms() {
        const detected = new Set();
        const host = window.location.hostname.toLowerCase();

        // 1. Hostname
        const hostMap = {
            'poki.com': 'poki',
            'crazygames.com': 'crazygames',
            'gamevui.vn': 'gamevui',
            'gamedistribution.com': 'gamedistribution',
            'playgama.com': 'playgama',
            'yandex': 'yandex',
            'games.yandex': 'yandex',
            'y8.com': 'y8',
            'y8games.com': 'y8',
            'lagged.com': 'lagged',
            'playhop.com': 'playhop',      // 🆕
            'gamemonetize.com': 'gamemonetize',
            'gamepix.com': 'gamepix',
            'kongregate.com': 'kongregate',
            'gameflare.com': 'gameflare'
        };
        for (const [key, value] of Object.entries(hostMap)) {
            if (host.includes(key)) detected.add(value);
        }

        // 2. Global objects
        if (window.GD_OPTIONS || window.gdApi || window.GDSdk) detected.add('gamedistribution');
        if (window.ID || window.ID?.ads || window.ID?.Event || window.y8 || window.y8?.sdk) detected.add('y8');
        if (window.CrazyGames || window.CrazyGames?.SDK || window.CrazySDK || window.CrazygamesAds) detected.add('crazygames');
        if (window.PokiSDK) detected.add('poki');
        if (window.bridge || window.PlayGama || window.pg || window.PG) detected.add('playgama');
        if (window.YaGames || window.ysdk) detected.add('yandex');
        if (window.GVAdBreak) detected.add('gamevui');
        if (window.FBInstant) detected.add('facebook');
        if (window.KongregateAPI) detected.add('kongregate');
        if (window.EzoicAd) detected.add('ezoic');
        if (window.LaggedAPI) detected.add('lagged');
        // 🆕 PlayHop detection
        if (window._a || window.adv || window.Playhop || window.PlayhopSDK || window.__playhopAds) {
            detected.add('playhop');
        }

        // 3. Unity
        if (window.unityInstance || window.gameInstance || window.UnityLoader ||
            document.querySelector('canvas.unity-canvas')) {
            detected.add('unity');
        }

        // 4. Script src
        const scripts = document.querySelectorAll('script[src]');
        for (const s of scripts) {
            const src = s.src.toLowerCase();
            if (src.includes('poki')) detected.add('poki');
            if (src.includes('crazygames')) detected.add('crazygames');
            if (src.includes('gamevui')) detected.add('gamevui');
            if (src.includes('gamedistribution') || src.includes('main.min.js')) detected.add('gamedistribution');
            if (src.includes('playgama')) detected.add('playgama');
            if (src.includes('yandex')) detected.add('yandex');
            if (src.includes('y8') || src.includes('id.net') || src.includes('gamebreakbeta')) detected.add('y8');
            if (src.includes('lagged')) detected.add('lagged');
            if (src.includes('playhop')) detected.add('playhop');   // 🆕
            if (src.includes('gamemonetize')) detected.add('gamemonetize');
            if (src.includes('gamepix')) detected.add('gamepix');
            if (src.includes('kongregate')) detected.add('kongregate');
            if (src.includes('gameflare')) detected.add('gameflare');
            if (src.includes('ezoic')) detected.add('ezoic');
            if (src.includes('fbinstant') || src.includes('facebook')) detected.add('facebook');
        }

        // Filter
        const result = new Set();
        for (const platform of detected) {
            if (CONFIG.IGNORE_PLATFORMS.includes(platform)) continue;
            result.add(platform);
        }
        if (result.size === 0) result.add(FALLBACK);
        return result;
    }

    // ============================================================
    //  CONSOLE SNIFFER
    // ============================================================
    function setupConsoleSniffer() {
        if (!CONFIG.ENABLE_CONSOLE_SNIFFER) return;
        const originalLog = console.log, originalWarn = console.warn, originalError = console.error;
        const sniff = (args) => {
            if (!args || args.length === 0) return;
            const msg = args.join(' ');
            if (typeof msg !== 'string') return;
            const lower = msg.toLowerCase();
            if (lower.includes('gamedistribution') || lower.includes('gd-api')) loadPlatform('gamedistribution');
            if (lower.includes('y8') || lower.includes('id.net') || lower.includes('id.ads')) loadPlatform('y8');
            if (lower.includes('crazygames')) loadPlatform('crazygames');
            if (lower.includes('poki')) loadPlatform('poki');
            if (lower.includes('playgama') || lower.includes('bridge')) loadPlatform('playgama');
            if (lower.includes('yandex') || lower.includes('ysdk') || lower.includes('yagames')) loadPlatform('yandex');
            if (lower.includes('unity') || lower.includes('unityengine')) loadPlatform('unity');
            if (lower.includes('fbinstant') || lower.includes('facebook instant')) loadPlatform('facebook');
            if (lower.includes('kongregate')) loadPlatform('kongregate');
            if (lower.includes('ezoic')) loadPlatform('ezoic');
            if (lower.includes('laggedapi') || lower.includes('lagged')) loadPlatform('lagged');
            // 🆕 PlayHop console sniff
            if (lower.includes('playhop') || lower.includes('sdk init')) loadPlatform('playhop');
        };
        console.log = function(...args) { sniff(args); return originalLog.apply(this, args); };
        console.warn = function(...args) { sniff(args); return originalWarn.apply(this, args); };
        console.error = function(...args) { sniff(args); return originalError.apply(this, args); };
        log.debug('Console sniffer installed');
    }

    // ============================================================
    //  LOADER ENGINE
    // ============================================================
    function loadPlatform(platformKey) {
        if (!platformKey || loaded.has(platformKey) || pending.has(platformKey)) return;
        const scriptId = SCRIPT_MAP[platformKey];
        if (!scriptId) { log.warn(`No script ID for: ${platformKey}`); return; }
        pending.add(platformKey);
        const url = `https://greasyfork.org/scripts/${scriptId}/code/${scriptId}.user.js`;
        const script = document.createElement('script');
        const tryLoad = (attempt) => {
            script.onload = () => { loaded.add(platformKey); pending.delete(platformKey); log.success(`Loaded ${platformKey}`); };
            script.onerror = () => {
                if (attempt < CONFIG.MAX_RETRIES) {
                    const delay = CONFIG.RETRY_DELAY * Math.pow(2, attempt);
                    log.warn(`Retry ${platformKey} in ${delay}ms (${attempt+1}/${CONFIG.MAX_RETRIES})`);
                    setTimeout(() => tryLoad(attempt + 1), delay);
                } else {
                    pending.delete(platformKey);
                    log.error(`Failed to load ${platformKey}`);
                }
            };
            script.src = url;
            (document.head || document.documentElement).appendChild(script);
        };
        tryLoad(0);
    }

    // ============================================================
    //  TRAPS & OBSERVER
    // ============================================================
    function installGlobalTrap() {
        if (!CONFIG.ENABLE_GLOBAL_TRAPS) return;
        const sdkNames = [
            'GD_OPTIONS','gdApi','GDSdk','ID','y8','CrazyGames','CrazySDK','CrazygamesAds',
            'PokiSDK','bridge','PlayGama','pg','PG','YaGames','ysdk','GVAdBreak',
            'unityInstance','gameInstance','UnityLoader','FBInstant','KongregateAPI','EzoicAd',
            'LaggedAPI',
            // 🆕 PlayHop global objects
            '_a','adv','Playhop','PlayhopSDK','__playhopAds'
        ];
        sdkNames.forEach(name => {
            let _value = window[name];
            Object.defineProperty(window, name, {
                configurable: true, enumerable: true,
                get() { return _value; },
                set(newVal) {
                    _value = newVal;
                    const detected = detectPlatforms();
                    detected.forEach(key => loadPlatform(key));
                }
            });
        });
        log.debug('Global traps installed');
    }

    function setupObserver() {
        if (!CONFIG.ENABLE_MUTATION_OBSERVER) return;
        const observer = new MutationObserver(() => {
            const detected = detectPlatforms();
            detected.forEach(key => loadPlatform(key));
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
        log.debug('MutationObserver installed');
    }

    // ============================================================
    //  INIT
    // ============================================================
    function init() {
        log.info(`Starting (log level: ${CONFIG.LOG_LEVEL})`);
        setupConsoleSniffer();
        installGlobalTrap();
        setupObserver();
        const initial = detectPlatforms();
        initial.forEach(key => loadPlatform(key));
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                const later = detectPlatforms();
                later.forEach(key => loadPlatform(key));
            });
        }
        log.info('Ready, waiting for events...');
    }

    if (document.head) init();
    else {
        const obs = new MutationObserver(() => {
            if (document.head) { obs.disconnect(); init(); }
        });
        obs.observe(document.documentElement, { childList: true, subtree: true });
    }
})();