您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Набор инструментов для Deeper
// ==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); } } } })();