// ==UserScript== // @name Tribal Wars - Attack Tagger // @version 1.0 // @description Schnelles Umbenennen von Angriffen mit vordefinierten Werten // @author Big Madness // @license MIT // @match https://*.die-staemme.de/game.php?*screen=overview_villages* // @grant none // @updateURL https://raw.githubusercontent.com/SmallMadness/ds_attack_tagger/refs/heads/main/attack_tagger.user.js // @downloadURL https://raw.githubusercontent.com/SmallMadness/ds_attack_tagger/refs/heads/main/attack_tagger.user.js // ==/UserScript== /* * MIT License * * Copyright (c) 2025 Big Madness * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ (function() { 'use strict'; // Script API Registration var api = typeof unsafeWindow != 'undefined' ? unsafeWindow.ScriptAPI : window.ScriptAPI; if (api) { api.register('Attack Tagger', true, 'Big Madness', 'support-nur-im-forum@die-staemme.de'); } let TAG_BUTTONS = [ { label: '!', value: '!', tooltip: 'Rausstellen', multiple: false, shortcut: '' }, { label: '*', value: '*', tooltip: 'Eigene Deff', multiple: false, shortcut: '' }, { label: '*S', value: '*S', tooltip: 'Stammes-Deff', multiple: false, shortcut: '' }, { label: 'X', value: 'X', tooltip: 'Getroffen', multiple: false, shortcut: '' }, { label: 'F', value: 'F', tooltip: 'Fake', multiple: false, shortcut: '' }, { label: '?', value: '?', tooltip: 'Unbekannt', multiple: false, shortcut: '' } ]; let TAG_BEFORE_NAME = false; let hasChanges = false; let saveButtonElement = null; let isSaving = false; const savedButtons = localStorage.getItem('attack_tagger_buttons'); if (savedButtons) { try { TAG_BUTTONS = JSON.parse(savedButtons); } catch (e) { console.error('Fehler beim Laden der Einstellungen:', e); } } const savedTagBefore = localStorage.getItem('attack_tagger_before'); if (savedTagBefore !== null) { TAG_BEFORE_NAME = savedTagBefore === 'true'; } function waitForElement(selector, callback, maxAttempts = 50) { let attempts = 0; const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); callback(element); } else if (++attempts >= maxAttempts) { clearInterval(interval); } }, 100); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } function init() { waitForElement('a.overview_filters_manage', (filterLink) => { createTagButtonBar(filterLink); filterLink.addEventListener('click', () => { setTimeout(() => { addTagBoxToFilterDialog(); }, 300); }); setInterval(() => { const filterForm = document.querySelector('form[action*="save_filters"]'); if (filterForm && !document.getElementById('tag_filter_box')) { addTagBoxToFilterDialog(); } }, 500); }); } function updateSaveButtonState() { if (saveButtonElement) { if (hasChanges) { saveButtonElement.style.backgroundColor = '#90EE90'; saveButtonElement.style.cursor = 'pointer'; saveButtonElement.style.opacity = '1'; saveButtonElement.disabled = false; } else { saveButtonElement.style.backgroundColor = '#cccccc'; saveButtonElement.style.cursor = 'not-allowed'; saveButtonElement.style.opacity = '0.5'; saveButtonElement.disabled = true; } } } function createTagButtonBar(filterLink) { const mainContainer = document.createElement('div'); mainContainer.style.cssText = ` margin: 10px 0; padding: 3px; background-color: #f4e4bc; border: 1px solid #7d510f; border-radius: 4px; display: flex; gap: 8px; align-items: flex-start; `; const label = document.createElement('span'); label.textContent = 'Tags:'; label.style.fontWeight = 'bold'; label.style.marginTop = '4px'; mainContainer.appendChild(label); const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 3px; align-items: center; flex-wrap: wrap; flex: 1; `; mainContainer.appendChild(buttonContainer); TAG_BUTTONS.forEach(btn => { if (btn.isSeparator) { const separator = document.createElement('div'); separator.style.cssText = ` width: 100%; height: 0; `; buttonContainer.appendChild(separator); } else { const button = document.createElement('button'); button.textContent = btn.label; button.className = 'btn'; button.title = btn.tooltip; button.style.cssText = ` padding: 4px 10px; cursor: pointer; min-width: 35px; font-weight: bold; `; button.addEventListener('click', (e) => { e.preventDefault(); tagSelectedAttacks(btn.value, btn.multiple); hasChanges = true; updateSaveButtonState(); }); buttonContainer.appendChild(button); } }); const settingsButton = document.createElement('button'); settingsButton.textContent = '⚙️'; settingsButton.title = 'Einstellungen'; settingsButton.className = 'btn'; settingsButton.style.cssText = ` padding: 4px 10px; cursor: pointer; margin-left: auto; `; settingsButton.addEventListener('click', (e) => { e.preventDefault(); showSettings(); }); mainContainer.appendChild(settingsButton); const removeButton = document.createElement('button'); removeButton.textContent = '❌'; removeButton.title = 'Tags entfernen'; removeButton.className = 'btn'; removeButton.style.cssText = ` padding: 4px 10px; cursor: pointer; background-color: #ffcccc; `; removeButton.addEventListener('click', (e) => { e.preventDefault(); removeTagsFromSelected(); hasChanges = true; updateSaveButtonState(); }); mainContainer.appendChild(removeButton); const saveButton = document.createElement('button'); saveButton.textContent = '💾'; saveButton.title = 'Änderungen speichern'; saveButton.className = 'btn'; saveButton.style.cssText = ` padding: 4px 10px; cursor: not-allowed; background-color: #cccccc; font-weight: bold; opacity: 0.5; `; saveButton.disabled = true; saveButton.addEventListener('click', (e) => { e.preventDefault(); if (hasChanges) { saveNextSelected(); } }); mainContainer.appendChild(saveButton); saveButtonElement = saveButton; filterLink.parentNode.insertBefore(mainContainer, filterLink.nextSibling); setupKeyboardShortcuts(); } async function tagSelectedAttacks(tagValue, isMultiple) { let checkboxes = document.querySelectorAll('input[type="checkbox"]:checked'); checkboxes = Array.from(checkboxes).filter(cb => { const name = cb.getAttribute('name'); return name && name.startsWith('id_'); }); if (checkboxes.length === 0) { alert('Bitte wähle mindestens einen Angriff aus!'); return; } let count = 0; // Verarbeite sequenziell mit async/await for (const checkbox of checkboxes) { const row = checkbox.closest('tr'); if (!row) continue; row.removeAttribute('data-attack-saved'); const quickedit = row.querySelector('.quickedit'); if (!quickedit) continue; const commandId = quickedit.getAttribute('data-id'); if (!commandId) continue; const labelSpan = quickedit.querySelector('.quickedit-label'); if (!labelSpan) continue; const currentName = labelSpan.textContent.trim(); let newName; if (isMultiple) { if (TAG_BEFORE_NAME) { newName = `[${tagValue}] ${currentName}`; } else { newName = `${currentName} [${tagValue}]`; } } else { const nameWithoutTags = currentName.replace(/\s*\[.*?\]\s*/g, '').trim(); if (TAG_BEFORE_NAME) { newName = `[${tagValue}] ${nameWithoutTags}`; } else { newName = `${nameWithoutTags} [${tagValue}]`; } } labelSpan.textContent = newName; count++; } if (count > 0) { showNotification(`${count} Angriff(e) mit [${tagValue}] getaggt`); } else { alert('Keine Namensfelder gefunden. Bitte öffne die Konsole (F12) für Details.'); } } async function saveNextSelected() { if (isSaving) { return; } isSaving = true; if (saveButtonElement) { saveButtonElement.style.backgroundColor = '#cccccc'; saveButtonElement.style.cursor = 'not-allowed'; saveButtonElement.style.opacity = '0.5'; saveButtonElement.disabled = true; } let checkboxes = document.querySelectorAll('input[type="checkbox"]:checked'); checkboxes = Array.from(checkboxes).filter(cb => { const name = cb.getAttribute('name'); return name && name.startsWith('id_'); }); checkboxes = checkboxes.filter(cb => { const row = cb.closest('tr'); return row && !row.hasAttribute('data-attack-saved'); }); if (checkboxes.length === 0) { alert('Bitte wähle mindestens einen Angriff aus!'); isSaving = false; return; } const checkbox = checkboxes[0]; const row = checkbox.closest('tr'); if (!row) { showNotification('Fehler: Zeile nicht gefunden'); isSaving = false; return; } const renameLink = row.querySelector('a.rename-icon'); if (!renameLink) { showNotification('Fehler: Umbenennen-Link nicht gefunden'); isSaving = false; return; } renameLink.click(); let submitButton = null; let attempts = 0; const maxAttempts = 20; while (!submitButton && attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); const quickeditEdit = row.querySelector('.quickedit-edit'); if (quickeditEdit) { const style = window.getComputedStyle(quickeditEdit); if (style.display !== 'none') { submitButton = quickeditEdit.querySelector('input[type="button"][value="Umbenennen"]'); } } attempts++; } if (submitButton) { await new Promise(resolve => setTimeout(resolve, 100)); submitButton.click(); await new Promise(resolve => setTimeout(resolve, 150)); row.setAttribute('data-attack-saved', 'true'); const remaining = checkboxes.length - 1; if (remaining > 0) { showNotification(`Gespeichert! Noch ${remaining} Angriff(e) übrig`); if (saveButtonElement) { saveButtonElement.style.backgroundColor = '#90EE90'; saveButtonElement.style.cursor = 'pointer'; saveButtonElement.style.opacity = '1'; saveButtonElement.disabled = false; } } else { showNotification('Alle Angriffe gespeichert!'); hasChanges = false; updateSaveButtonState(); } } else { showNotification('Fehler: Umbenennen-Button nicht gefunden'); if (saveButtonElement && hasChanges) { saveButtonElement.style.backgroundColor = '#90EE90'; saveButtonElement.style.cursor = 'pointer'; saveButtonElement.style.opacity = '1'; saveButtonElement.disabled = false; } } isSaving = false; } async function removeTagsFromSelected() { let checkboxes = document.querySelectorAll('input[type="checkbox"]:checked'); checkboxes = Array.from(checkboxes).filter(cb => { const name = cb.getAttribute('name'); return name && name.startsWith('id_'); }); if (checkboxes.length === 0) { alert('Bitte wähle mindestens einen Angriff aus!'); return; } let count = 0; // Verarbeite sequenziell mit async/await for (const checkbox of checkboxes) { const row = checkbox.closest('tr'); if (!row) continue; row.removeAttribute('data-attack-saved'); const quickedit = row.querySelector('.quickedit'); if (!quickedit) continue; const labelSpan = quickedit.querySelector('.quickedit-label'); if (!labelSpan) continue; const currentName = labelSpan.textContent.trim(); const newName = currentName.replace(/\s*\[.*?\]\s*/g, '').trim(); labelSpan.textContent = newName; count++; } if (count > 0) { showNotification(`Tags von ${count} Angriff(en) entfernt`); } } function addTagBoxToFilterDialog() { const filterForm = document.querySelector('form[action*="save_filters"]'); if (!filterForm) return; const filterTable = filterForm.querySelector('table.vis'); if (!filterTable) return; if (document.getElementById('tag_filter_box')) return; const commandInput = filterForm.querySelector('input[name="filters[target_comment]"]'); if (!commandInput) return; const wrapper = document.createElement('div'); wrapper.id = 'tag_filter_box'; wrapper.style.cssText = 'display: flex; gap: 10px; align-items: flex-start;'; filterTable.parentNode.insertBefore(wrapper, filterTable); wrapper.appendChild(filterTable); const buttonGroups = [[]]; TAG_BUTTONS.forEach(btn => { if (btn.isSeparator) { buttonGroups.push([]); } else { buttonGroups[buttonGroups.length - 1].push(btn); } }); buttonGroups.forEach((group, groupIndex) => { if (group.length === 0) return; const tagBox = document.createElement('table'); tagBox.className = 'vis'; tagBox.style.cssText = ` margin-left: 10px; vertical-align: top; `; tagBox.innerHTML = `
Der Attack Tagger ermöglicht es dir, eingehende Angriffe schnell und einfach mit Tags zu versehen, um sie besser zu organisieren und zu filtern.
📜 Big Madness
Hier kannst du die Tags anpassen:
| Beschreibung | Symbol | Shortcut | Mehrfach | Löschen | |
|---|---|---|---|---|---|
|
⋮⋮
|
${btn.isSeparator ? 'Trennlinie' : ``} | ${btn.isSeparator ? '' : ``} | ${btn.isSeparator ? '' : ``} | ${btn.isSeparator ? '' : ``} |