Deeper Tools

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

目前為 2025-04-16 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Deeper Tools
// @description  Набор инструментов для Deeper
// @author       https://github.com/lReDragol
// @namespace    http://tampermonkey.net/
// @version      3.6.1
// @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
// ==/UserScript==

(function() {
    'use strict';


    const countryNames = {
            LL: "не проходить тунель",
            ANY: "любая страна или регион",
            AMER: "---Америка---",
            ASIA: "---Азия---",
            AFRI: "---Африка---",
            EURO: "---Европа---",
            OCEA: "---Океания---",
            AMN: "Северная Америка",
            AMC: "Карибский бассейн",
            AMM: "Центральная Америка",
            AMS: "Южная Америка",
            ASC: "Центральная Азия",
            ASE: "Восточная Азия",
            ASW: "Западная Азия",
            ASS: "Южная Азия",
            ASD: "Юго-Восточная Азия",
            AFN: "Северная Африка",
            AFM: "Центральная Африка",
            AFE: "Восточная Африка",
            AFW: "Западная Африка",
            AFS: "Южная Африка",
            EUN: "Северная Европа",
            EUE: "Восточная Европа",
            EUW: "Западная Европа",
            EUS: "Южная Европа",
            OCP: "Полинезия",
            OCA: "Австралия и Новая Зеландия",
            OCM: "Меланезия",
            OCN: "Микронезия",
            AD: "Андорра",
            AE: "Объединенные Арабские Эмираты",
            AF: "Афганистан",
            AG: "Антигуа и Барбуда",
            AI: "Ангилья",
            AL: "Албания",
            AM: "Армения",
            AO: "Ангола",
            AR: "Аргентина",
            AS: "Американское Самоа",
            AT: "Австрия",
            AU: "Австралия",
            AW: "Аруба",
            AX: "Аландские острова",
            AZ: "Азербайджан",
            BA: "Босния и Герцеговина",
            BB: "Барбадос",
            BD: "Бангладеш",
            BE: "Бельгия",
            BF: "Буркина-Фасо",
            BG: "Болгария",
            BH: "Бахрейн",
            BI: "Бурунди",
            BJ: "Бенин",
            BL: "Сен-Бартелеми",
            BM: "Бермуды",
            BN: "Бруней",
            BO: "Боливия",
            BQ: "Карибская Нидерландия",
            BR: "Бразилия",
            BS: "Багамы",
            BT: "Бутан",
            BW: "Ботсвана",
            BY: "Беларусь",
            BZ: "Белиз",
            CA: "Канада",
            CC: "Кокосовые (Килинг) острова",
            CD: "Конго (Киншаса)",
            CF: "Центрально-Африканская Республика",
            CG: "Конго (Браззавиль)",
            CH: "Швейцария",
            CI: "Кот-д'Ивуар",
            CK: "Острова Кука",
            CL: "Чили",
            CM: "Камерун",
            CN: "Китай",
            CO: "Колумбия",
            CR: "Коста-Рика",
            CU: "Куба",
            CV: "Кабо-Верде",
            CW: "Кюрасао",
            CX: "Остров Рождества",
            CY: "Кипр",
            CZ: "Чехия",
            DE: "Германия",
            DJ: "Джибути",
            DK: "Дания",
            DM: "Доминика",
            DO: "Доминиканская Республика",
            DZ: "Алжир",
            EC: "Эквадор",
            EE: "Эстония",
            EG: "Египет",
            ER: "Эритрея",
            ES: "Испания",
            ET: "Эфиопия",
            FI: "Финляндия",
            FJ: "Фиджи",
            FK: "Фолклендские острова",
            FM: "Федеративные Штаты Микронезии",
            FO: "Фарерские острова",
            FR: "Франция",
            GA: "Габон",
            GB: "Великобритания",
            GD: "Гренада",
            GE: "Грузия",
            GF: "Французская Гвиана",
            GG: "Гернси",
            GH: "Гана",
            GI: "Гибралтар",
            GL: "Гренландия",
            GM: "Гамбия",
            GN: "Гвинея",
            GP: "Гваделупа",
            GQ: "Экваториальная Гвинея",
            GR: "Греция",
            GS: "Южная Джорджия и Южные Сандвичевы острова",
            GT: "Гватемала",
            GU: "Гуам",
            GW: "Гвинея-Бисау",
            GY: "Гайана",
            HK: "Гонконг (Китай)",
            HN: "Гондурас",
            HR: "Хорватия",
            HT: "Гаити",
            HU: "Венгрия",
            ID: "Индонезия",
            IE: "Ирландия",
            IL: "Израиль",
            IM: "Остров Мэн",
            IN: "Индия",
            IO: "Британская территория в Индийском океане",
            IQ: "Ирак",
            IR: "Иран",
            IS: "Исландия",
            IT: "Италия",
            JE: "Джерси",
            JM: "Ямайка",
            JO: "Иордания",
            JP: "Япония",
            KE: "Кения",
            KG: "Киргизия",
            KH: "Камбоджа",
            KI: "Кирибати",
            KM: "Коморы",
            KN: "Сент-Китс и Невис",
            KR: "Южная Корея",
            KW: "Кувейт",
            KY: "Каймановы острова",
            KZ: "Казахстан",
            KP: "Северная Корея",
            LA: "Лаос",
            LB: "Ливан",
            LC: "Сент-Люсия",
            LI: "Лихтенштейн",
            LK: "Шри-Ланка",
            LR: "Либерия",
            LS: "Лесото",
            LT: "Литва",
            LU: "Люксембург",
            LV: "Латвия",
            LY: "Ливия",
            MA: "Марокко",
            MC: "Монако",
            MD: "Молдавия",
            ME: "Черногория",
            MF: "Сен-Мартен (фр.)",
            MG: "Мадагаскар",
            MH: "Маршалловы острова",
            MK: "Северная Македония",
            ML: "Мали",
            MM: "Мьянма (Бирма)",
            MN: "Монголия",
            MO: "Макао (Китай)",
            MP: "Северные Марианские острова",
            MQ: "Мартиника",
            MR: "Мавритания",
            MS: "Монтсеррат",
            MT: "Мальта",
            MU: "Маврикий",
            MV: "Мальдивы",
            MW: "Малави",
            MX: "Мексика",
            MY: "Малайзия",
            MZ: "Мозамбик",
            NA: "Намибия",
            NC: "Новая Каледония",
            NE: "Нигер",
            NF: "Остров Норфолк",
            NG: "Нигерия",
            NI: "Никарагуа",
            NL: "Нидерланды",
            NO: "Норвегия",
            NP: "Непал",
            NR: "Науру",
            NU: "Ниуэ",
            NZ: "Новая Зеландия",
            OM: "Оман",
            PA: "Панама",
            PE: "Перу",
            PF: "Французская Полинезия",
            PG: "Папуа — Новая Гвинея",
            PH: "Филиппины",
            PK: "Пакистан",
            PL: "Польша",
            PM: "Сен-Пьер и Микелон",
            PN: "Острова Питкэрн",
            PR: "Пуэрто-Рико",
            PS: "Палестинские территории",
            PT: "Португалия",
            PW: "Палау",
            PY: "Парагвай",
            QA: "Катар",
            RE: "Реюньон",
            RO: "Румыния",
            RS: "Сербия",
            RU: "Россия",
            RW: "Руанда",
            SA: "Саудовская Аравия",
            SB: "Соломоновы острова",
            SC: "Сейшельские Острова",
            SD: "Судан",
            SE: "Швеция",
            SG: "Сингапур",
            SH: "Острова Святой Елены, Вознесения и Тристан-да-Кунья",
            SI: "Словения",
            SJ: "Шпицберген и Ян-Майен",
            SK: "Словакия",
            SL: "Сьерра-Леоне",
            SM: "Сан-Марино",
            SN: "Сенегал",
            SO: "Сомали",
            SR: "Суринам",
            SS: "Южный Судан",
            ST: "Сан-Томе и Принсипи",
            SV: "Сальвадор",
            SX: "Синт-Мартен",
            SY: "Сирия",
            SZ: "Эсватини",
            TC: "Теркс и Кайкос",
            TD: "Чад",
            TF: "Французские Южные и Антарктические Территории",
            TG: "Того",
            TH: "Таиланд",
            TJ: "Таджикистан",
            TK: "Токелау",
            TL: "Восточный Тимор",
            TM: "Туркменистан",
            TN: "Тунис",
            TO: "Тонга",
            TR: "Турция",
            TT: "Тринидад и Тобаго",
            TV: "Тувалу",
            TW: "Тайвань",
            TZ: "Танзания",
            UA: "Украина",
            UB: "Запад США",
            UC: "Средний Запад США",
            UD: "Юго-Запад США",
            UE: "Северо-Восток США",
            UF: "Юго-Восток США",
            UG: "Уганда",
            US: "Соединенные Штаты",
            UY: "Уругвай",
            UZ: "Узбекистан",
            VA: "Ватикан",
            VC: "Сент-Винсент и Гренадины",
            VE: "Венесуэла",
            VG: "Британские Виргинские острова",
            VI: "Американские Виргинские острова",
            VN: "Вьетнам",
            VU: "Вануату",
            WF: "Уоллис и Футуна",
            WS: "Самоа",
            XK: "Косово",
            YE: "Йемен",
            YT: "Майотта",
            ZA: "Южная Африка",
            ZM: "Замбия",
            ZW: "Зимбабве"
        };

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

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

    const nativeOpen = XMLHttpRequest.prototype.open;
    const nativeSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.open = function(method, url) {
        this._method = method;
        this._url = url;
        if (getScannerEnabled()) {
            try {
                const urlObj = new URL(url);
                addDomain(urlObj.hostname);
            } catch(e) {}
        }
        return nativeOpen.apply(this, arguments);
    };
    XMLHttpRequest.prototype.send = function(body) {
        if (
            this._url &&
            this._url.includes('/api/admin/login') &&
            this._method &&
            this._method.toUpperCase() === 'POST'
        ) {
            try {
                const parsed = JSON.parse(body);
                if (parsed && parsed.password) {
                    if (!GM_getValue('adminPassword')) {
                        GM_setValue('adminPassword', parsed.password);
                        console.log('[Deeper Tools] Пароль сохранён из XHR.');
                    }
                }
            } catch (err) {
                console.error('[Deeper Tools] Ошибка парсинга XHR при авторизации:', err);
            }
        }
        return nativeSend.apply(this, arguments);
    };

    if (window.location.href.includes('/login/')) {
        const storedPassword = GM_getValue('adminPassword');
        if (storedPassword) {
            window.addEventListener('load', () => {
                gmFetch('http://34.34.34.34/api/admin/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        "username": "admin",
                        "password": storedPassword
                    })
                })
                .then(response => {
                    if (response.status === 200) {
                        window.location.href = 'http://34.34.34.34/admin/dashboard';
                    }
                    return response.json();
                })
                .then(data => console.log('[Deeper Tools] Авторизация прошла успешно:', data))
                .catch(error => console.error('[Deeper Tools] Ошибка при авторизации:', error));
            });
        } else {
            console.log('[Deeper Tools] Пароль не найден. Выполните ручную авторизацию.');
        }
    }

    if (window.location.href.startsWith('http://34.34.34.34/') || window.location.href.startsWith('http://11.22.33.44/')) {
        const iconButton = document.createElement('div');
        iconButton.style.position = 'fixed';
        iconButton.style.width = '25px';
        iconButton.style.height = '25px';
        iconButton.style.top = '10px';
        iconButton.style.right = '10px';
        iconButton.style.zIndex = '9999';
        iconButton.style.backgroundColor = 'rgb(240, 240, 252)';
        iconButton.style.borderRadius = '4px';
        iconButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
        iconButton.style.cursor = 'pointer';
        iconButton.style.display = 'flex';
        iconButton.style.alignItems = 'center';
        iconButton.style.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');
        menuContainer.style.position = 'fixed';
        menuContainer.style.top = '45px';
        menuContainer.style.right = '10px';
        menuContainer.style.zIndex = '10000';
        menuContainer.style.padding = '10px';
        menuContainer.style.border = '1px solid #ccc';
        menuContainer.style.borderRadius = '4px';
        menuContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
        menuContainer.style.backgroundColor = '#fff';
        menuContainer.style.display = 'none';
        menuContainer.style.flexDirection = 'column';

        function toggleMenu() {
            menuContainer.style.display = (menuContainer.style.display === 'none') ? 'flex' : 'none';
        }
        iconButton.addEventListener('click', toggleMenu);

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

        const downloadBtn = document.createElement('button');
        downloadBtn.textContent = 'Поделиться доменами';
        Object.assign(downloadBtn.style, buttonStyle);

        const uploadBtn = document.createElement('button');
        uploadBtn.textContent = 'Добавить домены';
        Object.assign(uploadBtn.style, buttonStyle);

        const disableRebootBtn = document.createElement('button');
        disableRebootBtn.textContent = 'Отключить перезагрузку';
        Object.assign(disableRebootBtn.style, buttonStyle);

        const forgetBtn = document.createElement('button');
        forgetBtn.textContent = 'Забыть пароль';
        Object.assign(forgetBtn.style, buttonStyle);

        const allToffBtn = document.createElement('button');
        allToffBtn.textContent = 'All_T_OFF';
        allToffBtn.title = "Тут можно отключить все домены от определённых тунелей и переключить их на другой.";
        Object.assign(allToffBtn.style, buttonStyle);

        menuContainer.appendChild(downloadBtn);
        menuContainer.appendChild(uploadBtn);
        menuContainer.appendChild(disableRebootBtn);
        menuContainer.appendChild(forgetBtn);
        menuContainer.appendChild(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 });

        async function getExistingWhitelist() {
            const pageSize = 100;
            let pageNo = 1;
            let total = 0;
            let allItems = [];
            let firstIteration = true;
            do {
                const url = `http://34.34.34.34/api/smartRoute/getRoutingWhitelist/domain?pageNo=${pageNo}&pageSize=${pageSize}`;
                const response = await gmFetch(url);
                if (response.status !== 200) {
                    throw new Error('Ошибка при запросе списка на странице ' + pageNo);
                }
                const data = await response.json();
                if (firstIteration) {
                    total = data.total;
                    firstIteration = false;
                }
                if (data.list && data.list.length > 0) {
                    allItems = allItems.concat(data.list);
                }
                pageNo++;
            } while (allItems.length < total);
            return allItems;
        }

        downloadBtn.addEventListener('click', async () => {
            downloadBtn.disabled = true;
            downloadBtn.textContent = 'Скачивание...';
            try {
                const allItems = await getExistingWhitelist();
                const finalData = { total: allItems.length, list: allItems };
                const blob = new Blob([JSON.stringify(finalData, null, 2)], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'data.json';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            } catch (error) {
                console.error('[Deeper Tools] Ошибка при скачивании:', error);
                alert('Ошибка при скачивании данных. Проверьте консоль.');
            }
            downloadBtn.textContent = 'Скачать домены';
            downloadBtn.disabled = false;
        });

        uploadBtn.addEventListener('click', () => {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = 'application/json';
            input.style.display = 'none';
            input.addEventListener('change', async function() {
                if (input.files.length === 0) return;
                const file = input.files[0];
                const reader = new FileReader();
                reader.onload = async function(e) {
                    try {
                        const jsonData = JSON.parse(e.target.result);
                        if (!jsonData.list || !Array.isArray(jsonData.list)) {
                            throw new Error('Неверный формат файла: ожидалось поле list[].');
                        }
                        const fileDomainNames = jsonData.list.map(item => item.domainName);
                        const existing = await getExistingWhitelist();
                        const existingDomainNames = existing.map(item => item.domainName);
                        const duplicates = fileDomainNames.filter(d => existingDomainNames.includes(d));
                        if (duplicates.length > 0) {
                            console.log('[Deeper Tools] Удаляем дубликаты:', duplicates);
                            const delRes = await gmFetch('http://34.34.34.34/api/smartRoute/deleteFromWhitelist/domain', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify(duplicates)
                            });
                            if (delRes.status !== 200) {
                                console.error('[Deeper Tools] Ошибка при удалении дубликатов:', duplicates);
                            }
                        }
                        for (let item of jsonData.list) {
                            const payload = { domainName: item.domainName, tunnelCode: item.tunnelCode };
                            const res = await gmFetch('http://34.34.34.34/api/smartRoute/addToWhitelist/domain', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify(payload)
                            });
                            if (res.status !== 200) {
                                console.error('[Deeper Tools] Ошибка добавления домена:', item.domainName);
                            }
                        }
                        alert('[Deeper Tools] Данные успешно загружены!');
                    } catch(err) {
                        console.error('[Deeper Tools] Ошибка загрузки:', err);
                        alert('Ошибка загрузки. Смотрите консоль.');
                    }
                };
                reader.readAsText(file);
            });
            document.body.appendChild(input);
            input.click();
            document.body.removeChild(input);
        });

        disableRebootBtn.addEventListener('click', async () => {
            disableRebootBtn.disabled = true;
            disableRebootBtn.textContent = 'Отключение...';
            try {
                const queryParams = '?on=false&hour=0&minute=0&day=0';
                const response = await gmFetch(`http://34.34.34.34/api/autoReboot/config${queryParams}`, {
                    method: 'GET',
                    headers: { 'Content-Type': 'application/json' }
                });
                if (response.status !== 200) {
                    throw new Error('Ошибка при отключении перезагрузки');
                }
                alert('[Deeper Tools] Перезагрузка отключена!');
            } catch (error) {
                console.error('[Deeper Tools] Ошибка отключения перезагрузки:', error);
                alert('Ошибка отключения перезагрузки. Смотрите консоль.');
            }
            disableRebootBtn.textContent = 'Отключить перезагрузку';
            disableRebootBtn.disabled = false;
        });

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

        allToffBtn.addEventListener('click', showAllToffPopup);

        async function showAllToffPopup() {
            const overlay = document.createElement('div');
            overlay.style.position = 'fixed';
            overlay.style.top = '0';
            overlay.style.left = '0';
            overlay.style.width = '100%';
            overlay.style.height = '100%';
            overlay.style.background = 'rgba(0,0,0,0.5)';
            overlay.style.zIndex = '20000';
            const popup = document.createElement('div');
            popup.style.maxWidth = '440px';
            popup.style.width = '90%';
            popup.style.position = 'fixed';
            popup.style.top = '50%';
            popup.style.left = '50%';
            popup.style.transform = 'translate(-50%, -50%)';
            popup.style.background = '#fff';
            popup.style.padding = '20px';
            popup.style.borderRadius = '8px';
            popup.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
            const title = document.createElement('h3');
            title.textContent = 'Массовое отключение доменов';
            popup.appendChild(title);
            const tunnelsContainer = document.createElement('div');
            tunnelsContainer.style.maxHeight = '300px';
            tunnelsContainer.style.overflowY = 'auto';
            tunnelsContainer.style.marginBottom = '10px';
            popup.appendChild(tunnelsContainer);
            const btnContainer = document.createElement('div');
            btnContainer.style.display = 'flex';
            btnContainer.style.justifyContent = 'flex-end';
            btnContainer.style.gap = '10px';
            const switchAllBtn = document.createElement('button');
            switchAllBtn.textContent = 'Переключить все';
            switchAllBtn.style.backgroundColor = '#0077cc';
            switchAllBtn.style.color = '#fff';
            switchAllBtn.style.borderRadius = '4px';
            switchAllBtn.style.padding = '8px 14px';
            const offBtn = document.createElement('button');
            offBtn.textContent = 'Отключиться';
            offBtn.style.backgroundColor = '#bb0000';
            offBtn.style.color = '#fff';
            offBtn.style.borderRadius = '4px';
            offBtn.style.padding = '8px 14px';
            const cancelBtn = document.createElement('button');
            cancelBtn.textContent = 'Отмена';
            cancelBtn.style.backgroundColor = '#666';
            cancelBtn.style.color = '#fff';
            cancelBtn.style.borderRadius = '4px';
            cancelBtn.style.padding = '8px 14px';
            btnContainer.appendChild(switchAllBtn);
            btnContainer.appendChild(offBtn);
            btnContainer.appendChild(cancelBtn);
            popup.appendChild(btnContainer);
            overlay.appendChild(popup);
            document.body.appendChild(overlay);
            function closePopup() { overlay.remove(); }
            cancelBtn.addEventListener('click', closePopup);
            let tunnelsList = [];
            try {
                const response = await gmFetch('http://34.34.34.34/api/smartRoute/listTunnels');
                if (response.status !== 200) {
                    throw new Error('Ошибка получения списка тунелей');
                }
                tunnelsList = await response.json();
            } catch (err) {
                console.error('[Deeper Tools] Ошибка при получении тунелей:', err);
                alert('Ошибка получения списка тунелей. Смотрите консоль.');
                closePopup();
                return;
            }
            tunnelsList.forEach(tunnel => {
                const row = document.createElement('div');
                row.style.display = 'flex';
                row.style.alignItems = 'center';
                row.style.justifyContent = 'space-between';
                row.style.marginBottom = '5px';
                row.style.fontSize = '14px';
                const leftDiv = document.createElement('div');
                leftDiv.style.display = 'flex';
                leftDiv.style.alignItems = 'center';
                const cName = countryNames[tunnel.countryCode] || tunnel.countryCode;
                const rCode = tunnel.regionCode;
                const textSpan = document.createElement('span');
                textSpan.textContent = `${cName} ${rCode}`;
                leftDiv.appendChild(textSpan);
                const rightDiv = document.createElement('div');
                rightDiv.style.display = 'flex';
                rightDiv.style.alignItems = 'center';
                const activeSpan = document.createElement('span');
                activeSpan.style.width = '30px';
                activeSpan.style.textAlign = 'right';
                activeSpan.style.display = 'inline-block';
                activeSpan.textContent = tunnel.activeNum;
                activeSpan.style.marginRight = '10px';
                const chk = document.createElement('input');
                chk.type = 'checkbox';
                chk.dataset.tunnelCode = tunnel.tunnelCode;
                chk.dataset.regionCode = tunnel.regionCode;
                chk.dataset.countryCode = tunnel.countryCode;
                chk.dataset.activeNum = tunnel.activeNum;
                rightDiv.appendChild(activeSpan);
                rightDiv.appendChild(chk);
                row.appendChild(leftDiv);
                row.appendChild(rightDiv);
                tunnelsContainer.appendChild(row);
            });
            switchAllBtn.addEventListener('click', async () => {
                const checkedItems = [...tunnelsContainer.querySelectorAll('input[type=checkbox]')].filter(ch => ch.checked);
                if (checkedItems.length === 0) {
                    alert('Не выбрано ни одного туннеля.');
                    return;
                }
                const selectedCandidateTunnelCodes = checkedItems.map(ch => ch.dataset.tunnelCode);
                let whitelist = [];
                try {
                    whitelist = await getExistingWhitelist();
                } catch(err) {
                    console.error('[Deeper Tools] Ошибка получения белого списка:', err);
                    alert('Ошибка получения белого списка. Смотрите консоль.');
                    return;
                }
                let freshTunnelsList = [];
                try {
                    const response2 = await gmFetch('http://34.34.34.34/api/smartRoute/listTunnels');
                    if (response2.status !== 200) {
                        throw new Error('Ошибка получения списка тунелей (повторно)');
                    }
                    freshTunnelsList = await response2.json();
                } catch (err) {
                    console.error('[Deeper Tools] Ошибка при повторном запросе тунелей:', err);
                    alert('Ошибка при запросе тунелей. Смотрите консоль.');
                    return;
                }
                for (const domainEntry of whitelist) {
                    const candidates = freshTunnelsList.filter(t => selectedCandidateTunnelCodes.includes(t.tunnelCode));
                    if (candidates.length === 0) continue;
                    const maxActive = Math.max(...candidates.map(t => t.activeNum));
                    const bestCandidates = candidates.filter(t => t.activeNum === maxActive);
                    const chosen = bestCandidates[Math.floor(Math.random() * bestCandidates.length)];
                    try {
                        const editRes = await gmFetch('http://34.34.34.34/api/smartRoute/editWhiteEntry/domain', {
                            method: 'POST',
                            headers: {'Content-Type': 'application/json'},
                            body: JSON.stringify({
                                domainName: domainEntry.domainName,
                                fromTunnelCode: domainEntry.tunnelCode,
                                toTunnelCode: chosen.tunnelCode
                            })
                        });
                        if (editRes.status !== 200) {
                            console.error('[Deeper Tools] Ошибка переключения для домена:', domainEntry.domainName);
                        }
                    } catch(err) {
                        console.error('[Deeper Tools] Ошибка при переключении:', err);
                    }
                }
                alert('Массовое переключение выполнено.');
                closePopup();
            });
            offBtn.addEventListener('click', async () => {
                const checkedItems = [...tunnelsContainer.querySelectorAll('input[type=checkbox]')].filter(ch => ch.checked);
                if (checkedItems.length === 0) {
                    alert('Не выбрано ни одного туннеля.');
                    return;
                }
                let freshTunnelsList = [];
                try {
                    const response2 = await gmFetch('http://34.34.34.34/api/smartRoute/listTunnels');
                    if (response2.status !== 200) {
                        throw new Error('Ошибка получения списка тунелей (повторно)');
                    }
                    freshTunnelsList = await response2.json();
                } catch (err) {
                    console.error('[Deeper Tools] Ошибка при повторном запросе тунелей:', err);
                    alert('Ошибка при запросе тунелей. Смотрите консоль.');
                    return;
                }
                for (const item of checkedItems) {
                    const fromTunnel = item.dataset.tunnelCode;
                    const whitelist = await getExistingWhitelist();
                    const entriesToSwitch = whitelist.filter(entry => entry.tunnelCode === fromTunnel);
                    const candidates = freshTunnelsList.filter(t => t.tunnelCode !== fromTunnel);
                    if (candidates.length === 0) {
                        console.warn('Нет кандидатов для переключения, пропускаем:', fromTunnel);
                        continue;
                    }
                    const maxActive = Math.max(...candidates.map(t => t.activeNum));
                    const bestCandidates = candidates.filter(t => t.activeNum === maxActive);
                    for (const domainEntry of entriesToSwitch) {
                        const chosen = bestCandidates[Math.floor(Math.random() * bestCandidates.length)];
                        try {
                            const editRes = await gmFetch('http://34.34.34.34/api/smartRoute/editWhiteEntry/domain', {
                                method: 'POST',
                                headers: {'Content-Type': 'application/json'},
                                body: JSON.stringify({
                                    domainName: domainEntry.domainName,
                                    fromTunnelCode: fromTunnel,
                                    toTunnelCode: chosen.tunnelCode
                                })
                            });
                            if (editRes.status !== 200) {
                                console.error('[Deeper Tools] Ошибка переключения для домена:', domainEntry.domainName);
                            }
                        } catch(err) {
                            console.error('[Deeper Tools] Ошибка при переключении:', err);
                        }
                    }
                }
                alert('Массовое переключение выполнено.');
                closePopup();
            });
        }

        const domainSet = new Set();
        const originalFetch = window.fetch;
        window.fetch = function(input, init) {
            if (getScannerEnabled()) {
                try {
                    const url = (typeof input === 'string') ? input : input.url;
                    const urlObj = new URL(url);
                    addDomain(urlObj.hostname);
                } catch(e) {}
            }
            return originalFetch.apply(this, arguments);
        };

        const observer = new MutationObserver(mutations => {
            if (!getScannerEnabled()) return;
            mutations.forEach(m => {
                if (m.addedNodes) {
                    m.addedNodes.forEach(node => {
                        if (node.tagName) {
                            const src = node.src || node.href;
                            if (src) {
                                try {
                                    const urlObj = new URL(src);
                                    addDomain(urlObj.hostname);
                                } catch(e) {}
                            }
                        }
                    });
                }
            });
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
        setInterval(() => {
            if (!getScannerEnabled()) return;
            const entries = performance.getEntriesByType('resource');
            entries.forEach(entry => {
                try {
                    const urlObj = new URL(entry.name);
                    addDomain(urlObj.hostname);
                } catch(e) {}
            });
        }, 1000);

        function addDomain(domain) {
            if (!domainSet.has(domain)) {
                domainSet.add(domain);
                updateDomainList();
            }
        }

        function ensureScannerContainer() {
            if (!getScannerEnabled()) return;
            if (document.getElementById('domain-scanner-container')) return;
            const container = document.createElement('div');
            container.id = 'domain-scanner-container';
            container.style.position = 'fixed';
            container.style.top = '10px';
            container.style.right = '10px';
            container.style.width = '300px';
            container.style.maxHeight = '80vh';
            container.style.overflowY = 'auto';
            container.style.backgroundColor = 'white';
            container.style.border = '1px solid black';
            container.style.zIndex = '10000';
            container.style.padding = '10px';
            container.style.fontSize = '12px';
            container.style.fontFamily = 'monospace';
            container.style.color = 'black';
            container.style.whiteSpace = 'pre-wrap';
            const domainList = document.createElement('div');
            domainList.id = 'domain-list';
            container.appendChild(domainList);
            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);
        }

        function updateDomainList() {
            const container = document.getElementById('domain-scanner-container');
            if (!container) return;
            const listEl = container.querySelector('#domain-list');
            const checkedStates = {};
            listEl.querySelectorAll('.domain-checkbox').forEach(cb => {
                checkedStates[cb.dataset.domain] = cb.checked;
            });
            const sortedArr = Array.from(domainSet).sort();
            listEl.innerHTML = '';
            sortedArr.forEach(domain => {
                const domainRow = document.createElement('div');
                domainRow.style.display = 'flex';
                domainRow.style.justifyContent = 'space-between';
                domainRow.style.alignItems = 'center';
                domainRow.style.marginBottom = '3px';
                const domainText = document.createElement('span');
                domainText.textContent = domain;
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.classList.add('domain-checkbox');
                checkbox.dataset.domain = domain;
                checkbox.checked = !!checkedStates[domain];
                domainRow.appendChild(domainText);
                domainRow.appendChild(checkbox);
                listEl.appendChild(domainRow);
            });
        }

        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)) {
                    data.list.forEach(item => {
                        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 selectedDomains = [];
                checkboxes.forEach(cb => {
                    if (cb.checked) {
                        selectedDomains.push(cb.dataset.domain);
                    }
                });
                if (selectedDomains.length === 0) {
                    alert('[Deeper Tools] Выберите домены для добавления.');
                    return;
                }
                const newItems = [];
                selectedDomains.forEach(d => {
                    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 (let 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('Ошибка при добавлении. Смотрите консоль.');
            }
        }

        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 (document.readyState === 'complete' || document.readyState === 'interactive') {
                ensureScannerContainer();
            } else {
                document.addEventListener('DOMContentLoaded', ensureScannerContainer);
            }
        }
    }
})();