NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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()); })();