ZeroAd: Loader

Hybrid detector loader with configurable debug flags

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

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

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         ZeroAd: Loader
// @namespace    https://greasyfork.org/en/users/1483567-vujohn123
// @version      6.2.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 'lagged'
    // ============================================================
    const SCRIPT_MAP = {
        'poki': 579407,
        'crazygames': 579404,
        'adinplay': 579403,
        'gamevui': 579405,
        'unity': 583938,
        'gamedistribution': 583939,
        'playgama': 592446,
        'yandex': 592456,
        'y8': 592513,
        'lagged': 592657,      // 🆕 Lagged.com support
        'facebook': null,
        'gamemonetize': null,
        'gamepix': null,
        'kongregate': null,
        'playhop': 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',       // 🆕
            'gamemonetize.com': 'gamemonetize',
            'gamepix.com': 'gamepix',
            'kongregate.com': 'kongregate',
            'lagged.com': 'lagged',
            'playhop.com': 'playhop',
            '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');   // 🆕

        // 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('gamemonetize')) detected.add('gamemonetize');
            if (src.includes('gamepix')) detected.add('gamepix');
            if (src.includes('kongregate')) detected.add('kongregate');
            if (src.includes('playhop')) detected.add('playhop');
            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');   // 🆕
        };
        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'  // 🆕
        ];
        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 });
    }
})();