Deeper Tools

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

目前为 2025-04-16 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Deeper Tools
  3. // @description Набор инструментов для Deeper
  4. // @author https://github.com/lReDragol
  5. // @namespace http://tampermonkey.net/
  6. // @version 3.6.1
  7. // @icon https://avatars.mds.yandex.net/get-socsnippets/10235467/2a0000019509580bc84108597cea65bc46ee/square_83
  8. // @match http://34.34.34.34/*
  9. // @match http://11.22.33.44/*
  10. // @match *://*/*
  11. // @license MIT
  12. // @run-at document-start
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_unregisterMenuCommand
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_xmlhttpRequest
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23.  
  24. const countryNames = {
  25. LL: "не проходить тунель",
  26. ANY: "любая страна или регион",
  27. AMER: "---Америка---",
  28. ASIA: "---Азия---",
  29. AFRI: "---Африка---",
  30. EURO: "---Европа---",
  31. OCEA: "---Океания---",
  32. AMN: "Северная Америка",
  33. AMC: "Карибский бассейн",
  34. AMM: "Центральная Америка",
  35. AMS: "Южная Америка",
  36. ASC: "Центральная Азия",
  37. ASE: "Восточная Азия",
  38. ASW: "Западная Азия",
  39. ASS: "Южная Азия",
  40. ASD: "Юго-Восточная Азия",
  41. AFN: "Северная Африка",
  42. AFM: "Центральная Африка",
  43. AFE: "Восточная Африка",
  44. AFW: "Западная Африка",
  45. AFS: "Южная Африка",
  46. EUN: "Северная Европа",
  47. EUE: "Восточная Европа",
  48. EUW: "Западная Европа",
  49. EUS: "Южная Европа",
  50. OCP: "Полинезия",
  51. OCA: "Австралия и Новая Зеландия",
  52. OCM: "Меланезия",
  53. OCN: "Микронезия",
  54. AD: "Андорра",
  55. AE: "Объединенные Арабские Эмираты",
  56. AF: "Афганистан",
  57. AG: "Антигуа и Барбуда",
  58. AI: "Ангилья",
  59. AL: "Албания",
  60. AM: "Армения",
  61. AO: "Ангола",
  62. AR: "Аргентина",
  63. AS: "Американское Самоа",
  64. AT: "Австрия",
  65. AU: "Австралия",
  66. AW: "Аруба",
  67. AX: "Аландские острова",
  68. AZ: "Азербайджан",
  69. BA: "Босния и Герцеговина",
  70. BB: "Барбадос",
  71. BD: "Бангладеш",
  72. BE: "Бельгия",
  73. BF: "Буркина-Фасо",
  74. BG: "Болгария",
  75. BH: "Бахрейн",
  76. BI: "Бурунди",
  77. BJ: "Бенин",
  78. BL: "Сен-Бартелеми",
  79. BM: "Бермуды",
  80. BN: "Бруней",
  81. BO: "Боливия",
  82. BQ: "Карибская Нидерландия",
  83. BR: "Бразилия",
  84. BS: "Багамы",
  85. BT: "Бутан",
  86. BW: "Ботсвана",
  87. BY: "Беларусь",
  88. BZ: "Белиз",
  89. CA: "Канада",
  90. CC: "Кокосовые (Килинг) острова",
  91. CD: "Конго (Киншаса)",
  92. CF: "Центрально-Африканская Республика",
  93. CG: "Конго (Браззавиль)",
  94. CH: "Швейцария",
  95. CI: "Кот-д'Ивуар",
  96. CK: "Острова Кука",
  97. CL: "Чили",
  98. CM: "Камерун",
  99. CN: "Китай",
  100. CO: "Колумбия",
  101. CR: "Коста-Рика",
  102. CU: "Куба",
  103. CV: "Кабо-Верде",
  104. CW: "Кюрасао",
  105. CX: "Остров Рождества",
  106. CY: "Кипр",
  107. CZ: "Чехия",
  108. DE: "Германия",
  109. DJ: "Джибути",
  110. DK: "Дания",
  111. DM: "Доминика",
  112. DO: "Доминиканская Республика",
  113. DZ: "Алжир",
  114. EC: "Эквадор",
  115. EE: "Эстония",
  116. EG: "Египет",
  117. ER: "Эритрея",
  118. ES: "Испания",
  119. ET: "Эфиопия",
  120. FI: "Финляндия",
  121. FJ: "Фиджи",
  122. FK: "Фолклендские острова",
  123. FM: "Федеративные Штаты Микронезии",
  124. FO: "Фарерские острова",
  125. FR: "Франция",
  126. GA: "Габон",
  127. GB: "Великобритания",
  128. GD: "Гренада",
  129. GE: "Грузия",
  130. GF: "Французская Гвиана",
  131. GG: "Гернси",
  132. GH: "Гана",
  133. GI: "Гибралтар",
  134. GL: "Гренландия",
  135. GM: "Гамбия",
  136. GN: "Гвинея",
  137. GP: "Гваделупа",
  138. GQ: "Экваториальная Гвинея",
  139. GR: "Греция",
  140. GS: "Южная Джорджия и Южные Сандвичевы острова",
  141. GT: "Гватемала",
  142. GU: "Гуам",
  143. GW: "Гвинея-Бисау",
  144. GY: "Гайана",
  145. HK: "Гонконг (Китай)",
  146. HN: "Гондурас",
  147. HR: "Хорватия",
  148. HT: "Гаити",
  149. HU: "Венгрия",
  150. ID: "Индонезия",
  151. IE: "Ирландия",
  152. IL: "Израиль",
  153. IM: "Остров Мэн",
  154. IN: "Индия",
  155. IO: "Британская территория в Индийском океане",
  156. IQ: "Ирак",
  157. IR: "Иран",
  158. IS: "Исландия",
  159. IT: "Италия",
  160. JE: "Джерси",
  161. JM: "Ямайка",
  162. JO: "Иордания",
  163. JP: "Япония",
  164. KE: "Кения",
  165. KG: "Киргизия",
  166. KH: "Камбоджа",
  167. KI: "Кирибати",
  168. KM: "Коморы",
  169. KN: "Сент-Китс и Невис",
  170. KR: "Южная Корея",
  171. KW: "Кувейт",
  172. KY: "Каймановы острова",
  173. KZ: "Казахстан",
  174. KP: "Северная Корея",
  175. LA: "Лаос",
  176. LB: "Ливан",
  177. LC: "Сент-Люсия",
  178. LI: "Лихтенштейн",
  179. LK: "Шри-Ланка",
  180. LR: "Либерия",
  181. LS: "Лесото",
  182. LT: "Литва",
  183. LU: "Люксембург",
  184. LV: "Латвия",
  185. LY: "Ливия",
  186. MA: "Марокко",
  187. MC: "Монако",
  188. MD: "Молдавия",
  189. ME: "Черногория",
  190. MF: "Сен-Мартен (фр.)",
  191. MG: "Мадагаскар",
  192. MH: "Маршалловы острова",
  193. MK: "Северная Македония",
  194. ML: "Мали",
  195. MM: "Мьянма (Бирма)",
  196. MN: "Монголия",
  197. MO: "Макао (Китай)",
  198. MP: "Северные Марианские острова",
  199. MQ: "Мартиника",
  200. MR: "Мавритания",
  201. MS: "Монтсеррат",
  202. MT: "Мальта",
  203. MU: "Маврикий",
  204. MV: "Мальдивы",
  205. MW: "Малави",
  206. MX: "Мексика",
  207. MY: "Малайзия",
  208. MZ: "Мозамбик",
  209. NA: "Намибия",
  210. NC: "Новая Каледония",
  211. NE: "Нигер",
  212. NF: "Остров Норфолк",
  213. NG: "Нигерия",
  214. NI: "Никарагуа",
  215. NL: "Нидерланды",
  216. NO: "Норвегия",
  217. NP: "Непал",
  218. NR: "Науру",
  219. NU: "Ниуэ",
  220. NZ: "Новая Зеландия",
  221. OM: "Оман",
  222. PA: "Панама",
  223. PE: "Перу",
  224. PF: "Французская Полинезия",
  225. PG: "Папуа — Новая Гвинея",
  226. PH: "Филиппины",
  227. PK: "Пакистан",
  228. PL: "Польша",
  229. PM: "Сен-Пьер и Микелон",
  230. PN: "Острова Питкэрн",
  231. PR: "Пуэрто-Рико",
  232. PS: "Палестинские территории",
  233. PT: "Португалия",
  234. PW: "Палау",
  235. PY: "Парагвай",
  236. QA: "Катар",
  237. RE: "Реюньон",
  238. RO: "Румыния",
  239. RS: "Сербия",
  240. RU: "Россия",
  241. RW: "Руанда",
  242. SA: "Саудовская Аравия",
  243. SB: "Соломоновы острова",
  244. SC: "Сейшельские Острова",
  245. SD: "Судан",
  246. SE: "Швеция",
  247. SG: "Сингапур",
  248. SH: "Острова Святой Елены, Вознесения и Тристан-да-Кунья",
  249. SI: "Словения",
  250. SJ: "Шпицберген и Ян-Майен",
  251. SK: "Словакия",
  252. SL: "Сьерра-Леоне",
  253. SM: "Сан-Марино",
  254. SN: "Сенегал",
  255. SO: "Сомали",
  256. SR: "Суринам",
  257. SS: "Южный Судан",
  258. ST: "Сан-Томе и Принсипи",
  259. SV: "Сальвадор",
  260. SX: "Синт-Мартен",
  261. SY: "Сирия",
  262. SZ: "Эсватини",
  263. TC: "Теркс и Кайкос",
  264. TD: "Чад",
  265. TF: "Французские Южные и Антарктические Территории",
  266. TG: "Того",
  267. TH: "Таиланд",
  268. TJ: "Таджикистан",
  269. TK: "Токелау",
  270. TL: "Восточный Тимор",
  271. TM: "Туркменистан",
  272. TN: "Тунис",
  273. TO: "Тонга",
  274. TR: "Турция",
  275. TT: "Тринидад и Тобаго",
  276. TV: "Тувалу",
  277. TW: "Тайвань",
  278. TZ: "Танзания",
  279. UA: "Украина",
  280. UB: "Запад США",
  281. UC: "Средний Запад США",
  282. UD: "Юго-Запад США",
  283. UE: "Северо-Восток США",
  284. UF: "Юго-Восток США",
  285. UG: "Уганда",
  286. US: "Соединенные Штаты",
  287. UY: "Уругвай",
  288. UZ: "Узбекистан",
  289. VA: "Ватикан",
  290. VC: "Сент-Винсент и Гренадины",
  291. VE: "Венесуэла",
  292. VG: "Британские Виргинские острова",
  293. VI: "Американские Виргинские острова",
  294. VN: "Вьетнам",
  295. VU: "Вануату",
  296. WF: "Уоллис и Футуна",
  297. WS: "Самоа",
  298. XK: "Косово",
  299. YE: "Йемен",
  300. YT: "Майотта",
  301. ZA: "Южная Африка",
  302. ZM: "Замбия",
  303. ZW: "Зимбабве"
  304. };
  305.  
  306. function gmFetch(url, init = {}) {
  307. return new Promise((resolve, reject) => {
  308. GM_xmlhttpRequest({
  309. method: init.method || 'GET',
  310. url: url,
  311. headers: init.headers || {},
  312. data: init.body || null,
  313. onload: function(response) {
  314. try {
  315. response.json = function() {
  316. return Promise.resolve(JSON.parse(response.responseText));
  317. };
  318. } catch(e) {
  319. response.json = function() {
  320. return Promise.reject(e);
  321. };
  322. }
  323. resolve(response);
  324. },
  325. onerror: function(error) {
  326. reject(error);
  327. }
  328. });
  329. });
  330. }
  331.  
  332. function getScannerEnabled() {
  333. return GM_getValue('domainScannerEnabled', false);
  334. }
  335. function setScannerEnabled(val) {
  336. GM_setValue('domainScannerEnabled', val);
  337. updateScannerMenuCommand();
  338. if (!val) {
  339. const container = document.getElementById('domain-scanner-container');
  340. if (container) container.remove();
  341. } else {
  342. ensureScannerContainer();
  343. }
  344. console.log('[Deeper Tools] Domain Scanner: ' + (val ? 'ON' : 'OFF'));
  345. }
  346.  
  347. const nativeOpen = XMLHttpRequest.prototype.open;
  348. const nativeSend = XMLHttpRequest.prototype.send;
  349. XMLHttpRequest.prototype.open = function(method, url) {
  350. this._method = method;
  351. this._url = url;
  352. if (getScannerEnabled()) {
  353. try {
  354. const urlObj = new URL(url);
  355. addDomain(urlObj.hostname);
  356. } catch(e) {}
  357. }
  358. return nativeOpen.apply(this, arguments);
  359. };
  360. XMLHttpRequest.prototype.send = function(body) {
  361. if (
  362. this._url &&
  363. this._url.includes('/api/admin/login') &&
  364. this._method &&
  365. this._method.toUpperCase() === 'POST'
  366. ) {
  367. try {
  368. const parsed = JSON.parse(body);
  369. if (parsed && parsed.password) {
  370. if (!GM_getValue('adminPassword')) {
  371. GM_setValue('adminPassword', parsed.password);
  372. console.log('[Deeper Tools] Пароль сохранён из XHR.');
  373. }
  374. }
  375. } catch (err) {
  376. console.error('[Deeper Tools] Ошибка парсинга XHR при авторизации:', err);
  377. }
  378. }
  379. return nativeSend.apply(this, arguments);
  380. };
  381.  
  382. if (window.location.href.includes('/login/')) {
  383. const storedPassword = GM_getValue('adminPassword');
  384. if (storedPassword) {
  385. window.addEventListener('load', () => {
  386. gmFetch('http://34.34.34.34/api/admin/login', {
  387. method: 'POST',
  388. headers: { 'Content-Type': 'application/json' },
  389. body: JSON.stringify({
  390. "username": "admin",
  391. "password": storedPassword
  392. })
  393. })
  394. .then(response => {
  395. if (response.status === 200) {
  396. window.location.href = 'http://34.34.34.34/admin/dashboard';
  397. }
  398. return response.json();
  399. })
  400. .then(data => console.log('[Deeper Tools] Авторизация прошла успешно:', data))
  401. .catch(error => console.error('[Deeper Tools] Ошибка при авторизации:', error));
  402. });
  403. } else {
  404. console.log('[Deeper Tools] Пароль не найден. Выполните ручную авторизацию.');
  405. }
  406. }
  407.  
  408. if (window.location.href.startsWith('http://34.34.34.34/') || window.location.href.startsWith('http://11.22.33.44/')) {
  409. const iconButton = document.createElement('div');
  410. iconButton.style.position = 'fixed';
  411. iconButton.style.width = '25px';
  412. iconButton.style.height = '25px';
  413. iconButton.style.top = '10px';
  414. iconButton.style.right = '10px';
  415. iconButton.style.zIndex = '9999';
  416. iconButton.style.backgroundColor = 'rgb(240, 240, 252)';
  417. iconButton.style.borderRadius = '4px';
  418. iconButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
  419. iconButton.style.cursor = 'pointer';
  420. iconButton.style.display = 'flex';
  421. iconButton.style.alignItems = 'center';
  422. iconButton.style.justifyContent = 'center';
  423. const img = document.createElement('img');
  424. img.src = 'https://avatars.mds.yandex.net/get-socsnippets/10235467/2a0000019509580bc84108597cea65bc46ee/square_83';
  425. img.style.maxWidth = '80%';
  426. img.style.maxHeight = '80%';
  427. iconButton.appendChild(img);
  428.  
  429. const menuContainer = document.createElement('div');
  430. menuContainer.style.position = 'fixed';
  431. menuContainer.style.top = '45px';
  432. menuContainer.style.right = '10px';
  433. menuContainer.style.zIndex = '10000';
  434. menuContainer.style.padding = '10px';
  435. menuContainer.style.border = '1px solid #ccc';
  436. menuContainer.style.borderRadius = '4px';
  437. menuContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.3)';
  438. menuContainer.style.backgroundColor = '#fff';
  439. menuContainer.style.display = 'none';
  440. menuContainer.style.flexDirection = 'column';
  441.  
  442. function toggleMenu() {
  443. menuContainer.style.display = (menuContainer.style.display === 'none') ? 'flex' : 'none';
  444. }
  445. iconButton.addEventListener('click', toggleMenu);
  446.  
  447. const buttonStyle = {
  448. margin: '5px 0',
  449. padding: '8px 14px',
  450. backgroundColor: '#f8f8f8',
  451. border: '1px solid #ccc',
  452. borderRadius: '4px',
  453. cursor: 'pointer',
  454. fontSize: '14px'
  455. };
  456.  
  457. const downloadBtn = document.createElement('button');
  458. downloadBtn.textContent = 'Поделиться доменами';
  459. Object.assign(downloadBtn.style, buttonStyle);
  460.  
  461. const uploadBtn = document.createElement('button');
  462. uploadBtn.textContent = 'Добавить домены';
  463. Object.assign(uploadBtn.style, buttonStyle);
  464.  
  465. const disableRebootBtn = document.createElement('button');
  466. disableRebootBtn.textContent = 'Отключить перезагрузку';
  467. Object.assign(disableRebootBtn.style, buttonStyle);
  468.  
  469. const forgetBtn = document.createElement('button');
  470. forgetBtn.textContent = 'Забыть пароль';
  471. Object.assign(forgetBtn.style, buttonStyle);
  472.  
  473. const allToffBtn = document.createElement('button');
  474. allToffBtn.textContent = 'All_T_OFF';
  475. allToffBtn.title = "Тут можно отключить все домены от определённых тунелей и переключить их на другой.";
  476. Object.assign(allToffBtn.style, buttonStyle);
  477.  
  478. menuContainer.appendChild(downloadBtn);
  479. menuContainer.appendChild(uploadBtn);
  480. menuContainer.appendChild(disableRebootBtn);
  481. menuContainer.appendChild(forgetBtn);
  482. menuContainer.appendChild(allToffBtn);
  483.  
  484. function ensureMenu() {
  485. if (!document.body.contains(iconButton)) {
  486. document.body.appendChild(iconButton);
  487. }
  488. if (!document.body.contains(menuContainer)) {
  489. document.body.appendChild(menuContainer);
  490. }
  491. }
  492. document.addEventListener('DOMContentLoaded', ensureMenu);
  493. new MutationObserver(ensureMenu).observe(document.documentElement, { childList: true, subtree: true });
  494.  
  495. async function getExistingWhitelist() {
  496. const pageSize = 100;
  497. let pageNo = 1;
  498. let total = 0;
  499. let allItems = [];
  500. let firstIteration = true;
  501. do {
  502. const url = `http://34.34.34.34/api/smartRoute/getRoutingWhitelist/domain?pageNo=${pageNo}&pageSize=${pageSize}`;
  503. const response = await gmFetch(url);
  504. if (response.status !== 200) {
  505. throw new Error('Ошибка при запросе списка на странице ' + pageNo);
  506. }
  507. const data = await response.json();
  508. if (firstIteration) {
  509. total = data.total;
  510. firstIteration = false;
  511. }
  512. if (data.list && data.list.length > 0) {
  513. allItems = allItems.concat(data.list);
  514. }
  515. pageNo++;
  516. } while (allItems.length < total);
  517. return allItems;
  518. }
  519.  
  520. downloadBtn.addEventListener('click', async () => {
  521. downloadBtn.disabled = true;
  522. downloadBtn.textContent = 'Скачивание...';
  523. try {
  524. const allItems = await getExistingWhitelist();
  525. const finalData = { total: allItems.length, list: allItems };
  526. const blob = new Blob([JSON.stringify(finalData, null, 2)], { type: 'application/json' });
  527. const url = URL.createObjectURL(blob);
  528. const a = document.createElement('a');
  529. a.href = url;
  530. a.download = 'data.json';
  531. document.body.appendChild(a);
  532. a.click();
  533. document.body.removeChild(a);
  534. URL.revokeObjectURL(url);
  535. } catch (error) {
  536. console.error('[Deeper Tools] Ошибка при скачивании:', error);
  537. alert('Ошибка при скачивании данных. Проверьте консоль.');
  538. }
  539. downloadBtn.textContent = 'Скачать домены';
  540. downloadBtn.disabled = false;
  541. });
  542.  
  543. uploadBtn.addEventListener('click', () => {
  544. const input = document.createElement('input');
  545. input.type = 'file';
  546. input.accept = 'application/json';
  547. input.style.display = 'none';
  548. input.addEventListener('change', async function() {
  549. if (input.files.length === 0) return;
  550. const file = input.files[0];
  551. const reader = new FileReader();
  552. reader.onload = async function(e) {
  553. try {
  554. const jsonData = JSON.parse(e.target.result);
  555. if (!jsonData.list || !Array.isArray(jsonData.list)) {
  556. throw new Error('Неверный формат файла: ожидалось поле list[].');
  557. }
  558. const fileDomainNames = jsonData.list.map(item => item.domainName);
  559. const existing = await getExistingWhitelist();
  560. const existingDomainNames = existing.map(item => item.domainName);
  561. const duplicates = fileDomainNames.filter(d => existingDomainNames.includes(d));
  562. if (duplicates.length > 0) {
  563. console.log('[Deeper Tools] Удаляем дубликаты:', duplicates);
  564. const delRes = await gmFetch('http://34.34.34.34/api/smartRoute/deleteFromWhitelist/domain', {
  565. method: 'POST',
  566. headers: { 'Content-Type': 'application/json' },
  567. body: JSON.stringify(duplicates)
  568. });
  569. if (delRes.status !== 200) {
  570. console.error('[Deeper Tools] Ошибка при удалении дубликатов:', duplicates);
  571. }
  572. }
  573. for (let item of jsonData.list) {
  574. const payload = { domainName: item.domainName, tunnelCode: item.tunnelCode };
  575. const res = await gmFetch('http://34.34.34.34/api/smartRoute/addToWhitelist/domain', {
  576. method: 'POST',
  577. headers: { 'Content-Type': 'application/json' },
  578. body: JSON.stringify(payload)
  579. });
  580. if (res.status !== 200) {
  581. console.error('[Deeper Tools] Ошибка добавления домена:', item.domainName);
  582. }
  583. }
  584. alert('[Deeper Tools] Данные успешно загружены!');
  585. } catch(err) {
  586. console.error('[Deeper Tools] Ошибка загрузки:', err);
  587. alert('Ошибка загрузки. Смотрите консоль.');
  588. }
  589. };
  590. reader.readAsText(file);
  591. });
  592. document.body.appendChild(input);
  593. input.click();
  594. document.body.removeChild(input);
  595. });
  596.  
  597. disableRebootBtn.addEventListener('click', async () => {
  598. disableRebootBtn.disabled = true;
  599. disableRebootBtn.textContent = 'Отключение...';
  600. try {
  601. const queryParams = '?on=false&hour=0&minute=0&day=0';
  602. const response = await gmFetch(`http://34.34.34.34/api/autoReboot/config${queryParams}`, {
  603. method: 'GET',
  604. headers: { 'Content-Type': 'application/json' }
  605. });
  606. if (response.status !== 200) {
  607. throw new Error('Ошибка при отключении перезагрузки');
  608. }
  609. alert('[Deeper Tools] Перезагрузка отключена!');
  610. } catch (error) {
  611. console.error('[Deeper Tools] Ошибка отключения перезагрузки:', error);
  612. alert('Ошибка отключения перезагрузки. Смотрите консоль.');
  613. }
  614. disableRebootBtn.textContent = 'Отключить перезагрузку';
  615. disableRebootBtn.disabled = false;
  616. });
  617.  
  618. forgetBtn.addEventListener('click', () => {
  619. if (confirm('Внимание! Логин и пароль будут очищены. Продолжить?')) {
  620. GM_setValue('adminPassword', null);
  621. alert('[Deeper Tools] Пароль очищен. Авторизуйтесь вручную.');
  622. }
  623. });
  624.  
  625. allToffBtn.addEventListener('click', showAllToffPopup);
  626.  
  627. async function showAllToffPopup() {
  628. const overlay = document.createElement('div');
  629. overlay.style.position = 'fixed';
  630. overlay.style.top = '0';
  631. overlay.style.left = '0';
  632. overlay.style.width = '100%';
  633. overlay.style.height = '100%';
  634. overlay.style.background = 'rgba(0,0,0,0.5)';
  635. overlay.style.zIndex = '20000';
  636. const popup = document.createElement('div');
  637. popup.style.maxWidth = '440px';
  638. popup.style.width = '90%';
  639. popup.style.position = 'fixed';
  640. popup.style.top = '50%';
  641. popup.style.left = '50%';
  642. popup.style.transform = 'translate(-50%, -50%)';
  643. popup.style.background = '#fff';
  644. popup.style.padding = '20px';
  645. popup.style.borderRadius = '8px';
  646. popup.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
  647. const title = document.createElement('h3');
  648. title.textContent = 'Массовое отключение доменов';
  649. popup.appendChild(title);
  650. const tunnelsContainer = document.createElement('div');
  651. tunnelsContainer.style.maxHeight = '300px';
  652. tunnelsContainer.style.overflowY = 'auto';
  653. tunnelsContainer.style.marginBottom = '10px';
  654. popup.appendChild(tunnelsContainer);
  655. const btnContainer = document.createElement('div');
  656. btnContainer.style.display = 'flex';
  657. btnContainer.style.justifyContent = 'flex-end';
  658. btnContainer.style.gap = '10px';
  659. const switchAllBtn = document.createElement('button');
  660. switchAllBtn.textContent = 'Переключить все';
  661. switchAllBtn.style.backgroundColor = '#0077cc';
  662. switchAllBtn.style.color = '#fff';
  663. switchAllBtn.style.borderRadius = '4px';
  664. switchAllBtn.style.padding = '8px 14px';
  665. const offBtn = document.createElement('button');
  666. offBtn.textContent = 'Отключиться';
  667. offBtn.style.backgroundColor = '#bb0000';
  668. offBtn.style.color = '#fff';
  669. offBtn.style.borderRadius = '4px';
  670. offBtn.style.padding = '8px 14px';
  671. const cancelBtn = document.createElement('button');
  672. cancelBtn.textContent = 'Отмена';
  673. cancelBtn.style.backgroundColor = '#666';
  674. cancelBtn.style.color = '#fff';
  675. cancelBtn.style.borderRadius = '4px';
  676. cancelBtn.style.padding = '8px 14px';
  677. btnContainer.appendChild(switchAllBtn);
  678. btnContainer.appendChild(offBtn);
  679. btnContainer.appendChild(cancelBtn);
  680. popup.appendChild(btnContainer);
  681. overlay.appendChild(popup);
  682. document.body.appendChild(overlay);
  683. function closePopup() { overlay.remove(); }
  684. cancelBtn.addEventListener('click', closePopup);
  685. let tunnelsList = [];
  686. try {
  687. const response = await gmFetch('http://34.34.34.34/api/smartRoute/listTunnels');
  688. if (response.status !== 200) {
  689. throw new Error('Ошибка получения списка тунелей');
  690. }
  691. tunnelsList = await response.json();
  692. } catch (err) {
  693. console.error('[Deeper Tools] Ошибка при получении тунелей:', err);
  694. alert('Ошибка получения списка тунелей. Смотрите консоль.');
  695. closePopup();
  696. return;
  697. }
  698. tunnelsList.forEach(tunnel => {
  699. const row = document.createElement('div');
  700. row.style.display = 'flex';
  701. row.style.alignItems = 'center';
  702. row.style.justifyContent = 'space-between';
  703. row.style.marginBottom = '5px';
  704. row.style.fontSize = '14px';
  705. const leftDiv = document.createElement('div');
  706. leftDiv.style.display = 'flex';
  707. leftDiv.style.alignItems = 'center';
  708. const cName = countryNames[tunnel.countryCode] || tunnel.countryCode;
  709. const rCode = tunnel.regionCode;
  710. const textSpan = document.createElement('span');
  711. textSpan.textContent = `${cName} ${rCode}`;
  712. leftDiv.appendChild(textSpan);
  713. const rightDiv = document.createElement('div');
  714. rightDiv.style.display = 'flex';
  715. rightDiv.style.alignItems = 'center';
  716. const activeSpan = document.createElement('span');
  717. activeSpan.style.width = '30px';
  718. activeSpan.style.textAlign = 'right';
  719. activeSpan.style.display = 'inline-block';
  720. activeSpan.textContent = tunnel.activeNum;
  721. activeSpan.style.marginRight = '10px';
  722. const chk = document.createElement('input');
  723. chk.type = 'checkbox';
  724. chk.dataset.tunnelCode = tunnel.tunnelCode;
  725. chk.dataset.regionCode = tunnel.regionCode;
  726. chk.dataset.countryCode = tunnel.countryCode;
  727. chk.dataset.activeNum = tunnel.activeNum;
  728. rightDiv.appendChild(activeSpan);
  729. rightDiv.appendChild(chk);
  730. row.appendChild(leftDiv);
  731. row.appendChild(rightDiv);
  732. tunnelsContainer.appendChild(row);
  733. });
  734. switchAllBtn.addEventListener('click', async () => {
  735. const checkedItems = [...tunnelsContainer.querySelectorAll('input[type=checkbox]')].filter(ch => ch.checked);
  736. if (checkedItems.length === 0) {
  737. alert('Не выбрано ни одного туннеля.');
  738. return;
  739. }
  740. const selectedCandidateTunnelCodes = checkedItems.map(ch => ch.dataset.tunnelCode);
  741. let whitelist = [];
  742. try {
  743. whitelist = await getExistingWhitelist();
  744. } catch(err) {
  745. console.error('[Deeper Tools] Ошибка получения белого списка:', err);
  746. alert('Ошибка получения белого списка. Смотрите консоль.');
  747. return;
  748. }
  749. let freshTunnelsList = [];
  750. try {
  751. const response2 = await gmFetch('http://34.34.34.34/api/smartRoute/listTunnels');
  752. if (response2.status !== 200) {
  753. throw new Error('Ошибка получения списка тунелей (повторно)');
  754. }
  755. freshTunnelsList = await response2.json();
  756. } catch (err) {
  757. console.error('[Deeper Tools] Ошибка при повторном запросе тунелей:', err);
  758. alert('Ошибка при запросе тунелей. Смотрите консоль.');
  759. return;
  760. }
  761. for (const domainEntry of whitelist) {
  762. const candidates = freshTunnelsList.filter(t => selectedCandidateTunnelCodes.includes(t.tunnelCode));
  763. if (candidates.length === 0) continue;
  764. const maxActive = Math.max(...candidates.map(t => t.activeNum));
  765. const bestCandidates = candidates.filter(t => t.activeNum === maxActive);
  766. const chosen = bestCandidates[Math.floor(Math.random() * bestCandidates.length)];
  767. try {
  768. const editRes = await gmFetch('http://34.34.34.34/api/smartRoute/editWhiteEntry/domain', {
  769. method: 'POST',
  770. headers: {'Content-Type': 'application/json'},
  771. body: JSON.stringify({
  772. domainName: domainEntry.domainName,
  773. fromTunnelCode: domainEntry.tunnelCode,
  774. toTunnelCode: chosen.tunnelCode
  775. })
  776. });
  777. if (editRes.status !== 200) {
  778. console.error('[Deeper Tools] Ошибка переключения для домена:', domainEntry.domainName);
  779. }
  780. } catch(err) {
  781. console.error('[Deeper Tools] Ошибка при переключении:', err);
  782. }
  783. }
  784. alert('Массовое переключение выполнено.');
  785. closePopup();
  786. });
  787. offBtn.addEventListener('click', async () => {
  788. const checkedItems = [...tunnelsContainer.querySelectorAll('input[type=checkbox]')].filter(ch => ch.checked);
  789. if (checkedItems.length === 0) {
  790. alert('Не выбрано ни одного туннеля.');
  791. return;
  792. }
  793. let freshTunnelsList = [];
  794. try {
  795. const response2 = await gmFetch('http://34.34.34.34/api/smartRoute/listTunnels');
  796. if (response2.status !== 200) {
  797. throw new Error('Ошибка получения списка тунелей (повторно)');
  798. }
  799. freshTunnelsList = await response2.json();
  800. } catch (err) {
  801. console.error('[Deeper Tools] Ошибка при повторном запросе тунелей:', err);
  802. alert('Ошибка при запросе тунелей. Смотрите консоль.');
  803. return;
  804. }
  805. for (const item of checkedItems) {
  806. const fromTunnel = item.dataset.tunnelCode;
  807. const whitelist = await getExistingWhitelist();
  808. const entriesToSwitch = whitelist.filter(entry => entry.tunnelCode === fromTunnel);
  809. const candidates = freshTunnelsList.filter(t => t.tunnelCode !== fromTunnel);
  810. if (candidates.length === 0) {
  811. console.warn('Нет кандидатов для переключения, пропускаем:', fromTunnel);
  812. continue;
  813. }
  814. const maxActive = Math.max(...candidates.map(t => t.activeNum));
  815. const bestCandidates = candidates.filter(t => t.activeNum === maxActive);
  816. for (const domainEntry of entriesToSwitch) {
  817. const chosen = bestCandidates[Math.floor(Math.random() * bestCandidates.length)];
  818. try {
  819. const editRes = await gmFetch('http://34.34.34.34/api/smartRoute/editWhiteEntry/domain', {
  820. method: 'POST',
  821. headers: {'Content-Type': 'application/json'},
  822. body: JSON.stringify({
  823. domainName: domainEntry.domainName,
  824. fromTunnelCode: fromTunnel,
  825. toTunnelCode: chosen.tunnelCode
  826. })
  827. });
  828. if (editRes.status !== 200) {
  829. console.error('[Deeper Tools] Ошибка переключения для домена:', domainEntry.domainName);
  830. }
  831. } catch(err) {
  832. console.error('[Deeper Tools] Ошибка при переключении:', err);
  833. }
  834. }
  835. }
  836. alert('Массовое переключение выполнено.');
  837. closePopup();
  838. });
  839. }
  840.  
  841. const domainSet = new Set();
  842. const originalFetch = window.fetch;
  843. window.fetch = function(input, init) {
  844. if (getScannerEnabled()) {
  845. try {
  846. const url = (typeof input === 'string') ? input : input.url;
  847. const urlObj = new URL(url);
  848. addDomain(urlObj.hostname);
  849. } catch(e) {}
  850. }
  851. return originalFetch.apply(this, arguments);
  852. };
  853.  
  854. const observer = new MutationObserver(mutations => {
  855. if (!getScannerEnabled()) return;
  856. mutations.forEach(m => {
  857. if (m.addedNodes) {
  858. m.addedNodes.forEach(node => {
  859. if (node.tagName) {
  860. const src = node.src || node.href;
  861. if (src) {
  862. try {
  863. const urlObj = new URL(src);
  864. addDomain(urlObj.hostname);
  865. } catch(e) {}
  866. }
  867. }
  868. });
  869. }
  870. });
  871. });
  872. observer.observe(document.documentElement, { childList: true, subtree: true });
  873. setInterval(() => {
  874. if (!getScannerEnabled()) return;
  875. const entries = performance.getEntriesByType('resource');
  876. entries.forEach(entry => {
  877. try {
  878. const urlObj = new URL(entry.name);
  879. addDomain(urlObj.hostname);
  880. } catch(e) {}
  881. });
  882. }, 1000);
  883.  
  884. function addDomain(domain) {
  885. if (!domainSet.has(domain)) {
  886. domainSet.add(domain);
  887. updateDomainList();
  888. }
  889. }
  890.  
  891. function ensureScannerContainer() {
  892. if (!getScannerEnabled()) return;
  893. if (document.getElementById('domain-scanner-container')) return;
  894. const container = document.createElement('div');
  895. container.id = 'domain-scanner-container';
  896. container.style.position = 'fixed';
  897. container.style.top = '10px';
  898. container.style.right = '10px';
  899. container.style.width = '300px';
  900. container.style.maxHeight = '80vh';
  901. container.style.overflowY = 'auto';
  902. container.style.backgroundColor = 'white';
  903. container.style.border = '1px solid black';
  904. container.style.zIndex = '10000';
  905. container.style.padding = '10px';
  906. container.style.fontSize = '12px';
  907. container.style.fontFamily = 'monospace';
  908. container.style.color = 'black';
  909. container.style.whiteSpace = 'pre-wrap';
  910. const domainList = document.createElement('div');
  911. domainList.id = 'domain-list';
  912. container.appendChild(domainList);
  913. const addBtn = document.createElement('button');
  914. addBtn.id = 'add-to-deeper-btn';
  915. addBtn.textContent = 'Добавить в deeper';
  916. Object.assign(addBtn.style, {
  917. display: 'block',
  918. width: '100%',
  919. marginTop: '10px',
  920. padding: '6px 10px',
  921. backgroundColor: '#f8f8f8',
  922. border: '1px solid #ccc',
  923. borderRadius: '4px',
  924. cursor: 'pointer',
  925. fontSize: '14px'
  926. });
  927. addBtn.addEventListener('click', addToDeeper);
  928. container.appendChild(addBtn);
  929. document.body.appendChild(container);
  930. }
  931.  
  932. function updateDomainList() {
  933. const container = document.getElementById('domain-scanner-container');
  934. if (!container) return;
  935. const listEl = container.querySelector('#domain-list');
  936. const checkedStates = {};
  937. listEl.querySelectorAll('.domain-checkbox').forEach(cb => {
  938. checkedStates[cb.dataset.domain] = cb.checked;
  939. });
  940. const sortedArr = Array.from(domainSet).sort();
  941. listEl.innerHTML = '';
  942. sortedArr.forEach(domain => {
  943. const domainRow = document.createElement('div');
  944. domainRow.style.display = 'flex';
  945. domainRow.style.justifyContent = 'space-between';
  946. domainRow.style.alignItems = 'center';
  947. domainRow.style.marginBottom = '3px';
  948. const domainText = document.createElement('span');
  949. domainText.textContent = domain;
  950. const checkbox = document.createElement('input');
  951. checkbox.type = 'checkbox';
  952. checkbox.classList.add('domain-checkbox');
  953. checkbox.dataset.domain = domain;
  954. checkbox.checked = !!checkedStates[domain];
  955. domainRow.appendChild(domainText);
  956. domainRow.appendChild(checkbox);
  957. listEl.appendChild(domainRow);
  958. });
  959. }
  960.  
  961. async function addToDeeper() {
  962. try {
  963. const response = await gmFetch('http://34.34.34.34/api/smartRoute/getRoutingWhitelist/domain?pageNo=1&pageSize=100');
  964. if (response.status !== 200) {
  965. alert('[Deeper Tools] Ошибка при получении белого списка');
  966. return;
  967. }
  968. const data = await response.json();
  969. const existingDomains = new Set();
  970. const tunnelCodes = [];
  971. if (Array.isArray(data.list)) {
  972. data.list.forEach(item => {
  973. if (item.domainName) existingDomains.add(item.domainName);
  974. if (item.tunnelCode) tunnelCodes.push(item.tunnelCode);
  975. });
  976. }
  977. if (tunnelCodes.length === 0) {
  978. tunnelCodes.push('defaultCode');
  979. }
  980. const container = document.getElementById('domain-scanner-container');
  981. if (!container) return;
  982. const checkboxes = container.querySelectorAll('.domain-checkbox');
  983. const selectedDomains = [];
  984. checkboxes.forEach(cb => {
  985. if (cb.checked) {
  986. selectedDomains.push(cb.dataset.domain);
  987. }
  988. });
  989. if (selectedDomains.length === 0) {
  990. alert('[Deeper Tools] Выберите домены для добавления.');
  991. return;
  992. }
  993. const newItems = [];
  994. selectedDomains.forEach(d => {
  995. if (!existingDomains.has(d)) {
  996. const randomIndex = Math.floor(Math.random() * tunnelCodes.length);
  997. newItems.push({
  998. domainName: d,
  999. tunnelCode: tunnelCodes[randomIndex]
  1000. });
  1001. }
  1002. });
  1003. if (newItems.length === 0) {
  1004. alert('[Deeper Tools] Нет новых доменов для добавления.');
  1005. return;
  1006. }
  1007. for (let item of newItems) {
  1008. const r = await gmFetch('http://34.34.34.34/api/smartRoute/addToWhitelist/domain', {
  1009. method: 'POST',
  1010. headers: {'Content-Type': 'application/json'},
  1011. body: JSON.stringify(item)
  1012. });
  1013. if (r.status !== 200) {
  1014. console.error('[Deeper Tools] Ошибка при добавлении домена:', item);
  1015. }
  1016. }
  1017. alert('[Deeper Tools] Новые домены добавлены в deeper!');
  1018. } catch (err) {
  1019. console.error('[Deeper Tools] Ошибка при добавлении в deeper:', err);
  1020. alert('Ошибка при добавлении. Смотрите консоль.');
  1021. }
  1022. }
  1023.  
  1024. let scannerMenuCommandId = null;
  1025. function updateScannerMenuCommand() {
  1026. if (scannerMenuCommandId && typeof GM_unregisterMenuCommand === 'function') {
  1027. GM_unregisterMenuCommand(scannerMenuCommandId);
  1028. }
  1029. if (typeof GM_registerMenuCommand === 'function') {
  1030. const currentState = getScannerEnabled();
  1031. const label = 'Domain Scanner: ' + (currentState ? '🟢' : '🔴');
  1032. scannerMenuCommandId = GM_registerMenuCommand(label, () => {
  1033. setScannerEnabled(!getScannerEnabled());
  1034. });
  1035. }
  1036. }
  1037. if (GM_getValue('domainScannerEnabled') === undefined) {
  1038. GM_setValue('domainScannerEnabled', false);
  1039. }
  1040. updateScannerMenuCommand();
  1041. if (getScannerEnabled()) {
  1042. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  1043. ensureScannerContainer();
  1044. } else {
  1045. document.addEventListener('DOMContentLoaded', ensureScannerContainer);
  1046. }
  1047. }
  1048. }
  1049. })();