Shinoa Logs Helper

helper shinoa

// ==UserScript==
// @name         Shinoa Logs Helper
// @namespace    https://shinoa.tech/
// @version      1.0
// @description  helper shinoa
// @author       [02] Mikki Tyler
// @match        https://logs.shinoa.tech/*
// @grant        none
// @license      MIT
// ==/UserScript==

(async function () {
    let globalHeaders = {};
    let blockedCount = 0;
    let unblockedCount = 0;
    let isRetryInProgress = false;
    let retryQueue = [];

    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;
    const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

    XMLHttpRequest.prototype.open = function (method, url) {
        this._method = method;
        this._url = url;
        this._headers = {};
        return originalOpen.apply(this, arguments);
    };

    XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
        this._headers[header] = value;
        return originalSetRequestHeader.apply(this, arguments);
    };

    XMLHttpRequest.prototype.send = function (body) {
        if (this._method === 'POST') {
            globalHeaders = { ...this._headers };
        }
        return originalSend.apply(this, arguments);
    };

    function sendPostRequest(url, data) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', url, true);
            for (const key in globalHeaders) {
                xhr.setRequestHeader(key, globalHeaders[key]);
            }
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status >= 200 && xhr.status < 300) {
                        const response = JSON.parse(xhr.responseText);
                        resolve(response);
                    } else {
                        reject(xhr.responseText);
                    }
                }
            };
            xhr.send(JSON.stringify(data));
        });
    }

    const processedIds = new Set();
    const requestQueue = [];
    let isProcessing = false;
    const batchSize = 10;

    function processRequestQueue() {
        if (isProcessing || requestQueue.length === 0 || isRetryInProgress) return;

        isProcessing = true;
        let batch = requestQueue.splice(0, batchSize);

        Promise.all(batch.map(({ id, server, nicknameCell }) => {
            const data = { name: id, server: parseInt(server) };
            return sendPostRequest('../api/v1/punish', data)
                .then((response) => {
                    processResponse(response, nicknameCell, id, server);
                })
                .catch((error) => {
                    console.error(`Ошибка при запросе для ID ${id}:`, error);
                    if (error.includes('Too Many Attempts')) {
                        handleTooManyAttempts(id, server, nicknameCell);
                    }
                });
        })).finally(() => {
            setTimeout(() => {
                isProcessing = false;
                processRequestQueue();
            }, 3000);
        });
    }

    function handleTooManyAttempts(id, server, nicknameCell) {
        isRetryInProgress = true;
        retryQueue.push({ id, server, nicknameCell });
        retryRequest();
    }

    function retryRequest() {
        if (retryQueue.length === 0) {
            isRetryInProgress = false;
            processRequestQueue();
            return;
        }

        const { id, server, nicknameCell } = retryQueue[0];
        const data = { name: id, server: parseInt(server) };

        sendPostRequest('../api/v1/punish', data)
            .then((response) => {
                processResponse(response, nicknameCell, id, server);
                retryQueue.shift();
                retryRequest();
            })
            .catch((error) => {
                console.error(`Ошибка при повторном запросе для ID ${id}:`, error);
                setTimeout(retryRequest, 10000);
            });
    }

    function processResponse(response, nicknameCell, id, server) {
        if (response.ban === null) {
            if (!nicknameCell.textContent.includes('Не заблокирован')) {
                nicknameCell.textContent += ' -> ✅ | Не заблокирован';
                unblockedCount++;
            }
        } else {
            const reason = response.ban.reason;
            const date = response.ban.bandate;
            const admin = response.ban.admin;
            const nickname = response.ban.nickname;

            if (!nicknameCell.textContent.includes('❌')) {
                nicknameCell.textContent += ` -> ❌ | ${reason} || ${date}`;
                blockedCount++;
            }
        }
        updateBlockedCount();

        if (!nicknameCell.textContent.includes('✅') && !nicknameCell.textContent.includes('❌')) {
            requestQueue.push({ id, server, nicknameCell });
            processRequestQueue();
        }
    }

    function processRow(rowElement) {
        const nicknameCell = rowElement.querySelector('p.mb-0');
        const infoCell = rowElement.querySelector('small');
        if (!nicknameCell || !infoCell) return;

        const nickname = nicknameCell.textContent.trim();
        const infoMatch = infoCell.textContent.match(/Сервер: (\d+).*ID: (\d+)/);
        if (!infoMatch) return;

        const server = infoMatch[1];
        const id = infoMatch[2];

        if (processedIds.has(id)) return;
        processedIds.add(id);

        requestQueue.push({ id, server, nicknameCell });
        processRequestQueue();
    }

    function processNewRow(rowElement) {
        const nicknameCell = rowElement.querySelector('td[data-v-3996b970][data-v-409bd455]');
        const idCell = rowElement.querySelectorAll('td[data-v-3996b970][data-v-409bd455]')[1];
        const serverCell = rowElement.querySelectorAll('td[data-v-3996b970][data-v-409bd455]')[2];

        if (!nicknameCell || !idCell || !serverCell) return;

        const nickname = nicknameCell.textContent.trim();
        const id = idCell.textContent.trim();
        const server = serverCell.textContent.trim();

        if (processedIds.has(id)) return;
        processedIds.add(id);

        requestQueue.push({ id, server, nicknameCell });
        processRequestQueue();
    }

    function observeTableRows() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === 1 && node.matches('tr[data-v-409bd455]')) {
                        processRow(node);
                        processNewRow(node);
                    }
                });
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    async function updateTable() {
        const playerCell = document.querySelector('td[data-v-81416ece][data-v-409bd455].text-right');
        if (!playerCell) {
            return false;
        }

        const tableWrappers = document.querySelectorAll('.v-data-table.text-no-wrap.mx-auto.mt-10.theme--dark');
        if (tableWrappers.length > 0) {
            tableWrappers.forEach(tableWrapper => {
                const rows = tableWrapper.querySelectorAll('table tr');
                rows.forEach(row => {
                    const cells = row.querySelectorAll('td');
                    cells.forEach(cell => {
                    });
                });
            });
        }

        const rows = document.querySelectorAll('table tr');

        let lastIp = null;
        let regIp = null;
        let tradeacceptIp = null;
        let lastIpCell = null;
        let regIpCell = null;
        let tradeacceptIpCell = null;

        rows.forEach(row => {
            const cells = row.querySelectorAll('td');
            cells.forEach(cell => {
                if (cell.textContent.includes('Last IP')) {
                    lastIpCell = cell;
                    const match = cell.nextElementSibling.textContent.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
                    if (match) {
                        lastIp = match[0];
                    }
                }
                if (cell.textContent.includes('Reg IP')) {
                    regIpCell = cell;
                    const match = cell.nextElementSibling.textContent.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
                    if (match) {
                        regIp = match[0];
                    }
                }
                if (cell.textContent.includes('Tradeaccept IP')) {
                    tradeacceptIpCell = cell;
                    const match = cell.nextElementSibling.textContent.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
                    if (match) {
                        tradeacceptIp = match[0];
                    }
                }
            });
        });

        const addIpEventListeners = (ipCell, ip) => {
            const ipSpan = document.createElement('span');
            ipSpan.textContent = ip;
            ipSpan.style.cursor = 'pointer';
            ipSpan.style.color = '#2196f3';
            ipCell.innerHTML = '';
            ipCell.appendChild(ipSpan);

            ipSpan.addEventListener('click', (event) => {
                if (event.target.tagName === 'SPAN') {
                    window.open(`https://ip-api.com/#${ip}`, '_blank');
                }
            });
        };

        if (lastIpCell && lastIpCell.nextElementSibling) {
            addIpEventListeners(lastIpCell.nextElementSibling, lastIp);
        }
        if (regIpCell && regIpCell.nextElementSibling) {
            addIpEventListeners(regIpCell.nextElementSibling, regIp);
        }
        if (tradeacceptIpCell && tradeacceptIpCell.nextElementSibling) {
            addIpEventListeners(tradeacceptIpCell.nextElementSibling, tradeacceptIp);
        }

        return true;
    }

    function checkPageLoad() {
        const intervalId = setInterval(async () => {
            if (await updateTable()) {
                clearInterval(intervalId);
                startMutationObserver();
            }
        }, 1000);
    }

    function startMutationObserver() {
        const targetNode = document.body;
        const config = { childList: true, subtree: true };

        const callback = function(mutationsList, observer) {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    const playerCell = document.querySelector('td[data-v-81416ece][data-v-409bd455].text-right');
                    if (!playerCell) {
                        observer.disconnect();
                        checkPageLoad();
                        break;
                    }
                }
            }
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
    }

    function updateDataOnPage() {
        const cities = [
            "Phoenix", "Tucson", "Scottdale", "Chandler", "Brainburg", "Saint-Rose",
            "Mesa", "Red-Rock", "Yuma", "Surprise", "Prescott", "Glendale",
            "Kingman", "Winslow", "Payson", "Gilbert", "Show-Low", "Casa-Grande",
            "Page", "Sun-City", "Queen-Creek", "Sedona", "Holiday", "Wednesday", "Yava",
            "Faraway", "Bumble Bee", "Christmas", "Mirage", "Love", "Drake"
        ];

        const updateElements = () => {
            const elements = document.querySelectorAll('.v-list-item__title');
            elements.forEach((element) => {
                const text = element.textContent.trim();
                if (text.startsWith('Mobile ')) {
                    const mobileNumber = parseInt(text.split(' ')[1], 10);
                    if (!isNaN(mobileNumber)) {
                        element.textContent = `[${100 + mobileNumber}] ${text}`;
                    }
                } else {
                    const cityIndex = cities.indexOf(text);
                    if (cityIndex !== -1) {
                        element.textContent = `[${cityIndex + 1}] ${text}`;
                    }
                }
            });
        };

        const observer = new MutationObserver((mutationsList, observer) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    updateElements();
                }
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });

        updateElements();
    }

    function updateIdsInTables() {
        const tableWrappers = document.querySelectorAll('.v-data-table__wrapper');

        tableWrappers.forEach(tableWrapper => {
            const headers = tableWrapper.querySelectorAll('th');
            let hasTenantsColumn = false;
            let hasTypeColumn = false;

            headers.forEach(header => {
                if (header.querySelector('span[data-v-409bd455]').textContent.trim() === 'ЖИЛЬЦЫ') {
                    hasTenantsColumn = true;
                }
                if (header.querySelector('span[data-v-409bd455]').textContent.trim() === 'ТИП') {
                    hasTypeColumn = true;
                }
            });

            if (hasTenantsColumn || hasTypeColumn) {
                const rows = tableWrapper.querySelectorAll('table tr');

                rows.forEach(row => {
                    const cells = row.querySelectorAll('td');
                    if (cells.length > 0) {
                        const idCell = cells[0];
                        const id = parseInt(idCell.textContent.trim(), 10);
                        if (!isNaN(id) && id < 2000) {
                            idCell.textContent = (id - 1).toString();
                        } else {

                        }
                    }
                });
            }
        });
    }

    function searchAndUpdateIds() {
        const intervalId = setInterval(() => {
            const playerCell = document.querySelector('td[data-v-81416ece][data-v-409bd455].text-right');
            if (playerCell) {
                clearInterval(intervalId);
                updateIdsInTables();
                startMutationObserverForPlayerCell();
            }
        }, 0);
    }

    function startMutationObserverForPlayerCell() {
        const targetNode = document.body;
        const config = { childList: true, subtree: true };

        const callback = function(mutationsList, observer) {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    const playerCell = document.querySelector('td[data-v-81416ece][data-v-409bd455].text-right');
                    if (!playerCell) {
                        observer.disconnect();
                        searchAndUpdateIds();
                        break;
                    }
                }
            }
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
    }

    function insertFixText() {
        const spacerElement = document.querySelector('div[data-v-409bd455].spacer');
        if (spacerElement) {
            spacerElement.textContent = 'addition by [02] Mikki Tyler';
            spacerElement.style.display = 'flex';
            spacerElement.style.justifyContent = 'center';
            spacerElement.style.alignItems = 'center';
            spacerElement.style.height = '100%';
            spacerElement.style.textAlign = 'center';
        }
    }

    function startMutationObserverForSpacer() {
        const targetNode = document.body;
        const config = { childList: true, subtree: true };

        const callback = function(mutationsList, observer) {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    insertFixText();
                }
            }
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
    }

    function updateBlockedCount() {
        const blockedCountElement = document.querySelector('div[data-v-409bd455].v-card__text.d-flex.align-center.flex-wrap.pb-0');
        if (blockedCountElement) {
            const countElement = blockedCountElement.querySelector('div[data-blocked-count]');
            if (countElement) {
                countElement.innerHTML = `
                    <div style="display: flex; justify-content: center; align-items: center;">
                        <span style="color: red; font-weight: bold;">Заблокировано: ${blockedCount}</span>
                        <span style="color: green; font-weight: bold; margin-left: 10px;">Не заблокировано: ${unblockedCount}</span>
                    </div>
                `;
            } else {
                blockedCountElement.insertAdjacentHTML('beforeend', `
                    <div data-blocked-count style="margin-left: 20px; display: flex; justify-content: center; align-items: center;">
                        <span style="color: red; font-weight: bold;">Заблокировано: ${blockedCount}</span>
                        <span style="color: green; font-weight: bold; margin-left: 10px;">Не заблокировано: ${unblockedCount}</span>
                    </div>
                `);
            }
        }
    }

    function resetBlockedCount() {
        blockedCount = 0;
        unblockedCount = 0;
        updateBlockedCount();
    }

    window.addEventListener('load', () => {
        updateDataOnPage();
        checkPageLoad();
        searchAndUpdateIds();
        startMutationObserverForSpacer();
        observeTableRows();
        updateBlockedCount();

        const searchButton = document.querySelector('button[data-v-409bd455][data-v-3996b970]');
        if (searchButton) {
            searchButton.addEventListener('click', resetBlockedCount);
        }
    });
})();