MAX Blinder

Ограничение телеметрии мессенджера MAX

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         MAX Blinder
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Ограничение телеметрии мессенджера MAX
// @author       Echo91
// @match        https://*.max.ru/*
// @license      MIT
// @run-at       document-start
// @inject-into  page
// @sandbox      JavaScript
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // ========================== КОНФИГУРАЦИЯ ==========================
    const CONFIG = {
        BLOCK_FETCH_XHR: true,
        BLOCK_WEBSOCKET_OPCODE5: true,
        BLOCK_WEBRTC: true,
        BLOCK_BEACON: true,
        PROTECT_CANVAS: true,
        PROTECT_CANVAS_TO_DATA_URL: true,
        PROTECT_AUDIO: true,
        HIDE_WEBDRIVER: true,
        HIDE_CONNECTION: true,
        FAKE_PLUGINS: true,
        FIXED_SCREEN: false,
        LOG_SERVICE_WORKER: true,
        BLOCK_SERVICE_WORKER: false,
        FAKE_FONTS: true,
        FAKE_BATTERY: true,
        FAKE_TIMEZONE: false,
        FAKE_LANGUAGE: false,
        IFRAME_PROTECTION: true,
        SCREEN_WIDTH: 1920,
        SCREEN_HEIGHT: 1080,
        COLOR_DEPTH: 24,
        INSPECT_POST_PAYLOAD: true,
        TIMING_NOISE: true,
        CLEAR_STORAGE_ON_START: false,
        TELEMETRY_KEYWORDS: ['events', 'host_reachability', 'telemetry', 'metrics', 'analytics', 'crash', 'perf'],
        // НОВЫЕ ОПЦИИ
        FAKE_USER_AGENT_DATA: true,
        PROTECT_WEBGL: true,
        BLOCK_CACHE_STORAGE: false,
        MOUSE_NOISE: false,
        BLOCK_PROBING: true,
        BLOCKED_OPCODES: [2, 5, 22, 31, 103, 161],
        AUDIO_NOISE_AMPLITUDE: 0.00005,      // увеличено с 0.000005
        PROTECT_OFFLINE_AUDIO: true          // защита OfflineAudioContext
    };

    // ========================== ПЕРЕМЕННЫЕ ==========================
    let blockedCount = 0;
    let fullLogHistory = [];
    const maxLogs = 20;
    let pendingLogs = [];
    let uiShadow = null;

    // Защита от дублирования записей
    let lastRecorded = new Map();
    const DEDUP_MS = 500;

    // ========================== ЧЁРНЫЙ СПИСОК (расширен) ==========================
    const blackList = [
        // IP-определители и трекеры
        'api.ipify.org', 'ifconfig.me', 'ident.me', 'checkip.amazonaws.com',
        'ip.mail.ru', '2ip.ru', 'ipinfo.io', 'ip-api.com', 'myexternalip.com',
        'icanhazip.com', 'jsonip.com', 'httpbin.org/ip', 'wtfismyip.com',
        'apptracer.ru', 'sdk-api', 'crash', 'metrics', 'telemetry', 'analytics',
        'vigo', 'collector', 'log-api', 'error_report', 'notify-stat', 'event/send',
        'tracker-api.vk-analytics.ru', 'my.tracker', 'data.mail.ru', 'fb.do',
        'doubleclick.net', 'google-analytics.com', 'top-fwz1.mail.ru', 'counter.yadro.ru',
        // Новые домены и IP (зондирование, телеметрия, Сфера)
        'st.max.ru', 'stats.max.ru', 'telemetry.max.ru', 'collect.max.ru', 'vk.com', 'ifconfig.co', 'yandex.net',
        'vk.com/rkn', 'sphere.avantelecom.ru', 'api.ipapi.is', 'iplocate.io', 'ip.sb',
        '155.212.204.143', '155.212.204.78', '155.212.204.193', '95.161.225.253', '127.0.0.1'
    ];

    // Домены, проверка которых блокируется как «зондирование»
    const probingDomains = ['t.me', 'telegram.org', 'whatsapp.com', 'gosuslugi.ru'];

    // ========================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========================
    function shouldBlock(url) {
        if (!url || !CONFIG.BLOCK_FETCH_XHR) return false;
        const sUrl = String(url).toLowerCase();
        return blackList.some(bad => sUrl.includes(bad));
    }

    function shouldBlockProbing(url) {
        if (!CONFIG.BLOCK_PROBING) return false;
        const sUrl = String(url).toLowerCase();
        return probingDomains.some(domain => sUrl.includes(domain));
    }

    function hasTelemetryInPayload(body) {
        if (!CONFIG.INSPECT_POST_PAYLOAD || !body) return false;
        try {
            let data = body;
            if (typeof body === 'string') {
                data = JSON.parse(body);
            }
            const str = JSON.stringify(data).toLowerCase();
            return CONFIG.TELEMETRY_KEYWORDS.some(keyword => str.includes(keyword));
        } catch (e) {
            return false;
        }
    }

    const makeNative = (obj, prop) => {
        const original = obj[prop];
        if (typeof original === 'function') {
            Object.defineProperty(original, 'toString', {
                value: () => `function ${prop}() { [native code] }`,
                configurable: true,
                writable: true
            });
        }
    };

    // ========================== ЛОГИРОВАНИЕ ==========================
    function addEntryToLog(container, data) {
        const entry = document.createElement('div');
        entry.style.cssText = 'border-bottom: 1px solid rgba(255,255,255,0.1); padding: 4px 0; color: #ffcc00; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 9px;';
        entry.innerHTML = `<span style="color: #ff4d4d;">[${data.type}]</span> ${data.url}`;
        container.prepend(entry);
        if (container.childNodes.length > maxLogs) container.removeChild(container.lastChild);
    }

    function logBlock(type, url) {
        const key = `${type}|${url}`;
        const now = Date.now();
        if (lastRecorded.has(key) && (now - lastRecorded.get(key) < DEDUP_MS)) return;
        lastRecorded.set(key, now);

        blockedCount++;
        const time = new Date().toLocaleTimeString();
        let displayUrl = String(url).split('?')[0];
        try {
            const urlObj = new URL(url);
            displayUrl = urlObj.hostname + urlObj.pathname;
        } catch(e) {}
        const entryData = {
            type: type,
            url: displayUrl,
            full: `[${time}] [${type}] ${url}`
        };
        fullLogHistory.push(entryData.full);

        if (uiShadow) {
            const counter = uiShadow.getElementById('pm-counter');
            if (counter) counter.innerText = blockedCount;
            const logContainer = uiShadow.getElementById('pm-log-list');
            if (logContainer) {
                addEntryToLog(logContainer, entryData);
                return;
            }
        }
        pendingLogs.push(entryData);
    }

    // ========================== НОВЫЕ ЗАЩИТЫ ==========================

    // 1. User-Agent Client Hints
    if (CONFIG.FAKE_USER_AGENT_DATA && navigator.userAgentData) {
        try {
            Object.defineProperty(navigator, 'userAgentData', {
                get: () => ({
                    brands: [
                        { brand: 'Not(A:Brand', version: '99' },
                        { brand: 'Google Chrome', version: '124' },
                        { brand: 'Chromium', version: '124' }
                    ],
                    mobile: false,
                    platform: 'Windows',
                    getHighEntropyValues: (hints) => Promise.resolve({
                        architecture: 'x86',
                        bitness: '64',
                        model: '',
                        platform: 'Windows',
                        platformVersion: '10.0.0',
                        uaFullVersion: '124.0.6367.60'
                    })
                }),
                configurable: true
            });
        } catch(e) {}
    }

    // 2. WebGL Fingerprinting
    if (CONFIG.PROTECT_WEBGL && window.WebGLRenderingContext) {
        const protectWebGL = (ctx) => {
            if (!ctx || !ctx.getParameter) return;
            const originalGetParameter = ctx.getParameter;
            ctx.getParameter = function(param) {
                if (param === 0x9245) return 'Google Inc. (NVIDIA)';
                if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)';
                return originalGetParameter.call(this, param);
            };
        };
        const origGetContext = HTMLCanvasElement.prototype.getContext;
        HTMLCanvasElement.prototype.getContext = function(type, attributes) {
            const ctx = origGetContext.call(this, type, attributes);
            if (type === 'webgl' || type === 'experimental-webgl' || type === 'webgl2') {
                protectWebGL(ctx);
            }
            return ctx;
        };
        makeNative(HTMLCanvasElement.prototype, 'getContext');
    }

    // 3. Блокировка Cache Storage API (опционально)
    if (CONFIG.BLOCK_CACHE_STORAGE && window.caches) {
        const origOpen = caches.open;
        caches.open = function(name) {
            if (CONFIG.TELEMETRY_KEYWORDS.some(k => name.toLowerCase().includes(k))) {
                logBlock('CACHE', `blocked access to: ${name}`);
                return Promise.reject(new Error('Cache blocked by Blinder'));
            }
            return origOpen.apply(this, arguments);
        };
        makeNative(caches, 'open');
    }

    // 4. Шум в координатах мыши (опционально)
    if (CONFIG.MOUSE_NOISE) {
        ['mousemove', 'mousedown', 'mouseup', 'click'].forEach(eventType => {
            window.addEventListener(eventType, (e) => {
                const noiseX = Math.random() * 0.1 - 0.05;
                const noiseY = Math.random() * 0.1 - 0.05;
                try {
                    Object.defineProperty(e, 'screenX', { value: e.screenX + noiseX });
                    Object.defineProperty(e, 'screenY', { value: e.screenY + noiseY });
                } catch(ignore) {}
            }, true);
        });
    }

    // 5. Защита от зондирования (блокировка проверки доступности t.me, whatsapp.com и др.)
    if (CONFIG.BLOCK_PROBING) {
        // Перехват fetch
        const origFetch = window.fetch;
        window.fetch = function(input, init) {
            const url = typeof input === 'string' ? input : input.url;
            if (shouldBlockProbing(url)) {
                logBlock('PROBE', url);
                return Promise.reject(new TypeError('Network request failed (Blinder)'));
            }
            return origFetch.apply(this, arguments);
        };
        makeNative(window, 'fetch');

        // Перехват XHR для probing (дополнительно)
        const origOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url) {
            this._url = url;
            this._isProbe = shouldBlockProbing(url);
            return origOpen.apply(this, arguments);
        };
        const origSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function(body) {
            if (this._isProbe) {
                logBlock('PROBE', this._url);
                // Имитируем ошибку сети, чтобы сайт не ждал ответа
                setTimeout(() => {
                    Object.defineProperty(this, 'readyState', { value: 4 });
                    Object.defineProperty(this, 'status', { value: 0 });
                    Object.defineProperty(this, 'statusText', { value: 'Network error' });
                    this.dispatchEvent(new Event('error'));
                    this.dispatchEvent(new Event('readystatechange'));
                }, 1);
                return;
            }
            return origSend.apply(this, arguments);
        };
        makeNative(XMLHttpRequest.prototype, 'open');
        makeNative(XMLHttpRequest.prototype, 'send');
    }

    // ========================== ОСТАЛЬНЫЕ МОДУЛИ ==========================
    // (шум в таймингах, очистка хранилищ, сетевые перехваты, WebSocket фильтрация, Beacon, iframe, анти-фингерпринтинг)

    // ========================== ШУМ В ТАЙМИНГАХ ==========================
    if (CONFIG.TIMING_NOISE) {
        const origPerfNow = performance.now;
        performance.now = function() {
            return origPerfNow.call(this) + (Math.random() * 0.1 - 0.05);
        };
        makeNative(performance, 'now');

        const origDateNow = Date.now;
        Date.now = function() {
            return origDateNow.call(this) + Math.floor(Math.random() * 2 - 1);
        };
        makeNative(Date, 'now');
    }

    // ========================== ОЧИСТКА ХРАНИЛИЩ ==========================
    if (CONFIG.CLEAR_STORAGE_ON_START) {
        try {
            if (localStorage) {
                const keysToClear = [];
                for (let i = 0; i < localStorage.length; i++) {
                    const key = localStorage.key(i);
                    if (key && CONFIG.TELEMETRY_KEYWORDS.some(k => key.toLowerCase().includes(k))) {
                        keysToClear.push(key);
                    }
                }
                keysToClear.forEach(key => localStorage.removeItem(key));
                if (keysToClear.length) logBlock('STORAGE', `cleared ${keysToClear.length} items`);
            }
            if (window.indexedDB) {
                indexedDB.databases().then(dbs => {
                    dbs.forEach(db => {
                        if (db.name && CONFIG.TELEMETRY_KEYWORDS.some(k => db.name.toLowerCase().includes(k))) {
                            indexedDB.deleteDatabase(db.name);
                            logBlock('IDB', `deleted database: ${db.name}`);
                        }
                    });
                }).catch(e => console.warn('IDB enumeration failed', e));
            }
        } catch (e) {}
    }

    // ========================== СЕТЕВЫЕ ПЕРЕХВАТЫ (основные) ==========================
    if (CONFIG.BLOCK_FETCH_XHR) {
        // fetch (уже переопределён выше для probing, но добавим блокировку по чёрному списку)
        const origFetch2 = window.fetch;
        window.fetch = function(input, init) {
            const url = typeof input === 'string' ? input : input.url;
            if (shouldBlock(url)) {
                logBlock('FETCH', url);
                return Promise.resolve(new Response('{"status":"ok"}', { status: 200 }));
            }
            if (CONFIG.BLOCK_PROBING && shouldBlockProbing(url)) {
                logBlock('PROBE', url);
                return Promise.reject(new TypeError('Network request failed (Blinder)'));
            }
            return origFetch2.apply(this, arguments);
        };
        makeNative(window, 'fetch');

        // XHR (аналогично)
        const origOpenXHR = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url) {
            this._method = method;
            this._url = url;
            this._isTracker = shouldBlock(url);
            this._isProbe = CONFIG.BLOCK_PROBING && shouldBlockProbing(url);
            return origOpenXHR.apply(this, arguments);
        };
        makeNative(XMLHttpRequest.prototype, 'open');

        const origSendXHR = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function(body) {
            if (this._isTracker) {
                logBlock('XHR', this._url);
                setTimeout(() => {
                    Object.defineProperty(this, 'readyState', { value: 4 });
                    Object.defineProperty(this, 'status', { value: 200 });
                    Object.defineProperty(this, 'responseText', { value: '{"status":"ok"}' });
                    this.dispatchEvent(new Event('load'));
                    this.dispatchEvent(new Event('readystatechange'));
                }, 1);
                return;
            }
            if (this._isProbe) {
                logBlock('PROBE', this._url);
                setTimeout(() => {
                    Object.defineProperty(this, 'readyState', { value: 4 });
                    Object.defineProperty(this, 'status', { value: 0 });
                    Object.defineProperty(this, 'statusText', { value: 'Network error' });
                    this.dispatchEvent(new Event('error'));
                    this.dispatchEvent(new Event('readystatechange'));
                }, 1);
                return;
            }
            if (this._method === 'POST' && body && hasTelemetryInPayload(body)) {
                logBlock('POST-TELE', this._url);
                setTimeout(() => {
                    Object.defineProperty(this, 'readyState', { value: 4 });
                    Object.defineProperty(this, 'status', { value: 200 });
                    Object.defineProperty(this, 'responseText', { value: '{"status":"ok"}' });
                    this.dispatchEvent(new Event('load'));
                    this.dispatchEvent(new Event('readystatechange'));
                }, 1);
                return;
            }
            return origSendXHR.apply(this, arguments);
        };
        makeNative(XMLHttpRequest.prototype, 'send');
    }

    // ========================== WEBSOCKET ФИЛЬТРАЦИЯ (с новыми опкодами) ==========================
    if (CONFIG.BLOCK_WEBSOCKET_OPCODE5) {
        const origWSSend = WebSocket.prototype.send;
        WebSocket.prototype.send = function(data) {
            if (typeof data === 'string' && data.includes('"opcode"')) {
                try {
                    const msg = JSON.parse(data);
                    const opcode = msg.opcode;
                    if (CONFIG.BLOCKED_OPCODES.includes(opcode)) {
                        logBlock('WS-TELE', `opcode ${opcode} blocked`);
                        return;
                    }
                    // Дополнительная проверка на GET_HOST_REACHABILITY (для opcode 5)
                    if (opcode === 5 &&
                        msg.payload &&
                        msg.payload.events &&
                        Array.isArray(msg.payload.events) &&
                        msg.payload.events.some(e => e.event === 'GET_HOST_REACHABILITY')) {
                        logBlock('WS-TELE', 'GET_HOST_REACHABILITY blocked');
                        return;
                    }
                } catch (e) {}
            }
            return origWSSend.apply(this, arguments);
        };
        makeNative(WebSocket.prototype, 'send');
    }

    // ========================== BEACON ==========================
    if (CONFIG.BLOCK_BEACON) {
        const origBeacon = navigator.sendBeacon;
        navigator.sendBeacon = function(url, data) {
            if (shouldBlock(url) || (CONFIG.BLOCK_PROBING && shouldBlockProbing(url))) {
                logBlock('BEACON', url);
                return true;
            }
            return origBeacon.call(this, url, data);
        };
        makeNative(navigator, 'sendBeacon');
    }

    // ========================== IFRAME ЗАЩИТА ==========================
    if (CONFIG.IFRAME_PROTECTION) {
        const origCreateElement = document.createElement;
        const origOpen = window.open;
        const origAttachShadow = Element.prototype.attachShadow;

        function protectIframe(iframe) {
            if (!iframe.contentWindow) return;
            const win = iframe.contentWindow;
            if (CONFIG.BLOCK_FETCH_XHR) {
                win.fetch = new Proxy(win.fetch, {
                    apply(target, thisArg, args) {
                        const url = typeof args[0] === 'object' ? args[0].url : args[0];
                        if (shouldBlock(url) || (CONFIG.BLOCK_PROBING && shouldBlockProbing(url))) {
                            logBlock('FETCH (iframe)', url);
                            return Promise.resolve(new Response('{"status":"ok"}', { status: 200 }));
                        }
                        return Reflect.apply(target, thisArg, args);
                    }
                });
                const origIOpen = win.XMLHttpRequest.prototype.open;
                win.XMLHttpRequest.prototype.open = function(method, url) {
                    this._isTracker = shouldBlock(url) || (CONFIG.BLOCK_PROBING && shouldBlockProbing(url));
                    this._blockUrl = url;
                    return origIOpen.apply(this, arguments);
                };
                const origISend = win.XMLHttpRequest.prototype.send;
                win.XMLHttpRequest.prototype.send = function() {
                    if (this._isTracker) {
                        logBlock('XHR (iframe)', this._blockUrl);
                        setTimeout(() => {
                            Object.defineProperty(this, 'readyState', { value: 4 });
                            Object.defineProperty(this, 'status', { value: 200 });
                            Object.defineProperty(this, 'responseText', { value: '{"status":"ok"}' });
                            this.dispatchEvent(new Event('load'));
                            this.dispatchEvent(new Event('readystatechange'));
                        }, 1);
                        return;
                    }
                    return origISend.apply(this, arguments);
                };
            }
        }

        document.createElement = function(tagName, options) {
            const element = origCreateElement.call(document, tagName, options);
            if (tagName.toLowerCase() === 'iframe') {
                element.addEventListener('load', () => protectIframe(element));
                if (element.contentWindow) protectIframe(element);
            }
            return element;
        };
        makeNative(document, 'createElement');

        window.open = function(url, name, specs, replace) {
            const newWindow = origOpen.call(this, url, name, specs, replace);
            if (newWindow && newWindow.document) {
                newWindow.addEventListener('load', () => {
                    if (CONFIG.BLOCK_FETCH_XHR && newWindow.fetch) {
                        newWindow.fetch = new Proxy(newWindow.fetch, {
                            apply(target, thisArg, args) {
                                const url = typeof args[0] === 'object' ? args[0].url : args[0];
                                if (shouldBlock(url) || (CONFIG.BLOCK_PROBING && shouldBlockProbing(url))) {
                                    logBlock('FETCH (popup)', url);
                                    return Promise.resolve(new Response('{"status":"ok"}', { status: 200 }));
                                }
                                return Reflect.apply(target, thisArg, args);
                            }
                        });
                    }
                });
            }
            return newWindow;
        };
        makeNative(window, 'open');

        Element.prototype.attachShadow = function(init) {
            return origAttachShadow.call(this, init);
        };
        makeNative(Element.prototype, 'attachShadow');
    }

    // ========================== ЗАЩИТА ОТ ФИНГЕРПРИНТИНГА ==========================
    try {
        // Улучшенные аппаратные характеристики (согласованы с UACH)
        Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
        Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });

        // Canvas
        if (CONFIG.PROTECT_CANVAS) {
            const orgGetImageData = CanvasRenderingContext2D.prototype.getImageData;
            CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
                const imageData = orgGetImageData.call(this, x, y, w, h);
                const data = imageData.data;
                if (data.length > 0) {
                    data[0] = Math.min(255, Math.max(0, data[0] + (Math.random() > 0.5 ? 1 : -1)));
                }
                return imageData;
            };
        }

        if (CONFIG.PROTECT_CANVAS_TO_DATA_URL && CONFIG.PROTECT_CANVAS) {
            const orgToDataURL = HTMLCanvasElement.prototype.toDataURL;
            HTMLCanvasElement.prototype.toDataURL = function(type, quality) {
                const canvas = this;
                const ctx = canvas.getContext('2d');
                if (ctx && canvas.width > 0 && canvas.height > 0) {
                    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                    ctx.putImageData(imageData, 0, 0);
                }
                return orgToDataURL.call(this, type, quality);
            };
            makeNative(HTMLCanvasElement.prototype, 'toDataURL');
        }

        // Audio (увеличенная амплитуда шума)
        if (CONFIG.PROTECT_AUDIO && window.AudioContext) {
            const originalGetChannelData = AudioBuffer.prototype.getChannelData;
            AudioBuffer.prototype.getChannelData = function(channel) {
                const originalData = originalGetChannelData.call(this, channel);
                const noisyData = new Float32Array(originalData.length);
                const amp = CONFIG.AUDIO_NOISE_AMPLITUDE;
                for (let i = 0; i < originalData.length; i++) {
                    noisyData[i] = originalData[i] + (Math.random() * amp * 2 - amp);
                }
                return noisyData;
            };
            makeNative(AudioBuffer.prototype, 'getChannelData');
        }

        // OfflineAudioContext защита (новая)
        if (CONFIG.PROTECT_OFFLINE_AUDIO && window.OfflineAudioContext) {
            const origStartRendering = OfflineAudioContext.prototype.startRendering;
            OfflineAudioContext.prototype.startRendering = function() {
                const ctx = this;
                return origStartRendering.apply(ctx, arguments).then(buffer => {
                    if (buffer && buffer.numberOfChannels > 0) {
                        const data = buffer.getChannelData(0);
                        if (data && data.length) {
                            for (let i = 0; i < Math.min(100, data.length); i += 10) {
                                data[i] += (Math.random() - 0.5) * 0.0002;
                            }
                        }
                    }
                    return buffer;
                });
            };
            makeNative(OfflineAudioContext.prototype, 'startRendering');
        }

        // WebRTC
        if (CONFIG.BLOCK_WEBRTC && window.RTCPeerConnection) {
            const OriginalRTCPeerConnection = window.RTCPeerConnection;
            window.RTCPeerConnection = new Proxy(OriginalRTCPeerConnection, {
                construct(target, args) {
                    let config = args[0] || {};
                    config = { ...config, iceServers: [], iceTransportPolicy: 'relay' };
                    const pc = new target(config);
                    pc.addIceCandidate = function() { return Promise.resolve(); };
                    return pc;
                }
            });
            window.RTCPeerConnection.prototype = OriginalRTCPeerConnection.prototype;
            makeNative(window, 'RTCPeerConnection');
        }

        if (CONFIG.HIDE_CONNECTION && 'connection' in navigator) {
            const connection = navigator.connection;
            if (connection) {
                Object.defineProperty(connection, 'effectiveType', { get: () => '4g' });
                Object.defineProperty(connection, 'downlink', { get: () => 10 });
                Object.defineProperty(connection, 'rtt', { get: () => 50 });
            }
        }

        if (CONFIG.HIDE_WEBDRIVER && navigator.webdriver !== undefined) {
            Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
        }

        if (CONFIG.FAKE_TIMEZONE && Intl.DateTimeFormat) {
            const origResolvedOptions = Intl.DateTimeFormat.prototype.resolvedOptions;
            Intl.DateTimeFormat.prototype.resolvedOptions = function() {
                const options = origResolvedOptions.call(this);
                options.timeZone = 'Europe/Moscow';
                return options;
            };
            makeNative(Intl.DateTimeFormat.prototype, 'resolvedOptions');
        }

        if (CONFIG.FAKE_LANGUAGE) {
            Object.defineProperty(navigator, 'language', { get: () => 'ru-RU' });
            Object.defineProperty(navigator, 'languages', { get: () => ['ru-RU', 'ru'] });
        }

        if (CONFIG.FAKE_PLUGINS && navigator.plugins) {
            const fakePlugins = [
                { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
                { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
                { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' }
            ];
            const pluginArray = Object.create(PluginArray.prototype);
            pluginArray.length = fakePlugins.length;
            fakePlugins.forEach((p, i) => {
                pluginArray[i] = p;
                pluginArray[p.name] = p;
            });
            pluginArray.item = (index) => pluginArray[index];
            pluginArray.namedItem = (name) => fakePlugins.find(p => p.name === name) || null;
            Object.defineProperty(pluginArray, Symbol.toStringTag, { value: 'PluginArray', configurable: true });
            Object.defineProperty(navigator, 'plugins', { get: () => pluginArray, configurable: true });

            const fakeMimes = [
                { type: 'application/pdf', suffixes: 'pdf', description: '' },
                { type: 'text/pdf', suffixes: 'pdf', description: '' }
            ];
            const mimeArray = Object.create(MimeTypeArray.prototype);
            mimeArray.length = fakeMimes.length;
            fakeMimes.forEach((m, i) => {
                mimeArray[i] = m;
                mimeArray[m.type] = m;
            });
            mimeArray.item = (index) => mimeArray[index];
            mimeArray.namedItem = (type) => fakeMimes.find(m => m.type === type) || null;
            Object.defineProperty(mimeArray, Symbol.toStringTag, { value: 'MimeTypeArray', configurable: true });
            Object.defineProperty(navigator, 'mimeTypes', { get: () => mimeArray, configurable: true });
        }

        if (CONFIG.FAKE_FONTS) {
            if (document.fonts && document.fonts.query) {
                const origQuery = document.fonts.query;
                document.fonts.query = function() {
                    return Promise.resolve(['Arial', 'Verdana', 'Times New Roman', 'Courier New', 'Georgia']);
                };
                makeNative(document.fonts, 'query');
            }
            const orgMeasureText = CanvasRenderingContext2D.prototype.measureText;
            CanvasRenderingContext2D.prototype.measureText = function(text) {
                const metrics = orgMeasureText.call(this, text);
                if (metrics.width) {
                    const originalWidth = metrics.width;
                    Object.defineProperty(metrics, 'width', {
                        get: () => originalWidth + (Math.random() * 0.1 - 0.05)
                    });
                }
                return metrics;
            };
        }

        if (CONFIG.FAKE_BATTERY && navigator.getBattery) {
            const origGetBattery = navigator.getBattery;
            navigator.getBattery = function() {
                const fakeBattery = {
                    charging: false,
                    level: 1,
                    chargingTime: Infinity,
                    dischargingTime: Infinity,
                    addEventListener: () => {},
                    removeEventListener: () => {},
                    dispatchEvent: () => true
                };
                return Promise.resolve(fakeBattery);
            };
            makeNative(navigator, 'getBattery');
        }

        if (CONFIG.FIXED_SCREEN && window.screen) {
            const w = CONFIG.SCREEN_WIDTH;
            const h = CONFIG.SCREEN_HEIGHT;
            const cd = CONFIG.COLOR_DEPTH;
            Object.defineProperty(screen, 'width', { get: () => w, configurable: true });
            Object.defineProperty(screen, 'height', { get: () => h, configurable: true });
            Object.defineProperty(screen, 'availWidth', { get: () => w, configurable: true });
            Object.defineProperty(screen, 'availHeight', { get: () => h, configurable: true });
            Object.defineProperty(screen, 'colorDepth', { get: () => cd, configurable: true });
            Object.defineProperty(screen, 'pixelDepth', { get: () => cd, configurable: true });
        }

        if (CONFIG.BLOCK_SERVICE_WORKER && navigator.serviceWorker && navigator.serviceWorker.register) {
            const originalRegister = navigator.serviceWorker.register;
            navigator.serviceWorker.register = function(scriptURL, options) {
                logBlock('SW', `blocked: ${scriptURL}`);
                return Promise.reject(new Error('Service Worker registration blocked'));
            };
            makeNative(navigator.serviceWorker, 'register');
        } else if (CONFIG.LOG_SERVICE_WORKER && navigator.serviceWorker && navigator.serviceWorker.register) {
            const originalRegister = navigator.serviceWorker.register;
            navigator.serviceWorker.register = function(scriptURL, options) {
                console.log("📦 Service Worker registered:", scriptURL);
                return originalRegister.call(this, scriptURL, options);
            };
            makeNative(navigator.serviceWorker, 'register');
        }

    } catch (e) {}

    // ========================== ИНТЕРФЕЙС В SHADOW DOM ==========================
    function createUI() {
        if (document.getElementById('privacy-monitor-shadow-host')) return;
        if (!document.body) {
            setTimeout(createUI, 100);
            return;
        }

        const host = document.createElement('div');
        host.id = 'privacy-monitor-shadow-host';
        host.style.cssText = 'all: initial; display: block;';
        document.body.appendChild(host);

        const shadow = host.attachShadow({ mode: 'open' });
        uiShadow = shadow;

        const style = document.createElement('style');
        style.textContent = `
            #pm-log-list::-webkit-scrollbar {
                width: 6px;
                height: 6px;
            }
            #pm-log-list::-webkit-scrollbar-track {
                background: rgba(255, 255, 255, 0.05);
                border-radius: 3px;
            }
            #pm-log-list::-webkit-scrollbar-thumb {
                background: rgba(255, 255, 255, 0.2);
                border-radius: 3px;
            }
            #pm-log-list::-webkit-scrollbar-thumb:hover {
                background: rgba(255, 255, 255, 0.3);
            }
        `;
        shadow.appendChild(style);

        const main = document.createElement('div');
        main.id = 'privacy-monitor';
        Object.assign(main.style, {
            position: 'fixed', bottom: '15px', right: '20px', zIndex: '2147483647',
            background: 'rgba(15, 15, 15, 0.7)', color: '#00ff00', padding: '10px 14px',
            borderRadius: '10px', fontSize: '11px', fontFamily: 'monospace',
            boxShadow: '0 8px 32px rgba(0,0,0,0.5)', backdropFilter: 'blur(10px)', pointerEvents: 'auto'
        });

        main.innerHTML = `
            <div id="pm-header" style="display: flex; justify-content: space-between; align-items: center; min-width: 130px; cursor: pointer;">
                <span>🪬 BLINDER: <span id="pm-counter">${blockedCount}</span></span>
                <span id="pm-arrow">▲</span>
            </div>
            <div id="pm-content" style="display: none; margin-top: 10px; border-top: 1px solid rgba(255,255,255,0.2); padding-top: 8px; width: 260px;">
                <div id="pm-log-list" style="max-height: 150px; overflow-y: auto; margin-bottom: 8px;"></div>
                <div style="display: flex; gap: 6px;">
                    <button id="pm-copy" style="flex: 1; background: rgba(50,50,50,0.8); color: #0f0; border: none; padding: 2px 4px; cursor: pointer; border-radius: 3px; font-size: 9px; height: 18px;">Copy log</button>
                    <button id="pm-clear" style="flex: 1; background: rgba(50,50,50,0.8); color: #f44; border: none; padding: 2px 4px; cursor: pointer; border-radius: 3px; font-size: 9px; height: 18px;">Clear log</button>
                </div>
            </div>
        `;

        main.querySelector('#pm-header').onclick = () => {
            const content = main.querySelector('#pm-content');
            const arrow = main.querySelector('#pm-arrow');
            const isOpen = content.style.display === 'block';
            content.style.display = isOpen ? 'none' : 'block';
            arrow.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(180deg)';
        };

        main.querySelector('#pm-copy').onclick = async (e) => {
            e.stopPropagation();
            const btn = e.target;
            const originalText = btn.innerText;
            try {
                await navigator.clipboard.writeText(fullLogHistory.join('\n'));
                btn.innerText = 'Copied!';
                setTimeout(() => btn.innerText = originalText, 1000);
            } catch (err) {
                console.warn('Clipboard API failed, trying fallback...', err);
                try {
                    const textarea = document.createElement('textarea');
                    textarea.value = fullLogHistory.join('\n');
                    document.body.appendChild(textarea);
                    textarea.select();
                    document.execCommand('copy');
                    document.body.removeChild(textarea);
                    btn.innerText = 'Copied!';
                    setTimeout(() => btn.innerText = originalText, 1000);
                } catch (fallbackErr) {
                    console.error('Fallback copy failed:', fallbackErr);
                    btn.innerText = 'Error!';
                    setTimeout(() => btn.innerText = originalText, 1500);
                }
            }
        };

        main.querySelector('#pm-clear').onclick = (e) => {
            e.stopPropagation();
            blockedCount = 0;
            fullLogHistory = [];
            pendingLogs = [];
            main.querySelector('#pm-counter').innerText = '0';
            main.querySelector('#pm-log-list').innerHTML = '';
        };

        shadow.appendChild(main);

        const logContainer = shadow.getElementById('pm-log-list');
        while (pendingLogs.length > 0) {
            addEntryToLog(logContainer, pendingLogs.shift());
        }
        shadow.getElementById('pm-counter').innerText = blockedCount;
    }

    // ========================== ОЧИСТКА КЭША ДЕДУПЛИКАЦИИ ==========================
    setInterval(() => {
        const now = Date.now();
        for (const [key, ts] of lastRecorded.entries()) {
            if (now - ts > 10000) {
                lastRecorded.delete(key);
            }
        }
    }, 10000);

    // ========================== ЗАПУСК ==========================
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => setTimeout(createUI, 500));
    } else {
        setTimeout(createUI, 500);
    }
    setInterval(() => {
        if (!document.getElementById('privacy-monitor-shadow-host') && document.body) {
            createUI();
        }
    }, 2000);
})();