Deeper Tools

Набор инструментов для Deeper.

// ==UserScript==
// @name         Deeper Tools
// @description  Набор инструментов для Deeper.
// @namespace    http://tampermonkey.net/
// @version      4.1
// @author       https://github.com/lReDragol
// @icon         https://avatars.mds.yandex.net/get-socsnippets/10235467/2a0000019509580bc84108597cea65bc46ee/square_83
// @match        http://34.34.34.34/*
// @match        http://11.22.33.44/*
// @match        *://*/*
// @license      MIT
// @run-at       document-start
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';
    const ALLOWED_HOSTS = new Set(['34.34.34.34', '11.22.33.44']);
    const isAllowedHost = () => location.protocol === 'http:' && ALLOWED_HOSTS.has(location.hostname);
    const palettes = {
        default: {},
        green: {
            '--bg-primary': '#2a2a2a',
            '--bg-secondary': '#383838',
            '--bg-tertiary': '#454545',
            '--text-primary': '#d0f4d0',
            '--text-secondary': '#b0e8b0',
            '--text-muted': '#8ac48a',
            '--accent': '#4caf50',
            '--border-primary': '#4d4d4d',
            '--border-light': '#777777',
            '--hover-bg': '#505050',
            '--active-bg': '#626262',
            '--highlight': '#6ee76e',
            '--disabled-bg': '#2f2f2f'
        },
        red: {
            '--bg-primary': '#2b1a1a',
            '--bg-secondary': '#3d1f1f',
            '--bg-tertiary': '#502525',
            '--text-primary': '#ffe6e6',
            '--text-secondary': '#ffb3b3',
            '--text-muted': '#cc7f7f',
            '--accent': '#ff4d4d',
            '--border-primary': '#661010',
            '--border-light': '#993333',
            '--hover-bg': '#661515',
            '--active-bg': '#7a1a1a',
            '--highlight': '#ff7f7f',
            '--disabled-bg': '#2f1c1c'
        },
        purple: {
            '--bg-primary': '#1a1a2a',
            '--bg-secondary': '#28283a',
            '--bg-tertiary': '#35354b',
            '--text-primary': '#c0c0e8',
            '--text-secondary': '#9e9ede',
            '--text-muted': '#8a8abf',
            '--accent': '#e0e0f8',
            '--border-primary': '#3d3d4d',
            '--border-light': '#777787',
            '--hover-bg': '#505050',
            '--active-bg': '#626262',
            '--highlight': '#8f8fdf',
            '--disabled-bg': '#2f2f2f'
        }
    };

    const themeNames = Object.keys(palettes);
    let currentThemeIndex = GM_getValue('deeperThemeIndex', 0);

    const CSS_BASE = `
    * { background: transparent !important; color: var(--text-primary) !important; border-color: var(--border-primary) !important; }
    *::before, *::after { background: transparent !important; }
    html, body, [class*="bg-"], [style*="background"] { background-color: var(--bg-primary) !important; background-image: none !important; }
    h1, h2, h3, h4, h5, h6, p, span, label, div, li { color: var(--text-primary) !important; }
    a, a * { color: var(--accent) !important; }
    table, thead, tbody, tr, th, td { background: var(--bg-tertiary) !important; color: var(--text-primary) !important; border-color: var(--border-primary) !important; }
    button, input, select, textarea, .ant-btn, .ant-input, .ant-select-selector, .ant-input-affix-wrapper {
      background: var(--bg-secondary) !important; color: var(--text-primary) !important; border: 1px solid var(--border-light) !important;
    }
    button:hover, .ant-btn:hover, input:hover, select:hover, textarea:hover, .ant-input:hover, .ant-select-selector:hover {
      background: var(--hover-bg) !important;
    }
    button:active, .ant-btn:active { background: var(--active-bg) !important; }
    button[disabled], input[disabled], select[disabled], textarea[disabled], .ant-btn[disabled], .ant-input[disabled] {
      background: var(--disabled-bg) !important; color: var(--text-muted) !important; cursor: not-allowed !important; opacity: 0.6 !important;
    }
    .ant-layout, .ant-layout-header, .ant-layout-sider, .ant-layout-content, .ant-layout-footer {
      background: var(--bg-primary) !important; color: var(--text-primary) !important;
    }
    .ant-card, .card, .panel { background: var(--bg-terтиary) !important; box-shadow: none !important; color: var(--text-primary) !important; }
    .ant-menu, .ant-menu-item, .ant-menu-submenu, .ant-menu-item-group-title {
      background: var(--bg-secondary) !important; color: var(--text-primary) !important;
    }
    .ant-modal-content, .ant-popover-inner-content, .ant-popover-title {
      background: var(--bg-secondary) !important; color: var(--text-primary) !important; border-color: var(--border-primary) !important;
    }
    .ant-tooltip-inner { background: var(--bg-secondary) !important; color: var(--text-primary) !important; }
    .ant-tabs-nav, .ant-tabs-tab, .ant-tabs-tab-active, .ant-tabs-content-holder {
      background: var(--bg-secondary) !important; color: var(--text-primary) !important;
    }
    .ant-tag, .ant-tag-green, .ant-badge-status-success, .ant-badge-status-default {
      background: var(--bg-tertiary) !important; color: var(--text-primary) !important;
    }
    .anticon, .anticon svg { color: var(--accent) !important; fill: var(--accent) !important; }
    ::-webkit-scrollbar { width: 8px; background: var(--bg-secondary); }
    ::-webkit-scrollbar-thumb { background: var(--border-primary); border-radius: 4px; }
    ::selection { background: var(--highlight) !important; color: var(--bg-primary) !important; }
    .tm-sticky-controls { position: sticky; bottom: 0; z-index: 999; background: var(--bg-secondary); padding: 8px; display: flex; justify-content: space-between; align-items: center; }
    .page-list-container { margin: 1.5rem 0; display: flex; flex-direction: column; gap: 0.75rem; }
    .page-list-item { position: relative; padding: 1rem 3rem 1rem 1rem; background: var(--bg-tertiary); border: 1px solid var(--border-primary); border-radius: 0.5rem; }
    .page-list-item .delete-button { position: absolute; top: 0.5rem; right: 0.5rem; background: transparent; border: none; color: var(--text-secondary); font-size: 1.25rem; line-height: 1; cursor: pointer; }
    .page-list-item .delete-button:hover { color: var(--accent); }
  `;

    function applyTheme(idx) {
        GM_setValue('deeperThemeIndex', idx);
        const styleId = 'deeper-theme-style';
        let styleEl = document.getElementById(styleId);
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = styleId;
            document.head.appendChild(styleEl);
        }
        const name = themeNames[idx];
        if (name === 'default') {
            styleEl.textContent = '';
        } else {
            const pal = palettes[name];
            const vars = Object.entries(pal).map(([k, v]) => `${k}: ${v};`).join('\n');
            styleEl.textContent = `:root { ${vars} } ${CSS_BASE}`;
        }
    }
    if (isAllowedHost()) applyTheme(currentThemeIndex);

    function gmFetch(url, init = {}) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: init.method || 'GET',
                url,
                headers: init.headers || {},
                data: init.body || null,
                timeout: init.timeout || 30000,
                onload: function (response) {
                    response.json = () => Promise.resolve(JSON.parse(response.responseText || 'null'));
                    resolve(response);
                },
                onerror: reject,
                ontimeout: () => reject(new Error('GM_xmlhttpRequest: timeout'))
            });
        });
    }

    (function installAutoLogin(){
        if (!isAllowedHost()) return;

        function isLoginLike() {
            try {
                const p = location.pathname.replace(/\/+$/,'').toLowerCase();
                if (p === '/login' || p.endsWith('/admin/login')) return true;
                if (location.hash && /login/i.test(location.hash)) return true;
                if (location.search && /login/i.test(location.search)) return true;
                return false;
            } catch { return false; }
        }

        // Захват логина/пароля из обычной формы (submit), если не XHR/fetch
        function captureCredsFromForm() {
            try {
                const form = document.querySelector('form');
                if (!form) return;
                const userEl = form.querySelector('input[name="username"], input[type="text"], input[autocomplete="username"]');
                const passEl = form.querySelector('input[type="password"], input[name="password"], input[autocomplete="current-password"]');
                if (!passEl) return;

                const store = () => {
                    const u = (userEl && userEl.value || 'admin').trim();
                    const p = (passEl.value || '').trim();
                    if (u) GM_setValue('adminUsername', u);
                    if (p) {
                        GM_setValue('adminPassword', p);
                        console.log('[Deeper Tools] Пароль сохранён из формы.');
                    }
                };
                form.addEventListener('submit', store, { capture: true });
                passEl.addEventListener('change', store);
            } catch (e) {
                console.warn('[Deeper Tools] captureCredsFromForm error:', e);
            }
        }

        async function doAutoLogin() {
            const pwd = GM_getValue('adminPassword');
            const uname = GM_getValue('adminUsername') || 'admin';
            if (!pwd) {
                console.log('[Deeper Tools] Автологин: нет сохранённых учётных данных.');
                return;
            }
            try {
                console.log('[Deeper Tools] Автологин: POST /api/admin/login …');
                const r = await gmFetch(`${location.origin}/api/admin/login`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ username: uname, password: pwd })
                });
                const status = r.status;
                let body = null;
                try { body = await r.json(); } catch {}
                console.log('[Deeper Tools] Автологин: статус', status, body);
                if (status === 200) {
                    location.href = '/admin/dashboard';
                } else {
                    console.warn('[Deeper Tools] Автологин не принят сервером. Проверьте username/password/endpoint.');
                }
            } catch (e) {
                console.error('[Deeper Tools] Автологин ошибка:', e);
            }
        }

        if (isLoginLike()) {
            doAutoLogin();
            window.addEventListener('load', doAutoLogin, { once: true });

            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', captureCredsFromForm, { once: true });
            } else {
                captureCredsFromForm();
            }
        }
    })();

    /* ---------------------------------
   * Пейджинг/агрегатор для whitelist/blacklist
   * --------------------------------- */
    const PAGING = { SIZE: 100, MAX_PAGES: 500 };
    const isTargetEndpoint = (p) =>
    p === '/api/smartRoute/getRoutingWhitelist/domain' ||
          p === '/api/smartRoute/getRoutingBlacklist/domain';

    function mustAggregateUrl(urlStr, method = 'GET') {
        if (!isAllowedHost()) return false;
        try {
            const u = new URL(urlStr, location.href);
            if (!isTargetEndpoint(u.pathname)) return false;
            if (u.searchParams.get('__tm_bypass_all') === '1') return false;
            return String(method || 'GET').toUpperCase() === 'GET';
        } catch { return false; }
    }

    async function fetchAllPagesViaFetch(origFetch, baseUrl, init) {
        const all = [];
        let pageNo = 1;
        while (pageNo <= PAGING.MAX_PAGES) {
            const u = new URL(baseUrl);
            u.searchParams.set('pageNo', String(pageNo));
            u.searchParams.set('pageSize', String(PAGING.SIZE));
            u.searchParams.set('__tm_bypass_all', '1');

            const r = await origFetch(u.toString(), init);
            const d = await r.json();
            const list = Array.isArray(d.list) ? d.list : [];
            if (list.length) all.push(...list);
            if (list.length < PAGING.SIZE) return { template: d, list: all };
            pageNo++;
        }
        return { template: {}, list: all };
    }

    function dedupList(list) {
        const seen = new Set();
        const out = [];
        for (const it of list) {
            const key = it?.id ?? it?.domain ?? it?.domainName ?? JSON.stringify(it);
            if (!seen.has(key)) { seen.add(key); out.push(it); }
        }
        return out;
    }

    function defineRO(obj, prop, val) {
        try { Object.defineProperty(obj, prop, { configurable: true, get: () => val }); } catch {}
    }

    /* ---------------------------------
   * Улучшения UI списка доменов
   * --------------------------------- */
    function injectListCSS() {
        if (!isAllowedHost()) return;
        if (document.getElementById('dc-long-list-style')) return;
        const style = document.createElement('style');
        style.id = 'dc-long-list-style';
        style.textContent = `
      .ant-table-body, .ant-table-content, .ant-spin-nested-loading, .ant-spin-container {
        max-height: none !important;
        overflow: visible !important;
      }
      .ant-table table { table-layout: auto !important; }
    `;
      document.documentElement.appendChild(style);
  }

    function placeSearchNearLeftButtons() {
        if (!isAllowedHost()) return;

        const existingInput = document.getElementById('dc-domain-search');
        if (existingInput && document.body.contains(existingInput)) return;

        const allButtons = Array.from(document.querySelectorAll('button, .ant-btn'));
        const addButtons   = allButtons.filter(b => /Добавить/i.test(b.textContent || ''));
        const importButtons= allButtons.filter(b => /Импорт/i.test(b.textContent || ''));
        const exportButtons= allButtons.filter(b => /Экспорт/i.test(b.textContent || ''));

        if (!addButtons.length) return;

        let leftMostAddButton = addButtons[0];
        let minLeft = Infinity;
        for (const btn of addButtons) {
            const r = btn.getBoundingClientRect();
            if (r.left < minLeft) { minLeft = r.left; leftMostAddButton = btn; }
        }

        let container = leftMostAddButton;
        for (let i = 0; i < 8 && container; i++) {
            container = container.parentElement;
            if (!container) break;
            const texts = Array.from(container.querySelectorAll('button, .ant-btn')).map(el => (el.textContent || '').toLowerCase());
            const hasAdd    = texts.some(t => t.includes('добавить'));
            const hasImport = texts.some(t => t.includes('импорт'));
            const hasExport = texts.some(t => t.includes('экспорт'));
            if (hasAdd && hasImport && hasExport) break;
        }
        if (!container) container = leftMostAddButton.parentElement || document.body;

        const input = document.createElement('input');
        input.id = 'dc-domain-search';
        input.type = 'text';
        input.placeholder = 'Поиск доменов...';
        Object.assign(input.style, {
            height: '40px',
            padding: '0 12px',
            border: '1px solid rgba(0,0,0,.25)',
            borderRadius: '8px',
            marginRight: '8px',
            minWidth: '280px',
            flex: '0 0 auto',
            outline: 'none'
        });

        const cardOrTableRoot =
              leftMostAddButton.closest('.ant-card, [class*="card"], .ant-table-wrapper') ||
              container.closest('.ant-card, [class*="card"], .ant-table-wrapper') ||
              document.body;

        // функция фильтрации строк tbody
        const applyFilter = () => {
            const query = input.value.trim().toLowerCase();
            const table = cardOrTableRoot.querySelector('table') || document.querySelector('table');
            const tbody = table ? (table.tBodies && table.tBodies[0]) : cardOrTableRoot.querySelector('tbody');
            if (!tbody) return;
            tbody.querySelectorAll('tr').forEach(tr => {
                const text = (tr.textContent || '').toLowerCase();
                tr.style.display = !query || text.includes(query) ? '' : 'none';
            });
        };
        input.addEventListener('input', applyFilter);

        try {
            const directParent = leftMostAddButton.parentElement;
            if (directParent && directParent.contains(leftMostAddButton)) {
                directParent.insertBefore(input, leftMostAddButton);
            } else if (container && container.contains(leftMostAddButton)) {
                container.insertBefore(input, container.firstChild || null);
            } else {
                leftMostAddButton.before(input);
            }
        } catch (e) {
            console.warn('[Deeper Tools] insertBefore fallback:', e);
            (leftMostAddButton.parentElement || container || document.body).prepend(input);
        }

        const tbody = (cardOrTableRoot.querySelector('table') || {}).tBodies?.[0] || cardOrTableRoot.querySelector('tbody');
        if (tbody) {
            new MutationObserver(applyFilter).observe(tbody, { childList: true, subtree: true });
        }
    }


    function bootDomainPageHelpers() {
        injectListCSS();
        placeSearchNearLeftButtons();
    }

    /* ======================================================================
   * Domain Scanner — ГЛОБАЛЬНЫЙ (доступен везде через пункт меню)
   * ====================================================================== */

    function getScannerEnabled() { return GM_getValue('domainScannerEnabled', false); }
    function setScannerEnabled(val) {
        GM_setValue('domainScannerEnabled', val);
        updateScannerMenuCommand();
        if (!val) {
            const c = document.getElementById('domain-scanner-container');
            if (c) c.remove();
        } else {
            ensureScannerContainer();
        }
        console.log('[Deeper Tools] Domain Scanner: ' + (val ? 'ON' : 'OFF'));
    }

    if (!window.__deeper_hooks_installed__) {
        window.__deeper_hooks_installed__ = true;

        // Перехват XHR
        const nativeOpen  = XMLHttpRequest.prototype.open;
        const nativeSend  = XMLHttpRequest.prototype.send;
        const nativeSetRH = XMLHttpRequest.prototype.setRequestHeader;

        XMLHttpRequest.prototype.open = function (method, url) {
            this._method  = method;
            this._headers = {};
            try { this._urlObj = new URL(url, location.href); } catch (_) { this._urlObj = null; }
            if (getScannerEnabled() && this._urlObj) { try { addDomain(this._urlObj.hostname); } catch {} }
            return nativeOpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.setRequestHeader = function (k, v) {
            try { if (!this._headers) this._headers = {}; this._headers[k] = v; } catch {}
            return nativeSetRH.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function (body) {
            // Захват логина/пароля из /api/admin/login (JSON, URL-encoded, FormData)
            try {
                if (
                    this._urlObj &&
                    isAllowedHost() &&
                    this._urlObj.pathname.startsWith('/api/admin/login') &&
                    String(this._method || '').toUpperCase() === 'POST'
                ) {
                    let pwd = null, uname = null;
                    if (typeof body === 'string') {
                        try {
                            if (body.trim().startsWith('{')) {
                                const p = JSON.parse(body);
                                pwd = p && p.password;
                                uname = p && (p.username || p.login || p.user);
                            } else {
                                const usp = new URLSearchParams(body);
                                pwd = usp.get('password');
                                uname = usp.get('username') || usp.get('login') || usp.get('user');
                            }
                        } catch {}
                    } else if (body && typeof body === 'object') {
                        try {
                            if (typeof body.get === 'function') {
                                pwd = body.get('password') || pwd;
                                uname = body.get('username') || body.get('login') || body.get('user') || uname;
                            }
                        } catch {}
                    }
                    if (uname) GM_setValue('adminUsername', uname);
                    if (pwd && !GM_getValue('adminPassword')) {
                        GM_setValue('adminPassword', pwd);
                        console.log('[Deeper Tools] Пароль сохранён из XHR.');
                    }
                }
            } catch {}

            // Агрегатор для whitelist/blacklist через XHR/axios
            if (this._urlObj && mustAggregateUrl(this._urlObj.toString(), this._method)) {
                const base = new URL(this._urlObj.toString(), location.href);
                base.searchParams.set('pageNo', '1');
                base.searchParams.set('pageSize', String(PAGING.SIZE));

                fetchAllPagesViaFetch(window.fetch.bind(window), base, {
                    credentials: 'include',
                    headers: this._headers || {}
                }).then(({ template, list }) => {
                    const deduped = dedupList(list);
                    const payloadObj = { ...template, list: deduped, total: deduped.length };
                    const text = JSON.stringify(payloadObj);

                    defineRO(this, 'readyState', 4);
                    defineRO(this, 'status', 200);
                    defineRO(this, 'statusText', 'OK');
                    defineRO(this, 'responseURL', base.toString());
                    try {
                        this.getAllResponseHeaders = () => 'content-type: application/json; charset=utf-8\r\n';
                        this.getResponseHeader = (h) => (String(h).toLowerCase() === 'content-type' ? 'application/json; charset=utf-8' : null);
                    } catch {}

                    if (this.responseType === 'json') {
                        defineRO(this, 'response', payloadObj);
                    } else {
                        defineRO(this, 'response', text);
                        defineRO(this, 'responseText', text);
                    }

                    try { if (typeof this.onreadystatechange === 'function') this.onreadystatechange(new Event('readystatechange')); } catch {}
                    try { if (typeof this.onload === 'function') this.onload(new Event('load')); } catch {}
                    try {
                        this.dispatchEvent && this.dispatchEvent(new Event('readystatechange'));
                        this.dispatchEvent && this.dispatchEvent(new Event('load'));
                        this.dispatchEvent && this.dispatchEvent(new Event('loadend'));
                    } catch {}
                }).catch(e => {
                    console.error('[Deeper Tools] XHR aggregate error:', e);
                    try { nativeSend.apply(this, arguments); } catch {}
                });
                return;
            }

            return nativeSend.apply(this, arguments);
        };

        // Перехват fetch (агрегатор + сканер доменов + сохранение пароля)
        const originalFetch = window.fetch;
        window.fetch = function (input, init) {
            // Domain Scanner: подхват доменов
            if (getScannerEnabled()) {
                try {
                    const url = (typeof input === 'string') ? input : input.url;
                    const u = new URL(url, location.href);
                    addDomain(u.hostname);
                } catch {}
            }

            // Захват логина/пароля из fetch /api/admin/login (JSON, URL-encoded, FormData)
            try {
                if (isAllowedHost()) {
                    const urlStr = typeof input === 'string' ? input : input.url;
                    const u = new URL(urlStr, location.href);
                    const method = (init && init.method ? String(init.method) : (typeof input !== 'string' && input?.method) || 'GET').toUpperCase();

                    if (u.pathname.startsWith('/api/admin/login') && method === 'POST' && init && init.body && !GM_getValue('adminPassword')) {
                        let pwd = null, uname = null;
                        if (typeof init.body === 'string') {
                            try {
                                if (init.body.trim().startsWith('{')) {
                                    const p = JSON.parse(init.body);
                                    pwd = p && p.password;
                                    uname = p && (p.username || p.login || p.user);
                                } else {
                                    const usp = new URLSearchParams(init.body);
                                    pwd = usp.get('password');
                                    uname = usp.get('username') || usp.get('login') || usp.get('user');
                                }
                            } catch {}
                        } else if (typeof init.body === 'object') {
                            try {
                                if (typeof init.body.get === 'function') {
                                    pwd = init.body.get('password') || pwd; // FormData
                                    uname = init.body.get('username') || init.body.get('login') || init.body.get('user') || uname;
                                }
                            } catch {}
                        }
                        if (uname) GM_setValue('adminUsername', uname);
                        if (pwd) {
                            GM_setValue('adminPassword', pwd);
                            console.log('[Deeper Tools] Пароль сохранён из fetch.');
                        }
                    }
                }
            } catch {}

            // Агрегатор для whitelist/blacklist
            try {
                const urlStr = typeof input === 'string' ? input : input.url;
                const method = init?.method || (typeof input !== 'string' && input?.method);
                if (mustAggregateUrl(urlStr, method)) {
                    const base = new URL(urlStr, location.href);
                    base.searchParams.set('pageNo', '1');
                    base.searchParams.set('pageSize', String(PAGING.SIZE));
                    const headers = (init && init.headers) || {};
                    const fInit = { credentials: 'include', headers };
                    return fetchAllPagesViaFetch(originalFetch, base, fInit).then(({ template, list }) => {
                        const deduped = dedupList(list);
                        const payload = { ...template, list: deduped, total: deduped.length };
                        return new Response(JSON.stringify(payload), {
                            status: 200,
                            headers: { 'Content-Type': 'application/json; charset=utf-8' }
                        });
                    }).catch(e => {
                        console.error('[Deeper Tools] fetch aggregate error:', e);
                        return originalFetch.apply(this, arguments);
                    });
                }
            } catch {}

            return originalFetch.apply(this, arguments);
        };

        // Отслеживание ресурсов, добавленных в DOM (сканер)
        const resObserver = new MutationObserver(mutations => {
            if (!getScannerEnabled()) return;
            for (const m of mutations) {
                if (!m.addedNodes) continue;
                for (const node of m.addedNodes) {
                    if (node && node.tagName) {
                        const src = node.src || node.href;
                        if (src) {
                            try { addDomain(new URL(src, location.href).hostname); } catch {}
                        }
                    }
                }
            }
        });
        resObserver.observe(document.documentElement, { childList: true, subtree: true });
    }

    // По performance timeline (сканер)
    setInterval(() => {
        if (!getScannerEnabled()) return;
        const entries = performance.getEntriesByType('resource') || [];
        for (const entry of entries) {
            try { addDomain(new URL(entry.name, location.href).hostname); } catch {}
        }
    }, 1000);

    // Набор доменов и статус (сканер)
    const domainSet = new Set();
    const domainStatus = new Map();

    function addDomain(domain) {
        if (!domain) return;
        if (!domainSet.has(domain)) {
            domainSet.add(domain);
            domainStatus.set(domain, 'testing');
            testDomainAvailability(domain);
        }
        updateDomainList();
        updateStats();
    }

    async function testDomainAvailability(domain) {
        const schemes = location.protocol === 'https:' ? ['https:', 'http:'] : ['http:', 'https:'];
        for (const scheme of schemes) {
            const ok = await testWithScheme(domain, scheme).catch(() => false);
            if (ok) { domainStatus.set(domain, 'ok'); updateDomainList(); updateStats(); return; }
        }
        domainStatus.set(domain, 'blocked');
        updateDomainList(); updateStats();
    }

    function testWithScheme(domain, scheme) {
        return new Promise((resolve) => {
            const url = scheme + '//' + domain + '/favicon.ico';
            GM_xmlhttpRequest({
                method: 'GET',
                url,
                timeout: 7000,
                onload: function (res) {
                    const st = res.status || 0;
                    resolve(st >= 200 && st < 400);
                },
                onerror: function () { resolve(false); },
                ontimeout: function () { resolve(false); }
            });
        });
    }

    function ensureScannerContainer() {
        if (!getScannerEnabled()) return;
        if (document.getElementById('domain-scanner-container')) return;

        const container = document.createElement('div');
        container.id = 'domain-scanner-container';
        Object.assign(container.style, {
            position: 'fixed', top: '10px', right: '10px', width: '340px', maxHeight: '80vh',
            overflowY: 'auto', backgroundColor: 'white', border: '1px solid black', zIndex: 10000,
            padding: '10px', fontSize: '12px', fontFamily: 'monospace', color: 'black', whiteSpace: 'pre-wrap'
        });

        // Статистика
        const statsBox = document.createElement('div');
        statsBox.id = 'domain-stats';
        statsBox.style.marginBottom = '8px';

        function makeStatRow(labelId, labelText) {
            const wrap = document.createElement('div');
            wrap.style.marginBottom = '6px';

            const label = document.createElement('div');
            label.id = labelId + '-label';
            label.textContent = labelText + ': 0 / 0';
            label.style.marginBottom = '2px';

            const bar = document.createElement('div');
            bar.className = 'progress-outer';
            Object.assign(bar.style, {
                height: '6px', background: '#eee', border: '1px solid #bbb', borderRadius: '3px', overflow: 'hidden'
            });
            const inner = document.createElement('div');
            inner.id = labelId + '-bar';
            Object.assign(inner.style, {
                height: '100%', width: '0%', background: labelId.includes('allowed') ? '#3cba54' : '#db3236'
            });
            bar.appendChild(inner);

            wrap.appendChild(label);
            wrap.appendChild(bar);
            return wrap;
        }

        statsBox.appendChild(makeStatRow('allowed', 'Разрешённые'));
        statsBox.appendChild(makeStatRow('blocked', 'Запрещённые'));
        container.appendChild(statsBox);

        // Список доменов
        const domainList = document.createElement('div');
        domainList.id = 'domain-list';
        container.appendChild(domainList);

        // Кнопка добавления в deeper
        const addBtn = document.createElement('button');
        addBtn.id = 'add-to-deeper-btn';
        addBtn.textContent = 'Добавить в deeper';
        Object.assign(addBtn.style, {
            display: 'block', width: '100%', marginTop: '10px', padding: '6px 10px',
            backgroundColor: '#f8f8f8', border: '1px solid #ccc', borderRadius: '4px', cursor: 'pointer', fontSize: '14px'
        });
        addBtn.addEventListener('click', addToDeeper);
        container.appendChild(addBtn);

        document.body.appendChild(container);
        updateStats();
    }

    function updateDomainList() {
        const container = document.getElementById('domain-scanner-container');
        if (!container) return;
        const listEl = container.querySelector('#domain-list');
        const checked = {};
        listEl.querySelectorAll('.domain-checkbox').forEach(cb => { checked[cb.dataset.domain] = cb.checked; });

        const sortedArr = Array.from(domainSet).sort();
        listEl.innerHTML = '';
        sortedArr.forEach(domain => {
            const row = document.createElement('div');
            Object.assign(row.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' });

            const left = document.createElement('div');
            left.style.display = 'flex';
            left.style.alignItems = 'center';
            left.style.gap = '6px';

            const s = domainStatus.get(domain);
            const icon = document.createElement('span');
            icon.className = 'domain-status-icon';
            icon.textContent = s === 'ok' ? '✅' : (s === 'blocked' ? '❌' : '⏳');

            const t = document.createElement('span');
            t.textContent = domain;

            left.appendChild(icon);
            left.appendChild(t);

            const cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.classList.add('domain-checkbox');
            cb.dataset.domain = domain;
            cb.checked = !!checked[domain];

            row.appendChild(left);
            row.appendChild(cb);
            listEl.appendChild(row);
        });
    }

    function updateStats() {
        const listRoot = document.getElementById('domain-scanner-container');
        if (!listRoot) return;

        let statsBox = document.getElementById('domain-stats');
        if (!statsBox) {
            statsBox = document.createElement('div');
            statsBox.id = 'domain-stats';
            statsBox.style.marginBottom = '8px';

            function makeStatRow(labelId, labelText) {
                const wrap = document.createElement('div');
                wrap.style.marginBottom = '6px';

                const label = document.createElement('div');
                label.id = labelId + '-label';
                label.textContent = labelText + ': 0 / 0';
                label.style.marginBottom = '2px';

                const bar = document.createElement('div');
                bar.className = 'progress-outer';
                Object.assign(bar.style, {
                    height: '6px', background: '#eee', border: '1px solid #bbb', borderRadius: '3px', overflow: 'hidden'
                });
                const inner = document.createElement('div');
                inner.id = labelId + '-bar';
                Object.assign(inner.style, {
                    height: '100%', width: '0%', background: labelId.includes('allowed') ? '#3cba54' : '#db3236'
                });
                bar.appendChild(inner);

                wrap.appendChild(label);
                wrap.appendChild(bar);
                return wrap;
            }

            statsBox.appendChild(makeStatRow('allowed', 'Разрешённые'));
            statsBox.appendChild(makeStatRow('blocked', 'Запрещённые'));

            const domainList = document.getElementById('domain-list');
            if (domainList && domainList.parentElement) {
                domainList.parentElement.insertBefore(statsBox, domainList);
            } else {
                listRoot.appendChild(statsBox);
            }
        }

        const total = domainSet.size || 0;
        let ok = 0, blocked = 0;
        for (const d of domainSet) {
            const st = domainStatus.get(d);
            if (st === 'ok') ok++;
            else if (st === 'blocked') blocked++;
        }

        const allowedLabel = document.getElementById('allowed-label');
        const blockedLabel = document.getElementById('blocked-label');
        const allowedBar = document.getElementById('allowed-bar');
        const blockedBar = document.getElementById('blocked-bar');

        if (allowedLabel) allowedLabel.textContent = `Разрешённые: ${ok} / ${total}`;
        if (blockedLabel) blockedLabel.textContent = `Запрещённые: ${blocked} / ${total}`;
        const pctOk = total ? Math.round((ok / total) * 100) : 0;
        const pctBlocked = total ? Math.round((blocked / total) * 100) : 0;
        if (allowedBar) allowedBar.style.width = pctOk + '%';
        if (blockedBar) blockedBar.style.width = pctBlocked + '%';
    }

    async function addToDeeper() {
        try {
            const response = await gmFetch('http://34.34.34.34/api/smartRoute/getRoutingWhitelist/domain?pageNo=1&pageSize=100');
            if (response.status !== 200) { alert('[Deeper Tools] Ошибка при получении белого списка'); return; }
            const data = await response.json();
            const existingDomains = new Set();
            const tunnelCodes = [];
            if (Array.isArray(data.list)) {
                for (const item of data.list) {
                    if (item.domainName) existingDomains.add(item.domainName);
                    if (item.tunnelCode) tunnelCodes.push(item.tunnelCode);
                }
            }
            if (tunnelCodes.length === 0) tunnelCodes.push('defaultCode');

            const container = document.getElementById('domain-scanner-container'); if (!container) return;
            const checkboxes = container.querySelectorAll('.domain-checkbox');
            const selected = []; checkboxes.forEach(cb => { if (cb.checked) selected.push(cb.dataset.domain); });
            if (selected.length === 0) { alert('[Deeper Tools] Выберите домены для добавления.'); return; }

            const newItems = [];
            for (const d of selected) {
                if (!existingDomains.has(d)) {
                    const randomIndex = Math.floor(Math.random() * tunnelCodes.length);
                    newItems.push({ domainName: d, tunnelCode: tunnelCodes[randomIndex] });
                }
            }
            if (newItems.length === 0) { alert('[Deeper Tools] Нет новых доменов для добавления.'); return; }

            for (const item of newItems) {
                const r = await gmFetch('http://34.34.34.34/api/smartRoute/addToWhitelist/domain', {
                    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item)
                });
                if (r.status !== 200) console.error('[Deeper Tools] Ошибка при добавлении домена:', item);
            }
            alert('[Deeper Tools] Новые домены добавлены в deeper!');
        } catch (err) {
            console.error('[Deeper Tools] Ошибка при добавлении в deeper:', err);
            alert('Ошибка при добавлении. Смотрите консоль.');
        }
    }

    if (isAllowedHost()) {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', bootDomainPageHelpers);
        } else {
            bootDomainPageHelpers();
        }
        new MutationObserver(bootDomainPageHelpers).observe(document.documentElement, { childList: true, subtree: true });

        // Кнопки "Оптимизировать регионы" и "Тема"
        window.addEventListener('DOMContentLoaded', () => {
            const observer = new MutationObserver(() => {
                const menu = document.querySelector('div[style*="flex-direction"]');
                if (!menu) return;
                observer.disconnect();

                const buttonStyle = {
                    margin: '5px 0',
                    padding: '8px 14px',
                    backgroundColor: '#f8f8f8',
                    border: '1px solid #ccc',
                    borderRadius: '4px',
                    cursor: 'pointer',
                    fontSize: '14px'
                };

                const optimizeBtn = document.createElement('button');
                optimizeBtn.id = 'optimize-regions-btn';
                optimizeBtn.textContent = 'Оптимизировать регионы';
                Object.assign(optimizeBtn.style, buttonStyle);
                optimizeBtn.addEventListener('click', optimizeRegions);
                menu.appendChild(optimizeBtn);

                const themeBtn = document.createElement('button');
                themeBtn.id = 'toggle-theme-btn';
                themeBtn.textContent = 'Тема';
                Object.assign(themeBtn.style, buttonStyle);
                themeBtn.addEventListener('click', () => {
                    currentThemeIndex = (currentThemeIndex + 1) % themeNames.length;
                    applyTheme(currentThemeIndex);
                });
                menu.appendChild(themeBtn);
            });
            observer.observe(document.body, { childList: true, subtree: true });
        });

        // Плавающая иконка-меню
        const iconButton = document.createElement('div');
        Object.assign(iconButton.style, {
            position: 'fixed', width: '25px', height: '25px', top: '10px', right: '10px', zIndex: '9999',
            backgroundColor: 'rgb(240, 240, 252)', borderRadius: '4px', boxShadow: '0 2px 5px rgba(0,0,0,0.3)',
            cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center'
        });
        const img = document.createElement('img');
        img.src = 'https://avatars.mds.yandex.net/get-socsnippets/10235467/2a0000019509580bc84108597cea65bc46ee/square_83';
        img.style.maxWidth = '80%'; img.style.maxHeight = '80%'; iconButton.appendChild(img);

        const menuContainer = document.createElement('div');
        Object.assign(menuContainer.style, {
            position: 'fixed', top: '45px', right: '10px', zIndex: '10000', padding: '10px',
            border: '1px solid #ccc', borderRadius: '4px', boxShadow: '0 2px 5px rgba(0,0,0,0.3)',
            backgroundColor: '#fff', display: 'none', flexDirection: 'column'
        });
        function toggleMenu() { menuContainer.style.display = (menuContainer.style.display === 'none' ? 'flex' : 'none'); }
        iconButton.addEventListener('click', toggleMenu);

        const buttonStyle2 = {
            margin: '5px 0', padding: '8px 14px', backgroundColor: '#f8f8f8',
            border: '1px solid #ccc', borderRadius: '4px', cursor: 'pointer', fontSize: '14px'
        };

        const forgetBtn = document.createElement('button'); forgetBtn.textContent = 'Забыть пароль'; Object.assign(forgetBtn.style, buttonStyle2);
        const allToffBtn = document.createElement('button'); allToffBtn.textContent = 'All_T_OFF'; allToffBtn.title = 'Отключить все домены у выбранных туннелей и переключить их на другой.'; Object.assign(allToffBtn.style, buttonStyle2);
        menuContainer.append(forgetBtn, allToffBtn);

        function ensureMenu() {
            if (!document.body.contains(iconButton)) document.body.appendChild(iconButton);
            if (!document.body.contains(menuContainer)) document.body.appendChild(menuContainer);
        }
        document.addEventListener('DOMContentLoaded', ensureMenu);
        new MutationObserver(ensureMenu).observe(document.documentElement, { childList: true, subtree: true });

        // Забыть пароль (и логин)
        forgetBtn.addEventListener('click', () => {
            if (confirm('Внимание! Логин и пароль будут очищены. Продолжить?')) {
                GM_setValue('adminPassword', null);
                GM_setValue('adminUsername', null);
                alert('[Deeper Tools] Данные очищены. Авторизуйтесь вручную.');
            }
        });

        allToffBtn.addEventListener('click', showAllToffPopup);

        async function optimizeRegions() {
            if (!isAllowedHost()) return;

            console.log('🔄 Запуск оптимизации регионов (батчи по 5)');
            const btn = document.getElementById('optimize-regions-btn');
            if (btn) { btn.disabled = true; btn.textContent = 'Оптимизация…'; }

            const regionMap = {
                AMN: ["BM","CA","GL","MX","PM","US","UB","UC","UD","UE","UF"],
                AMC: ["AG","AI","AW","BB","BL","BQ","BS","CU","CW","DM","DO","GD...N","KY","LC","MF","MQ","MS","PR","SX","TC","TT","VC","VG","VI"],
                AMM: ["BZ","CR","GT","HN","NI","PA","SV"],
                AMS: ["AR","BO","BR","CL","CO","EC","FK","GF","GS","GY","PE","PY","SR","UY","VE"],
                ASC: ["KG","KZ","TJ","TM","UZ"],
                ASE: ["CN","HK","JP","KP","KR","MN","MO","TW"],
                ASW: ["AE","AM","AZ","BH","IR","GE","IL","IQ","JO","KW","LB","OM","PS","QA","SA","SY","YE"],
                ASS: ["AF","BD","BT","IN","LK","MV","NP","PK"],
                ASD: ["BN","ID","KH","LA","MM","MY","PH","SG","TH","TL","VN"],
                AFN: ["DZ","EG","LY","MA","SD","TN"],
                AFS: ["BW","LS","NA","SZ","ZA"],
                AFE: ["BI","DJ","ER","ET","KE","KM","MG","MU","MW","MZ","RE","RW","SC","SO","TZ","UG","YT","ZM","ZW"],
                AFW: ["AO","BF","BJ","BV","CI","CV","GH","GM","GN","GW","LR","ML","MR","NE","NG","SH","SL","SN","ST","TG"],
                EUN: ["GG","IE","IM","JE","UK"],
                EEU: ["AT","BE","CH","DE","DK","FI","FO","FR","IS","IT","LI","LU","MC","NL","NO","SE","SJ","SM","VA"],
                EUS: ["AD","AL","BA","BG","BY","CZ","EE","ES","GI","GR","HR","HU","LT","LV","MD","ME","MK","PL","PT","RO","RS","RU","SI","SK","UA"],
                OCN: ["AU","NF","NZ"],
                OCS: ["AS","CK","FJ","FM","GU","KI","MH","MP","NC","NR","NU","PF","PG","PN","PW","SB","TK","TO","TV","UM","VU","WF","WS"]
            };

            async function listWhitelist(pageNo, pageSize) {
                const res = await gmFetch(`${location.origin}/api/smartRoute/getRoutingWhitelist/domain?pageNo=${pageNo}&pageSize=${pageSize}`);
                return res.json();
            }

            async function deleteFromWhitelist(domains) {
                return gmFetch(`${location.origin}/api/smartRoute/deleteFromWhitelist/domain`, {
                    method: 'POST', headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(domains)
                });
            }

            async function processRegion(regionCode) {
                console.log(`➡️ Обработка региона ${regionCode}`);
                const countries = regionMap[regionCode];
                let page = 1, pageSize = 100, done = false;

                const accumulated = [];
                while (!done) {
                    const data = await listWhitelist(page, pageSize);
                    if (!data || !Array.isArray(data.list)) break;
                    for (const item of data.list) {
                        if (countries.includes(item.countryCode)) accumulated.push(item.domainName);
                    }
                    if (data.list.length < pageSize) done = true; else page++;
                }

                if (accumulated.length === 0) {
                    console.log(`⏭ Регион ${regionCode}: нет доменов для удаления`);
                    return;
                }

                console.log(`🗑 Регион ${regionCode}: удаляем ${accумulated.length} доменов`);
                await deleteFromWhitelist(accumulated);
            }

            const codeList = Object.keys(regionMap);
            const batchSize = 5;
            for (let i = 0; i < codeList.length; i += batchSize) {
                const batch = codeList.slice(i, i + batchSize);
                console.log('⚙️ Батч:', batch.join(', '));
                await Promise.all(batch.map(rc => processRegion(rc)));
            }

            if (btn) { btn.disabled = false; btn.textContent = 'Оптимизировать регионы'; }
            console.log('✅ Оптимизация завершена');
        }

        async function showAllToffPopup() {
            const overlay = document.createElement('div');
            Object.assign(overlay.style, {
                position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 20000
            });

            const popup = document.createElement('div');
            Object.assign(popup.style, {
                maxWidth: '600px', width: '95%', position: 'fixed', top: '50%', left: '50%',
                transform: 'translate(-50%,-50%)', background: '#fff', padding: '20px',
                borderRadius: '8px', boxShadow: '0 2px 10px rgba(0,0,0,.3)', color: '#000'
            });

            const title = document.createElement('h3');
            title.textContent = 'Массовое отключение доменов';
            popup.appendChild(title);

            const tunnelsContainer = document.createElement('div');
            Object.assign(tunnelsContainer.style, {
                maxHeight: '300px', overflowY: 'auto', marginBottom: '10px'
            });
            popup.appendChild(tunnelsContainer);

            const btns = document.createElement('div');
            Object.assign(btns.style, { display: 'flex', justifyContent: 'flex-end', gap: '10px' });

            const switchAllBtn = document.createElement('button');
            switchAllBtn.textContent = 'Переключить все';
            Object.assign(switchAllBtn.style, { background: '#0077cc', color: '#fff', borderRadius: '4px', padding: '8px 14px', cursor: 'pointer' });

            const offBtn = document.createElement('button');
            offBtn.textContent = 'Отключиться';
            Object.assign(offBtn.style, { background: '#bb0000', color: '#fff', borderRadius: '4px', padding: '8px 14px', cursor: 'pointer' });

            const randomizeBtn = document.createElement('button');
            randomizeBtn.textContent = 'Рандомайзер';
            Object.assign(randomizeBtn.style, { background: '#007700', color: '#fff', borderRadius: '4px', padding: '8px 14px', cursor: 'pointer' });

            const cancelBtn = document.createElement('button');
            cancelBtn.textContent = 'Отмена';
            Object.assign(cancelBtn.style, { background: '#666', color: '#fff', borderRadius: '4px', padding: '8px 14px', cursor: 'pointer' });

            btns.append(switchAllBtn, offBtn, randomizeBtn, cancelBtn);
            popup.appendChild(btns);
            overlay.appendChild(popup);
            document.body.appendChild(overlay);

            function closePopup() { overlay.remove(); }
            cancelBtn.addEventListener('click', closePopup);

            // helpers
            async function listTunnels() {
                const r = await gmFetch(`${location.origin}/api/smartRoute/listTunnels`);
                if (r.status !== 200) throw new Error('listTunnels failed');
                return r.json();
            }
            async function getAllWhitelist() {
                const pageSize = 100;
                let pageNo = 1, done = false, all = [];
                while (!done) {
                    const r = await gmFetch(`${location.origin}/api/smartRoute/getRoutingWhitelist/domain?pageNo=${pageNo}&pageSize=${pageSize}`);
                    const d = await r.json();
                    if (!d || !Array.isArray(d.list)) break;
                    all.push(...d.list);
                    if (d.list.length < pageSize) done = true; else pageNo++;
                }
                return all;
            }
            const pickBest = (cands) => {
                if (!cands.length) return null;
                const maxActive = Math.max(...cands.map(t => Number(t.activeNum || 0)));
                const best = cands.filter(t => Number(t.activeNum || 0) === maxActive);
                return best[Math.floor(Math.random() * best.length)];
            };

            // render tunnels
            let tunnelsList = [];
            try {
                tunnelsList = await listTunnels();
            } catch (e) {
                console.error('[Deeper Tools] Ошибка при получении туннелей:', e);
                alert('Ошибка получения списка туннелей. Смотрите консоль.');
                return closePopup();
            }

            tunnelsList.forEach(t => {
                const row = document.createElement('div');
                Object.assign(row.style, { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '5px', fontSize: '14px' });

                const left = document.createElement('div');
                left.style.display = 'flex';
                left.style.alignItems = 'center';
                const text = document.createElement('span');
                text.textContent = `${t.countryCode} ${t.regionCode}`;
                left.appendChild(text);

                const right = document.createElement('div');
                Object.assign(right.style, { display: 'flex', alignItems: 'center' });
                const active = document.createElement('span');
                Object.assign(active.style, { width: '30px', textAlign: 'right', display: 'inline-block', marginRight: '10px' });
                active.textContent = t.activeNum;

                const chk = document.createElement('input');
                chk.type = 'checkbox';
                chk.dataset.tunnelCode = t.tunnelCode;
                chk.dataset.regionCode = t.regionCode;
                chk.dataset.countryCode = t.countryCode;
                chk.dataset.activeNum = t.activeNum;

                right.append(active, chk);
                row.append(left, right);
                tunnelsContainer.appendChild(row);
            });

            switchAllBtn.addEventListener('click', async () => {
                const checked = [...tunnelsContainer.querySelectorAll('input[type=checkbox]')].filter(ch => ch.checked);
                if (!checked.length) return alert('Не выбрано ни одного туннеля.');
                try {
                    const whitelist = await getAllWhitelist();
                    const fresh = await listTunnels();
                    const selectedCodes = checked.map(ch => ch.dataset.tunnelCode);

                    for (const entry of whitelist) {
                        const cands = fresh.filter(t => selectedCodes.includes(t.tunnelCode));
                        const chosen = pickBest(cands);
                        if (!chosen) continue;
                        try {
                            await gmFetch(`${location.origin}/api/smartRoute/editWhiteEntry/domain`, {
                                method: 'POST', headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify({ domainName: entry.domainName, fromTunnelCode: entry.tunnelCode, toTunnelCode: chosen.tunnelCode })
                            });
                        } catch (e) { console.error('[Deeper Tools] Ошибка переключения для домена:', entry.domainName, e); }
                    }
                    alert('Массовое переключение выполнено.');
                    closePopup();
                } catch (e) {
                    console.error('[Deeper Tools] Ошибка "Переключить все":', e);
                    alert('Ошибка при переключении. Смотрите консоль.');
                }
            });

            offBtn.addEventListener('click', async () => {
                const checked = [...tunnelsContainer.querySelectorAll('input[type=checkbox]')].filter(ch => ch.checked);
                if (!checked.length) return alert('Не выбрано ни одного туннеля.');
                try {
                    const fresh = await listTunnels();
                    for (const item of checked) {
                        const fromCode = item.dataset.tunnelCode;
                        const wl = await getAllWhitelist();
                        const entries = wl.filter(e => e.tunnelCode === fromCode);
                        const cands = fresh.filter(t => t.tunnelCode !== fromCode);
                        const chosenBase = pickBest(cands);
                        if (!chosenBase) continue;

                        for (const entry of entries) {
                            const chosen = pickBest(cands) || chosenBase;
                            try {
                                await gmFetch(`${location.origin}/api/smartRoute/editWhiteEntry/domain`, {
                                    method: 'POST', headers: { 'Content-Type': 'application/json' },
                                    body: JSON.stringify({ domainName: entry.domainName, fromTunnelCode: fromCode, toTunnelCode: chosen.tunnelCode })
                                });
                            } catch (e) { console.error('[Deeper Tools] Ошибка отключения для домена:', entry.domainName, e); }
                        }
                    }
                    alert('Массовое отключение выполнено.');
                    closePopup();
                } catch (e) {
                    console.error('[Deeper Tools] Ошибка "Отключиться":', e);
                    alert('Ошибка при отключении. Смотрите консоль.');
                }
            });

            randomizeBtn.addEventListener('click', async () => {
                try {
                    const wl = await getAllWhitelist();
                    if (!wl.length) return alert('Нет доменов для распределения.');
                    const allTunnels = (await listTunnels()).map(t => t.tunnelCode);
                    if (!allTunnels.length) return alert('Нет доступных туннелей.');

                    const domains = wl.slice();
                    for (let i = domains.length - 1; i > 0; i--) {
                        const j = Math.floor(Math.random() * (i + 1));
                        [domains[i], domains[j]] = [domains[j], domains[i]];
                    }
                    for (let i = 0; i < domains.length; i++) {
                        const entry = domains[i];
                        const toTunnel = allTunnels[i % allTunnels.length];
                        await gmFetch(`${location.origin}/api/smartRoute/editWhiteEntry/domain`, {
                            method: 'POST', headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ domainName: entry.domainName, fromTunnelCode: entry.tunnelCode, toTunnelCode: toTunnel })
                        });
                    }
                    alert('Рандомизация доменов завершена!');
                    closePopup();
                } catch (e) {
                    console.error('[Deeper Tools] Ошибка рандомизации:', e);
                    alert('Ошибка при рандомизации. Смотрите консоль.');
                }
            });
        }
    }

    /* ---------------------------------
   * Меню Tampermonkey: Domain Scanner
   * --------------------------------- */
    let scannerMenuCommandId = null;
    function updateScannerMenuCommand() {
        if (scannerMenuCommandId && typeof GM_unregisterMenuCommand === 'function') {
            GM_unregisterMenuCommand(scannerMenuCommandId);
        }
        if (typeof GM_registerMenuCommand === 'function') {
            const currentState = getScannerEnabled();
            const label = 'Domain Scanner: ' + (currentState ? '🟢' : '🔴');
            scannerMenuCommandId = GM_registerMenuCommand(label, () => setScannerEnabled(!getScannerEnabled()));
        }
    }
    if (GM_getValue('domainScannerEnabled') === undefined) GM_setValue('domainScannerEnabled', false);
    updateScannerMenuCommand();
    if (getScannerEnabled()) {
        if (['complete', 'interactive'].includes(document.readyState)) ensureScannerContainer();
        else document.addEventListener('DOMContentLoaded', ensureScannerContainer);
    }

})();