// ==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);
}
}
}
})();