Bobber / Spy filter

// ==UserScript==
// @name         Spy filter
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Spy Filter with enhanced functionalities including defense value, API-based fleet multiplier with "Keep Player" and "Delete Duplicates" functionality
// @author       Bobber
// @license      MIT
// @match        https://*.ogame.gameforge.com/game/index.php?page=ingame&component=messages*
// @updateURL https://openuserjs.org/meta/Bobber/Spy_filter.meta.js
// @downloadURL https://openuserjs.org/install/Bobber/Spy_filter.user.js
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';
    const versionCheckKey = 'spyFilterVersionCheckDone';
    const scriptMetaURL = GM_info.script.updateURL;
    const scriptDownloadURL = GM_info.script.downloadURL;

    class SpyFilter {
        constructor() {
            this.fleetThreshold = null;
            this.resourceThreshold = null;
            this.defThreshold = null;
            this.lootFleetThreshold = null;
            this.debrisFactor = null;
            this.keepPlayer = false;
            this.deleteDuplicates = false;
            this.reportsToDelete = [];
            this.ajaxToken = null;
            this.debugger = new Debugger(this);
            this.updateAvailable = false;
            this.versionCheckDone = false;
            this.lastUpdateCheck = parseInt(localStorage.getItem('lastUpdateCheck'), 10) || 0;
            this.init();
        }

        async init() {
            this.loadThresholdValues();
            this.addSpyFilterTab();
            this.addDeleteButton();
            await this.fetchDebrisFactor();
            const messages = await this.fetchReports();
            const reportsData = this.extractMessageData(messages);
            this.processReportsData(reportsData);
            this.updateDeleteCounter(0, this.reportsToDelete.length);
            this.observeMessages();
            this.checkStoredVersion();
            this.checkForUpdate();
        }

        checkStoredVersion() {
            const currentVersion = GM_info.script.version;
            const storedVersion = localStorage.getItem('lastAvailableVersion');


            if (storedVersion && this.compareVersions(currentVersion, storedVersion) < 0) {
                this.updateAvailable = true;
                this.updateTabForUpdate(scriptDownloadURL);
                console.log(`Eine neue Version ist im Speicher vorhanden: ${storedVersion}`);
            }
        }

        extractRemoteVersion(metaText) {
            const versionRegex = /@version\s+([^\s]+)/;
            const match = metaText.match(versionRegex);
            return match ? match[1] : null;
        }

        compareVersions(currentVersion, remoteVersion) {
            const currentParts = currentVersion.split('.').map(Number);
            const remoteParts = remoteVersion.split('.').map(Number);
            for (let i = 0; i < Math.max(currentParts.length, remoteParts.length); i++) {
                const currentPart = currentParts[i] || 0;
                const remotePart = remoteParts[i] || 0;
                if (currentPart < remotePart) return -1;
                if (currentPart > remotePart) return 1;
            }
            return 0;
        }

        checkForUpdate() {
            const UPDATE_CHECK_INTERVAL = 6 * 60 * 60 * 1000;
            const currentTime = Date.now();
            const lastUpdateCheck = parseInt(GM_getValue('lastUpdateCheck', '0'), 10);


            const storedVersion = localStorage.getItem('lastAvailableVersion');
            if (storedVersion && this.compareVersions(GM_info.script.version, storedVersion) < 0) {
                console.log("Update vorhanden, Serverabfrage übersprungen.");
                return;
            }

            if (currentTime - lastUpdateCheck < UPDATE_CHECK_INTERVAL) {
                console.log("Versionsprüfung übersprungen; die letzte Prüfung war vor weniger als 6 Stunden.");
                return;
            }

            GM_setValue('lastUpdateCheck', currentTime.toString());
            const scriptMetaURL = GM_info.script.updateURL || this.getMetadataValue('updateURL');
            const scriptDownloadURL = GM_info.script.downloadURL || this.getMetadataValue('downloadURL');
            if (!scriptMetaURL || !scriptDownloadURL) {
                console.error('Konnte die Update-URLs nicht aus den Metadaten abrufen.');
                return;
            }

            console.log("Überprüfe auf Skript-Updates...");
            GM_xmlhttpRequest({
                method: 'GET',
                url: scriptMetaURL,
                nocache: true,
                anonymous: true,
                onload: (response) => {
                    if (response.status === 200) {
                        const metaText = response.responseText;
                        const remoteVersion = this.extractRemoteVersion(metaText);
                        if (remoteVersion) {
                            const currentVersion = GM_info.script.version;
                            if (this.compareVersions(currentVersion, remoteVersion) < 0) {
                                this.updateAvailable = true;
                                this.updateTabForUpdate(scriptDownloadURL);
                                localStorage.setItem('lastAvailableVersion', remoteVersion);
                                console.log(`Neue Version verfügbar: ${remoteVersion}`);
                            } else {
                                this.updateAvailable = false;
                                localStorage.setItem('lastAvailableVersion', currentVersion);
                                console.log("Spy Filter ist auf dem neuesten Stand.");
                                this.clearUpdateStatus();
                            }
                        }
                    } else {
                        console.error(`Fehler beim Überprüfen auf Updates: ${response.status} (${response.statusText})`);
                    }
                },
                onerror: (error) => {
                    console.error('Fehler bei der Versionsprüfung:', error);
                }
            });
        }

        updateTabForUpdate(downloadURL) {
            if (this.spyFilterTab) {
                this.spyFilterTab.innerHTML = 'Update<span class="ago_panel_tab_info"></span>';
                this.spyFilterTab.style.cursor = 'pointer';
                this.spyFilterTab.addEventListener('click', () => {

                    localStorage.setItem('intendedVersion', localStorage.getItem('lastAvailableVersion'));
                    window.open(downloadURL, '_blank');
                });
            }
        }

        clearUpdateStatus() {
            if (this.spyFilterTab) {
                this.spyFilterTab.innerHTML = 'Spy filter<span class="ago_panel_tab_info"></span>';
                this.spyFilterTab.style.cursor = 'pointer';
            }
        }

        getMetadataValue(key) {
            const metaStr = GM_info.scriptMetaStr;
            const metaRegex = new RegExp(`@${key}\\s+(.+)`);
            const match = metaStr.match(metaRegex);
            return match ? match[1].trim() : null;
        }
        formatNumber(value) {
            return Math.floor(value).toString().replace(
                /\B(?=(\d{3})+(?!\d))/g, '.');
        }
        async fetchDebrisFactor() {
            const storedDebrisFactor = localStorage.getItem(
                'debrisFactor');
            if (storedDebrisFactor !== null) {
                this.debrisFactor = parseFloat(storedDebrisFactor);
                this.debugger.log('Debris Factor loaded from storage:',
                    this.debrisFactor);
                this.updateLootFleetLabel(this.debrisFactor);
            } else {
                const currentDomain = window.location.hostname;
                const apiUrl =
                    `https://${currentDomain}/api/serverData.xml`;
                try {
                    const response = await fetch(apiUrl);
                    const data = await response.text();
                    const parser = new DOMParser();
                    const xmlDoc = parser.parseFromString(data,
                        "application/xml");
                    const debrisFactorElement = xmlDoc.querySelector(
                        "debrisFactor");
                    if (debrisFactorElement) {
                        this.debrisFactor = parseFloat(
                            debrisFactorElement.textContent);
                        localStorage.setItem('debrisFactor', this
                            .debrisFactor.toString());
                        this.debugger.log(
                            'Debris Factor fetched and stored:',
                            this.debrisFactor);
                        this.updateLootFleetLabel(this.debrisFactor);
                    }
                } catch (error) {
                    this.debugger.error('Error fetching debris factor:',
                        error);
                }
            }
        }
        updateLootFleetLabel(debrisFactor) {
            const debrisFactorPercent = (debrisFactor * 100).toFixed(0);
            const checkLabelInterval = setInterval(() => {
                const lootFleetLabel = document.getElementById(
                    'lootFleetLabel');
                if (lootFleetLabel) {
                    lootFleetLabel.innerHTML =
                        `Loot+Fleet: ${debrisFactorPercent}% DF`;
                    clearInterval(checkLabelInterval);
                }
            }, 100);
        }
        async fetchReports() {
            const currentUrl = new URL(window.location.href);
            const baseUrl =
            `${currentUrl.origin}${currentUrl.pathname}`;
            const userLanguage = navigator.language || navigator
                .userLanguage;
            const url =
                `${baseUrl}?page=componentOnly&component=messages&asJson=1&action=getMessagesList`;
            const params = new URLSearchParams(currentUrl.search);
            params.set('activeSubTab', '20');
            params.set('showTrash', 'false');
            const options = {
                headers: {
                    accept: "application/json, text/javascript, */*; q=0.01",
                    "accept-language": `${userLanguage},${userLanguage.split('-')[0]};q=0.9,en-US;q=0.8,en;q=0.7`,
                    "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
                    "x-requested-with": "XMLHttpRequest"
                },
                referrer: currentUrl.href,
                body: params.toString(),
                method: "POST",
                mode: "cors",
                credentials: "include"
            };
            try {
                const response = await fetch(url, options);
                const data = await response.json();
                this.ajaxToken = data.newAjaxToken;
                this.debugger.log('Fetched reports and ajaxToken:', {
                    data,
                    ajaxToken: this.ajaxToken
                });
                return data.messages;
            } catch (error) {
                this.debugger.error('Error fetching reports:', error);
                return [];
            }
        }
        extractMessageData(messages) {
            const parser = new DOMParser();
            const extractedData = [];
            messages.forEach((messageHtml) => {
                const doc = parser.parseFromString(messageHtml,
                    'text/html');
                const messageElement = doc.querySelector(
                '.msg');
                if (messageElement) {
                    const lootPercentage = parseFloat(
                            messageElement.getAttribute(
                                'data-messages-filters-loot')
                            ) || 100;
                    const lootFactor = lootPercentage / 100;
                    const fleetValue = parseInt(messageElement
                            .querySelector('.rawMessageData')
                            .getAttribute(
                            'data-raw-fleetValue') || '0', 10
                            ) || 0;
                    const defValue = parseInt(messageElement
                        .querySelector('.rawMessageData')
                        .getAttribute(
                            'data-raw-defenseValue') || '0',
                        10) || 0;
                    const resourceValue = parseInt(
                        messageElement.querySelector(
                            '.rawMessageData').getAttribute(
                            'data-raw-resources') || '0', 10
                        ) || 0;
                    const timestamp = parseInt(messageElement
                            .querySelector('.rawMessageData')
                            .getAttribute(
                            'data-raw-dateTime') || '0', 10) ||
                        0;
                    const messageContent = messageElement
                        .querySelector('.msgContent')
                        ?.textContent || "";
                    if (messageContent.includes(
                            "Eine fremde Flotte vom Planeten"
                            ) && messageContent.includes(
                            "Chance auf Spionageabwehr")) {
                        this.debugger.log(
                            "Ignoring report as it is directed against you", {
                                messageId: messageElement
                                    .getAttribute(
                                        'data-msg-id')
                            });
                        return;
                    }
                    const calculatedResourceValue =
                        resourceValue * lootFactor;
                    const combinedLootFleetValue = fleetValue *
                        this.debrisFactor +
                        calculatedResourceValue;
                    extractedData.push({
                        messageId: messageElement
                            .getAttribute(
                            'data-msg-id'),
                        playerName: messageElement
                            .getAttribute(
                                'data-messages-filters-playerName'
                                ),
                        coordinates: messageElement
                            .getAttribute(
                                'data-messages-filters-coordinates'
                                ),
                        fleetValue: fleetValue,
                        defValue: defValue,
                        calculatedResourceValue: calculatedResourceValue,
                        combinedLootFleetValue: combinedLootFleetValue,
                        targetType: messageElement
                            .querySelector(
                                '.rawMessageData')
                            .getAttribute(
                                'data-raw-targetPlanetType'
                                ) === '3' ?
                            'Moon' : 'Planet',
                        hashCode: messageElement
                            .querySelector(
                                '.rawMessageData')
                            .getAttribute(
                                'data-raw-hashcode'),
                        timestamp: timestamp,
                        lootPercentage: lootFactor
                    });
                }
            });
            return extractedData;
        }
        applyThresholdColor(report, isDuplicate) {
            const {
                messageId,
                fleetValue,
                calculatedResourceValue,
                defValue
            } = report;
            const reportId = `#m_${messageId}`;
            const lootCell = document.querySelector(
                `${reportId} > td:nth-child(4)`);
            const fleetCell = document.querySelector(
                `${reportId} > td:nth-child(5)`);
            const defCell = document.querySelector(
                `${reportId} > td:nth-child(6)`);
            if (lootCell) lootCell.style.color = '';
            if (fleetCell) fleetCell.style.color = '';
            if (defCell) defCell.style.color = '';
            if (this.fleetThreshold !== null && fleetValue >= this
                .fleetThreshold && fleetCell) {
                fleetCell.style.setProperty('color', 'green',
                    'important');
            }
            if (this.resourceThreshold !== null &&
                calculatedResourceValue >= this.resourceThreshold &&
                lootCell) {
                lootCell.style.setProperty('color', 'green',
                    'important');
            }
            const combinedLootFleetValue = fleetValue * this
                .debrisFactor + calculatedResourceValue;
            if (this.lootFleetThreshold !== null &&
                combinedLootFleetValue >= this.lootFleetThreshold) {
                if (lootCell) lootCell.style.setProperty('color',
                    'green', 'important');
                if (fleetCell) fleetCell.style.setProperty('color',
                    'green', 'important');
            }
            if (this.defThreshold !== null && defValue > this
                .defThreshold && defCell) {
                defCell.style.setProperty('color', 'red', 'important');
            }
            if (isDuplicate) {
                if (lootCell) lootCell.style.setProperty('color', 'red',
                    'important');
                if (fleetCell) fleetCell.style.setProperty('color',
                    'red', 'important');
                if (defCell) defCell.style.setProperty('color', 'red',
                    'important');
            }
        }
        processReportsData(reportsData) {
            this.reportsToDelete = [];
            const seenReports = new Map();
            const playersToKeep = new Set();
            reportsData.forEach((report) => {
                if (this.keepPlayer) {
                    if (
                        (this.fleetThreshold !== null && report
                            .fleetValue >= this.fleetThreshold
                            ) || (this.lootFleetThreshold !==
                            null && report
                            .combinedLootFleetValue >= this
                            .lootFleetThreshold) || (this
                            .resourceThreshold !== null &&
                            report.calculatedResourceValue >=
                            this.resourceThreshold) || (this
                            .defThreshold !== null && report
                            .defValue <= this.defThreshold)) {
                        playersToKeep.add(report.playerName);
                    }
                }
            });
            reportsData.forEach((report) => {
                let deleteReport = false;
                let isDuplicate = false;
                if (this.deleteDuplicates) {
                    const reportKey =
                        `${report.playerName}_${report.coordinates}_${report.targetType}`;
                    const existingReport = seenReports.get(
                        reportKey);
                    if (existingReport) {
                        isDuplicate = true;
                        if (report.timestamp > existingReport
                            .timestamp) {
                            this.reportsToDelete.push({
                                messageId: existingReport
                                    .messageId,
                                hashCode: existingReport
                                    .hashCode
                            });
                            seenReports.set(reportKey, report);
                        } else {
                            this.reportsToDelete.push({
                                messageId: report
                                    .messageId,
                                hashCode: report
                                    .hashCode
                            });
                        }
                    } else {
                        seenReports.set(reportKey, report);
                    }
                }
                if (!playersToKeep.has(report.playerName) && !
                    isDuplicate) {
                    if (
                        (this.fleetThreshold !== null && report
                            .fleetValue < this.fleetThreshold
                            ) || (this.lootFleetThreshold !==
                            null && report
                            .combinedLootFleetValue < this
                            .lootFleetThreshold) || (this
                            .resourceThreshold !== null &&
                            report.calculatedResourceValue <
                            this.resourceThreshold) || (this
                            .defThreshold !== null && report
                            .defValue > this.defThreshold)) {
                        deleteReport = true;
                    }
                }
                report.deleteReport = deleteReport;
                report.isDuplicate = isDuplicate;
                this.debugger.detailedLog(report);
                if (deleteReport || (isDuplicate && this
                        .deleteDuplicates)) {
                    const existingToDelete = this
                        .reportsToDelete.find(r => r
                            .messageId === report.messageId);
                    if (!existingToDelete) {
                        this.reportsToDelete.push({
                            messageId: report.messageId,
                            hashCode: report.hashCode
                        });
                    }
                }
                this.applyThresholdColor(report, isDuplicate);
            });
            this.debugger.log('Reports marked for deletion', this
                .reportsToDelete);
        }
        async deleteMessages() {
            const totalToDelete = this.reportsToDelete.length;
            this.updateDeleteCounter(0, totalToDelete);
            if (totalToDelete > 0) {
                try {
                    const messageIds = this.reportsToDelete.map(
                        report => report.messageId);
                    await this.deleteReports(messageIds);
                    this.updateDeleteCounter(totalToDelete,
                        totalToDelete);
                    this.debugger.log(
                        `Successfully deleted ${totalToDelete} reports.`
                        );
                } catch (error) {
                    this.debugger.error('Error deleting reports:',
                        error);
                }
            } else {
                this.debugger.log('No reports to delete.');
            }
            this.reportsToDelete = [];
            this.reloadActiveTabAndRestoreColors();
        }
        async deleteReports(messageIds) {
            const baseUrl =
                `${window.location.origin}${window.location.pathname}`;
            const url =
                `${baseUrl}?page=componentOnly&component=messages&asJson=1&action=flagDeleted`;
            const body = new URLSearchParams({
                token: this.ajaxToken
            });

            messageIds.forEach(id => body.append('messageIds[]', id));
            const options = {
                headers: {
                    accept: "application/json, text/javascript, */*; q=0.01",
                    "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
                    "x-requested-with": "XMLHttpRequest",
                    cookie: document.cookie,
                },
                referrer: `${baseUrl}&component=messages`,
                referrerPolicy: "strict-origin-when-cross-origin",
                body: body.toString(),
                method: "POST",
                mode: "cors",
                credentials: "include"
            };
            this.debugger.log("Sending bulk delete request with body:",
                body.toString());
            try {
                const response = await fetch(url, options);
                const result = await response.json();
                this.debugger.log("Server response:", result);
                if (result.status === "success") {
                    this.ajaxToken = result.newAjaxToken || this
                        .ajaxToken;
                    this.debugger.log(
                        'Successfully deleted messages with IDs',
                        messageIds);
                } else {
                    this.debugger.error('Failed to delete messages',
                        result);
                }
            } catch (error) {
                this.debugger.error(
                    `Error deleting messages with IDs: ${messageIds.join(', ')}`,
                    error);
                throw error;
            }
        }
        reloadActiveTabAndRestoreColors() {
            const activeTab = document.querySelector(
                '#messagewrapper > div.tabsWrapper > div.innerTabItem.active'
                );
            if (activeTab) {
                activeTab.click();
                setTimeout(() => {
                    const messages = document.querySelectorAll(
                        '.msg');
                    messages.forEach(msg => {
                        const fleetValue = parseInt(msg
                            .querySelector(
                                '.rawMessageData')
                            .getAttribute(
                                'data-raw-fleetValue'
                                ) || '0', 10);
                        const resourceValue = parseInt(
                            msg.querySelector(
                                '.rawMessageData')
                            .getAttribute(
                                'data-raw-resources'
                                ) || '0', 10) || 0;
                        const lootPercentage =
                            parseFloat(msg.getAttribute(
                                'data-messages-filters-loot'
                                )) || 100;
                        const lootFactor =
                            lootPercentage / 100;
                        const calculatedResourceValue =
                            resourceValue * lootFactor;
                        const defValue = parseInt(msg
                            .querySelector(
                                '.rawMessageData')
                            .getAttribute(
                                'data-raw-defenseValue'
                                ) || '0', 10) || 0;
                        const messageId = msg
                            .getAttribute(
                            'data-msg-id');
                        this.applyThresholdColor({
                            messageId,
                            fleetValue,
                            calculatedResourceValue,
                            defValue
                        }, false);
                    });
                }, 500);
            }
        }
        updateDeleteCounter(deletedCount = 0, totalToDelete = 0) {
            const statusDisplay = document.getElementById(
                'deleteStatus');
            if (statusDisplay) {
                statusDisplay.textContent =
                    `Deleting: ${deletedCount} / ${totalToDelete}`;
                this.debugger.log('Delete Counter Updated', {
                    deletedCount,
                    totalToDelete
                });
            }
        }
        addSpyFilterTab() {
            let panel = document.querySelector('#ago_panel');
            let newTab = document.createElement('div');
            newTab.id = 'ago_panel_SpyFilter';
            newTab.className = 'ago_panel_tab';
            newTab.setAttribute('ago-data',
                '{"update":{"tab":"SpyFilter","status":"toggle"}}');
            newTab.innerHTML =
                'Spy filter<span class="ago_panel_tab_info"></span>';
            let toolsTab = document.querySelector('#ago_panel_Tools');
            toolsTab.parentNode.insertBefore(newTab, toolsTab
                .nextSibling);
            let filterMenu = document.createElement('div');
            filterMenu.id = 'ago_panel_SpyFilter_Content';
            filterMenu.className = 'ago_panel_tab_content';
            filterMenu.style.display = 'none';
            filterMenu.innerHTML = `
                <div id="filter_panel_content" style="background: #111017; padding: 10px; display: flex; flex-direction: column; align-items: center;">
                    <div style="margin-bottom: 10px; width: 100%; display: flex; flex-direction: column;">
                        <label for="lootResult" style="margin-bottom: 3px; text-align: left; padding-left: 1px; font-size: 12px;">Loot:</label>
                        <input type="text" id="lootResult" style="border-radius: 3px; font-size: 12px; height: 30px; line-height: 30px; -webkit-appearance: textfield; -webkit-box-sizing: border-box; box-shadow: inset 0 1px 3px 0 #27292B; width: 100%; padding: 5px; color: #0d1014; background-color: #ffffff; border: 1px solid #4d4d4d; text-align: center;">
                    </div>
                    <div style="margin-bottom: 10px; width: 100%; display: flex; flex-direction: column;">
                        <label for="fleetResult" style="margin-bottom: 3px; text-align: left; padding-left: 1px; font-size: 12px;">Fleet:</label>
                        <input type="text" id="fleetResult" style="border-radius: 3px; font-size: 12px; height: 30px; line-height: 30px; -webkit-appearance: textfield; -webkit-box-sizing: border-box; box-shadow: inset 0 1px 3px 0 #27292B; width: 100%; padding: 5px; color: #0d1014; background-color: #ffffff; border: 1px solid #4d4d4d; text-align: center;">
                    </div>
                    <div style="margin-bottom: 10px; width: 100%; display: flex; flex-direction: column;">
                        <label for="lootFleetResult" id="lootFleetLabel" style="margin-bottom: 3px; text-align: left; padding-left: 1px; font-size: 12px;">Loot+Fleet:</label>
                        <input type="text" id="lootFleetResult" style="border-radius: 3px; font-size: 12px; height: 30px; line-height: 30px; -webkit-appearance: textfield; -webkit-box-sizing: border-box; box-shadow: inset 0 1px 3px 0 #27292B; width: 100%; padding: 5px; color: #0d1014; background-color: #ffffff; border: 1px solid #4d4d4d; text-align: center;">
                    </div>
                    <div style="margin-bottom: 10px; width: 100%; display: flex; flex-direction: column;">
                        <label for="defResult" style="margin-bottom: 3px; text-align: left; padding-left: 1px; font-size: 12px;">Def:</label>
                        <input type="text" id="defResult" style="border-radius: 3px; font-size: 12px; height: 30px; line-height: 30px; -webkit-appearance: textfield; -webkit-box-sizing: border-box; box-shadow: inset 0 1px 3px 0 #27292B; width: 100%; padding: 5px; color: #0d1014; background-color: #ffffff; border: 1px solid #4d4d4d; text-align: center;">
                    </div>
                    <div style="margin-top: 10px; width: 100%;">
                        <button id="saveFilter" style="border: none; cursor: pointer; width: 266px; background: #3b4858; color: #c8c8c8; height: 30px; background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0,rgba(0, 0, 0, 0.1)),color-stop(1,rgba(255, 255, 255, 0.1)));">Save</button>
                    </div>
                    <div style="margin-top: 10px; width: 100%; display: flex; align-items: center;">
                        <input type="checkbox" id="keepPlayerCheckbox" style="width: 20px; height: 20px; cursor: pointer;">
                        <label for="keepPlayerCheckbox" style="margin-left: 10px; font-size: 12px; color: #c8c8c8;">Keep player</label>
                    </div>
                    <div style="margin-top: 10px; width: 100%; display: flex; align-items: center;">
                        <input type="checkbox" id="deleteDuplicatesCheckbox" style="width: 20px; height: 20px; cursor: pointer;">
                        <label for="deleteDuplicatesCheckbox" style="margin-left: 10px; font-size: 12px; color: #c8c8c8;">Delete duplicates</label>
                    </div>
                </div>
            `;
            newTab.insertAdjacentElement('afterend', filterMenu);

            this.spyFilterTab = newTab;
            this.filterMenu = filterMenu;
            newTab.addEventListener('click', () => {
                if (this.updateAvailable) {

                    window.open(scriptDownloadURL, '_blank');
                } else {

                    document.querySelectorAll(
                        '.ago_panel_tab_content').forEach(
                        content => {
                            if (content !== filterMenu) {
                                content.style.display =
                                    'none';
                            }
                        });
                    filterMenu.style.display = filterMenu.style
                        .display === 'none' ? 'block' : 'none';
                }
            });
            document.getElementById('lootResult').addEventListener(
                'input', (event) => {
                    this.formatInputValue(event.target);
                });
            document.getElementById('fleetResult').addEventListener(
                'input', (event) => {
                    this.formatInputValue(event.target);
                });
            document.getElementById('lootFleetResult').addEventListener(
                'input', (event) => {
                    this.formatInputValue(event.target);
                });
            document.getElementById('defResult').addEventListener(
                'input', (event) => {
                    this.formatInputValue(event.target);
                });
            document.getElementById('keepPlayerCheckbox')
                .addEventListener('change', (event) => {
                    this.keepPlayer = event.target.checked;
                });
            document.getElementById('deleteDuplicatesCheckbox')
                .addEventListener('change', (event) => {
                    this.deleteDuplicates = event.target.checked;
                });
            document.getElementById('saveFilter').addEventListener(
                'click', async () => {
                    const lootInputValue = document
                        .getElementById('lootResult').value
                        .replace(/\./g, '');
                    const fleetInputValue = document
                        .getElementById('fleetResult').value
                        .replace(/\./g, '');
                    const defInputValue = document
                        .getElementById('defResult').value
                        .replace(/\./g, '');
                    const lootFleetInputValue = document
                        .getElementById('lootFleetResult').value
                        .replace(/\./g, '');
                    this.resourceThreshold = lootInputValue ?
                        parseInt(lootInputValue, 10) : null;
                    this.fleetThreshold = fleetInputValue ?
                        parseInt(fleetInputValue, 10) : null;
                    this.defThreshold = defInputValue ?
                        parseInt(defInputValue, 10) : null;
                    this.lootFleetThreshold =
                        lootFleetInputValue ? parseInt(
                            lootFleetInputValue, 10) : null;
                    this.saveThresholdValues();
                    const messages = await this.fetchReports();
                    const reportsData = this.extractMessageData(
                        messages);
                    this.processReportsData(reportsData);
                    this.updateDeleteCounter(0, this
                        .reportsToDelete.length);
                });
            document.getElementById('lootResult').value = this
                .resourceThreshold !== null ? this.formatNumber(this
                    .resourceThreshold) : '';
            document.getElementById('fleetResult').value = this
                .fleetThreshold !== null ? this.formatNumber(this
                    .fleetThreshold) : '';
            document.getElementById('lootFleetResult').value = this
                .lootFleetThreshold !== null ? this.formatNumber(this
                    .lootFleetThreshold) : '';
            document.getElementById('defResult').value = this
                .defThreshold !== null ? this.formatNumber(this
                    .defThreshold) : '';
            document.getElementById('keepPlayerCheckbox').checked = this
                .keepPlayer;
            document.getElementById('deleteDuplicatesCheckbox')
                .checked = this.deleteDuplicates;
        }
        updateTabForUpdate() {
            if (this.spyFilterTab) {
                this.spyFilterTab.innerHTML =
                    'Update<span class="ago_panel_tab_info"></span>';
                this.spyFilterTab.style.cursor = 'pointer';
            }
        }
        formatInputValue(input) {
            const value = input.value.replace(/\D/g, '');
            input.value = value.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
        }
        loadThresholdValues() {
            const storedFleetThreshold = localStorage.getItem(
                'fleetThreshold');
            const storedResourceThreshold = localStorage.getItem(
                'resourceThreshold');
            const storedDefThreshold = localStorage.getItem(
                'defThreshold');
            const storedLootFleetThreshold = localStorage.getItem(
                'lootFleetThreshold');
            const storedKeepPlayer = localStorage.getItem('keepPlayer');
            const storedDeleteDuplicates = localStorage.getItem(
                'deleteDuplicates');
            this.fleetThreshold = storedFleetThreshold !== null ?
                parseInt(storedFleetThreshold.replace(/\./g, ''), 10) :
                null;
            this.resourceThreshold = storedResourceThreshold !== null ?
                parseInt(storedResourceThreshold.replace(/\./g, ''),
                10) : null;
            this.defThreshold = storedDefThreshold !== null ? parseInt(
                storedDefThreshold.replace(/\./g, ''), 10) : null;
            this.lootFleetThreshold = storedLootFleetThreshold !==
                null ? parseInt(storedLootFleetThreshold.replace(/\./g,
                    ''), 10) : null;
            this.keepPlayer = storedKeepPlayer === 'true';
            this.deleteDuplicates = storedDeleteDuplicates === 'true';
        }
        saveThresholdValues() {
            this.fleetThreshold !== null ? localStorage.setItem(
                    'fleetThreshold', this.fleetThreshold.toString()) :
                localStorage.removeItem('fleetThreshold');
            this.resourceThreshold !== null ? localStorage.setItem(
                'resourceThreshold', this.resourceThreshold
                .toString()) : localStorage.removeItem(
                'resourceThreshold');
            this.defThreshold !== null ? localStorage.setItem(
                    'defThreshold', this.defThreshold.toString()) :
                localStorage.removeItem('defThreshold');
            this.lootFleetThreshold !== null ? localStorage.setItem(
                'lootFleetThreshold', this.lootFleetThreshold
                .toString()) : localStorage.removeItem(
                'lootFleetThreshold');
            localStorage.setItem('keepPlayer', this.keepPlayer
            .toString());
            localStorage.setItem('deleteDuplicates', this
                .deleteDuplicates.toString());
        }
addDeleteButton() {
    const targetNode = document.querySelector("#messagewrapper > div.tabsWrapperSubMenu");

    if (!targetNode) {
        console.error('Target element not found for delete button insertion');
        return;
    }

    // Funktion zum Hinzufügen des Buttons
    const insertDeleteButton = () => {
        // Überprüft, ob der Button bereits existiert
        const existingDeleteButtonContainer = document.querySelector('.deleteAllContainer');
        if (existingDeleteButtonContainer) return; // Button bereits vorhanden, abbrechen

        // Erstellt den neuen Container für den Delete-Button
        const deleteAllContainer = document.createElement('div');
        deleteAllContainer.className = 'deleteAllContainer';
        deleteAllContainer.style.display = 'flex';
        deleteAllContainer.style.alignItems = 'center';

        // Statusanzeige hinzufügen
        const statusDisplay = document.createElement('div');
        statusDisplay.id = 'deleteStatus';
        statusDisplay.style.marginRight = '10px';
        statusDisplay.style.color = '#ffffff';
        deleteAllContainer.appendChild(statusDisplay);

        // Container für den Delete-Button erstellen
        const deleteButtonContainer = document.createElement('gradient-button');
        deleteButtonContainer.id = 'deleteButtonContainer';
        deleteButtonContainer.style.display = 'flex';

        // Den Delete-Button erstellen mit der gewünschten Struktur
        const deleteButton = document.createElement('button');
        deleteButton.className = 'custom_btn';
        deleteButton.innerHTML = '<span>Delete</span>';
        deleteButton.style.width = '80px';
        deleteButton.style.height = '20px';

        // Event für den Button festlegen
        deleteButton.addEventListener('click', this.deleteMessages.bind(this));

        // Button in den Container einfügen
        deleteButtonContainer.appendChild(deleteButton);
        deleteAllContainer.appendChild(deleteButtonContainer);

        // Einfügen des Containers vor den zusätzlichen Schaltflächen im Untermenü
        const messagesExtraBtns = targetNode.querySelector('.messagesExtraBtns');
        if (messagesExtraBtns) {
            targetNode.insertBefore(deleteAllContainer, messagesExtraBtns);
        } else {
            targetNode.appendChild(deleteAllContainer);
        }

        // CSS für den Hover-Effekt hinzufügen
        const style = document.createElement('style');
        style.innerHTML = `
            .custom_btn:hover {
                background: #0d0d0d;
                box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
                color: #ffffff;
            }
        `;
        document.head.appendChild(style);
    };

    // Erstellt den MutationObserver, um das Hinzufügen des Buttons zu überwachen
    const observer = new MutationObserver((mutationsList, observer) => {
        // Prüft auf Änderungen, die das Ziel-Element betreffen
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                // Versucht, den Button einzufügen, wenn Knoten hinzugefügt wurden
                insertDeleteButton();
            }
        }
    });

    // Startet den Observer auf dem Ziel-Element
    observer.observe(targetNode, {
        childList: true,
        subtree: false // Direkt auf die Kinder des targetNode beschränkt
    });

    // Ruft die Funktion direkt auf, um sicherzustellen, dass der Button beim ersten Laden eingefügt wird
    insertDeleteButton();
}

        observeMessages() {
            const observer = new MutationObserver(() => {
                const messages = document.querySelectorAll(
                    '.msg');
                const seenReports = new Map();
                messages.forEach(msg => {
                    const fleetValue = parseInt(msg
                        .querySelector(
                            '.rawMessageData')
                        .getAttribute(
                            'data-raw-fleetValue'
                            ) || '0', 10);
                    const resourceValue = parseInt(msg
                        .querySelector(
                            '.rawMessageData')
                        .getAttribute(
                            'data-raw-resources') ||
                        '0', 10) || 0;
                    const lootPercentage = parseFloat(
                        msg.getAttribute(
                            'data-messages-filters-loot'
                            )) || 100;
                    const lootFactor = lootPercentage /
                        100;
                    const calculatedResourceValue =
                        resourceValue * lootFactor;
                    const defValue = parseInt(msg
                        .querySelector(
                            '.rawMessageData')
                        .getAttribute(
                            'data-raw-defenseValue'
                            ) || '0', 10) || 0;
                    const messageId = msg.getAttribute(
                        'data-msg-id');
                    const playerName = msg.getAttribute(
                        'data-messages-filters-playerName'
                        );
                    const coordinates = msg
                        .getAttribute(
                            'data-messages-filters-coordinates'
                            );
                    const targetType = msg
                        .querySelector(
                            '.rawMessageData')
                        .getAttribute(
                            'data-raw-targetPlanetType'
                            ) === '3' ? 'Moon' :
                        'Planet';
                    const reportKey =
                        `${playerName}_${coordinates}_${targetType}`;
                    let isDuplicate = false;
                    if (this.deleteDuplicates &&
                        seenReports.has(reportKey)) {
                        isDuplicate = true;
                    } else {
                        seenReports.set(reportKey, {
                            messageId,
                            fleetValue,
                            calculatedResourceValue,
                            defValue
                        });
                    }
                    this.applyThresholdColor({
                        messageId,
                        fleetValue,
                        calculatedResourceValue,
                        defValue
                    }, isDuplicate);
                });
            });
            const messagesContainer = document.querySelector(
                '#messagecontainercomponent > div > div.messageContent'
                );
            if (messagesContainer) {
                observer.observe(messagesContainer, {
                    childList: true,
                    subtree: true
                });
            }
        }
    }
    class Debugger {
        constructor(instance) {
            this.instance = instance;
        }
        log(message, data = {}) {
            console.log(`DEBUG: ${message}`, data);
        }
        detailedLog(report) {
            const {
                messageId,
                playerName,
                coordinates,
                fleetValue,
                calculatedResourceValue,
                defValue,
                combinedLootFleetValue,
                deleteReport,
                isDuplicate,
                targetType,
                lootPercentage
            } = report;
            let reason = '';
            let reasonColor = 'green';
            let messageColor = 'green';
            let idColor = 'green';
            if (deleteReport || isDuplicate) {
                idColor = 'red';
            }
            if (deleteReport) {
                messageColor = 'red';
                if (this.instance.fleetThreshold !== null &&
                    fleetValue < this.instance.fleetThreshold) {
                    reason =
                        `Fleet Value (%c${this.instance.formatNumber(fleetValue)} < ${this.instance.formatNumber(this.instance.fleetThreshold)}%c)`;
                    reasonColor = 'red';
                } else if (this.instance.lootFleetThreshold !== null &&
                    combinedLootFleetValue < this.instance
                    .lootFleetThreshold) {
                    reason =
                        `Loot+Fleet Value (%c${this.instance.formatNumber(combinedLootFleetValue)} < ${this.instance.formatNumber(this.instance.lootFleetThreshold)}%c)`;
                    reasonColor = 'red';
                } else if (this.instance.resourceThreshold !== null &&
                    calculatedResourceValue < this.instance
                    .resourceThreshold) {
                    reason =
                        `Resource Value (%c${this.instance.formatNumber(calculatedResourceValue)} < ${this.instance.formatNumber(this.instance.resourceThreshold)}%c) [Loot %: ${lootPercentage * 100}%]`;
                    reasonColor = 'red';
                } else if (this.instance.defThreshold !== null &&
                    defValue > this.instance.defThreshold) {
                    reason =
                        `Defense Value (%c${this.instance.formatNumber(defValue)} > ${this.instance.formatNumber(this.instance.defThreshold)}%c)`;
                    reasonColor = 'red';
                }
            } else {
                if (this.instance.fleetThreshold !== null &&
                    fleetValue >= this.instance.fleetThreshold) {
                    reason =
                        `Fleet Value (%c${this.instance.formatNumber(fleetValue)} >= ${this.instance.formatNumber(this.instance.fleetThreshold)}%c)`;
                } else if (this.instance.lootFleetThreshold !== null &&
                    combinedLootFleetValue >= this.instance
                    .lootFleetThreshold) {
                    reason =
                        `Loot+Fleet Value (%c${this.instance.formatNumber(combinedLootFleetValue)} >= ${this.instance.formatNumber(this.instance.lootFleetThreshold)}%c)`;
                } else if (this.instance.resourceThreshold !== null &&
                    calculatedResourceValue >= this.instance
                    .resourceThreshold) {
                    reason =
                        `Resource Value (%c${this.instance.formatNumber(calculatedResourceValue)} >= ${this.instance.formatNumber(this.instance.resourceThreshold)}%c) [Loot %: ${lootPercentage * 100}%]`;
                } else if (this.instance.defThreshold !== null &&
                    defValue <= this.instance.defThreshold) {
                    reason =
                        `Defense Value (%c${this.instance.formatNumber(defValue)} <= ${this.instance.formatNumber(this.instance.defThreshold)}%c)`;
                }
            }
            const duplicateStyle = 'color: red;';
            const idStyle = `color: ${idColor};`;
            if (isDuplicate) {
                console.log(
                    `Message ID: %c${messageId}%c, Player: ${playerName}, Coordinates: ${coordinates}, Target Type: ${targetType}, Fleet: ${this.instance.formatNumber(fleetValue)}, Resources: ${this.instance.formatNumber(calculatedResourceValue)}, Defense: ${this.instance.formatNumber(defValue)}, Combined Loot+Fleet: ${this.instance.formatNumber(combinedLootFleetValue)} %c(Duplicate)`,
                    idStyle, '', duplicateStyle);
            } else {
                console.log(
                    `Message ID: %c${messageId}%c, Player: ${playerName}, Coordinates: ${coordinates}, Target Type: ${targetType}, Fleet: ${this.instance.formatNumber(fleetValue)}, Resources: ${this.instance.formatNumber(calculatedResourceValue)}, Defense: ${this.instance.formatNumber(defValue)}, Combined Loot+Fleet: ${this.instance.formatNumber(combinedLootFleetValue)}`,
                    idStyle, '');
            }
            if (reason) {
                console.log(`Reason: ${reason}`,
                    `color: ${reasonColor};`, '', '');
            }
        }
        error(message, data = {}) {
            console.error(`ERROR: ${message}`, data);
        }
    }

    window.addEventListener('load', () => new SpyFilter());
})();