Shinoa Logs Helper

helper shinoa

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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