Deeper Tools

Набор инструментов для Deeper: автоавторизация, белый список, сканер доменов.

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

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

(function() {
    'use strict';

    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 (!localStorage.getItem('adminPassword')) {
                        localStorage.setItem('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 = localStorage.getItem('adminPassword');
        if (storedPassword) {
            window.addEventListener('load', () => {
                fetch('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/')) {
        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 = '5px';
        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: '6px 10px',
            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);

        menuContainer.appendChild(downloadBtn);
        menuContainer.appendChild(uploadBtn);
        menuContainer.appendChild(disableRebootBtn);
        menuContainer.appendChild(forgetBtn);

        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 fetch(url);
                if (!response.ok) {
                    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 fetch('http://34.34.34.34/api/smartRoute/deleteFromWhitelist/domain', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify(duplicates)
                            });
                            if (!delRes.ok) {
                                console.error('[Deeper Tools] Ошибка при удалении дубликатов:', duplicates);
                            }
                        }
                        // Добавляем все из файла
                        for (let item of jsonData.list) {
                            const payload = { domainName: item.domainName, tunnelCode: item.tunnelCode };
                            const res = await fetch('http://34.34.34.34/api/smartRoute/addToWhitelist/domain', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' },
                                body: JSON.stringify(payload)
                            });
                            if (!res.ok) {
                                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 fetch(`http://34.34.34.34/api/autoReboot/config${queryParams}`, {
                    method: 'GET',
                    headers: { 'Content-Type': 'application/json' }
                });
                if (!response.ok) {
                    throw new Error('Ошибка при отключении перезагрузки');
                }
                alert('[Deeper Tools] Перезагрузка отключена!');
            } catch (error) {
                console.error('[Deeper Tools] Ошибка отключения перезагрузки:', error);
                alert('Ошибка отключения перезагрузки. Смотрите консоль.');
            }
            disableRebootBtn.textContent = 'Отключить перезагрузку';
            disableRebootBtn.disabled = false;
        });

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


    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);
            const container = document.getElementById('domain-scanner-container');
            if (container) {
                const listEl = container.querySelector('#domain-list');
                const sortedArr = Array.from(domainSet).sort();
                listEl.textContent = sortedArr.join('\n');
            }
        }
    }

    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.height = '400px';
        container.style.overflowY = 'scroll';
        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 buttonWrapper = document.createElement('div');
        buttonWrapper.style.marginTop = '10px';

        const addBtn = document.createElement('button');
        addBtn.textContent = 'Добавить в deeper';
        Object.assign(addBtn.style, {
            padding: '6px 10px',
            backgroundColor: '#f8f8f8',
            border: '1px solid #ccc',
            borderRadius: '4px',
            cursor: 'pointer',
            fontSize: '14px'
        });
        addBtn.addEventListener('click', addToDeeper);

        buttonWrapper.appendChild(addBtn);
        container.appendChild(buttonWrapper);
        document.body.appendChild(container);
    }

    async function addToDeeper() {
        try {
            const resp = await fetch('http://34.34.34.34/api/smartRoute/getRoutingWhitelist/domain?pageNo=1&pageSize=100');
            if (!resp.ok) {
                alert('[Deeper Tools] Ошибка при получении белого списка');
                return;
            }
            const data = await resp.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 newItems = [];
            domainSet.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 fetch('http://34.34.34.34/api/smartRoute/addToWhitelist/domain', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify(item)
                });
                if (!r.ok) {
                    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);
        }
    }

})();