From 2a452394df5c9b56c02b22d17b75e293f107d9eb Mon Sep 17 00:00:00 2001 From: Akamaru Date: Fri, 3 Oct 2025 20:18:11 +0200 Subject: [PATCH] =?UTF-8?q?Anime-Loads:=20Verlauf-Massenl=C3=B6schung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++ anime-loads-bulk-delete-history.user.js | 267 ++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 anime-loads-bulk-delete-history.user.js diff --git a/README.md b/README.md index 83c7d7a..9e89121 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,23 @@ Filtert und versteckt Uploads bestimmter Releasegruppen auf Serienfans.org und F --- +### 5. `anime-loads-bulk-delete-history.user.js` + +**Beschreibung:** +Fügt Buttons zum schnelleren Löschen von Verlaufseinträgen auf anime-loads.org hinzu. Erspart das mühsame einzelne Löschen von hunderten oder tausenden Einträgen. + +**Funktionen:** +- Checkboxen zur Auswahl einzelner Einträge +- "Alle auswählen/abwählen" Button für schnelle Massenauswahl +- "Ausgewählte löschen" Button zum Löschen markierter Einträge +- "Alle löschen" Button zum Löschen aller Einträge auf der aktuellen Seite +- "Alle Seiten laden" Button lädt alle Verlaufsseiten in eine einzige Ansicht (bei 3000+ Einträgen sehr praktisch) +- Live-Zähler zeigt Anzahl ausgewählter Einträge (z.B. "Ausgewählt: 15 / 3180") +- Moderne Modal-Dialoge +- Fortschrittsanzeige beim Laden mehrerer Seiten + +--- + ## Übersicht der enthaltenen UserStyles ### 1. `myanimelist-tweaks.user.css` diff --git a/anime-loads-bulk-delete-history.user.js b/anime-loads-bulk-delete-history.user.js new file mode 100644 index 0000000..0b0a033 --- /dev/null +++ b/anime-loads-bulk-delete-history.user.js @@ -0,0 +1,267 @@ +// ==UserScript== +// @name Anime-Loads: Verlauf-Massenlöschung +// @namespace https://git.ponywave.de/Akamaru/Userscripts +// @version 1.0 +// @description Fügt Buttons hinzu, um Verlaufseinträge auf anime-loads.org schneller zu löschen +// @author Akamaru +// @match https://www.anime-loads.org/history* +// @grant none +// @updateURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/anime-loads-bulk-delete-history.user.js +// @downloadURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/anime-loads-bulk-delete-history.user.js +// ==/UserScript== + +(function() { + 'use strict'; + + // Warte bis die Seite geladen ist + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + + function init() { + const historyDiv = document.getElementById('history'); + if (!historyDiv) return; + + // Erstelle Button-Container + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = 'margin-bottom: 15px; padding: 10px; background-color: #f5f5f5; border-radius: 5px;'; + + // Button: Alle löschen + const deleteAllBtn = document.createElement('button'); + deleteAllBtn.textContent = 'Alle löschen'; + deleteAllBtn.className = 'btn btn-danger'; + deleteAllBtn.style.marginRight = '10px'; + deleteAllBtn.onclick = deleteAll; + + // Button: Ausgewählte löschen + const deleteSelectedBtn = document.createElement('button'); + deleteSelectedBtn.textContent = 'Ausgewählte löschen'; + deleteSelectedBtn.className = 'btn btn-warning'; + deleteSelectedBtn.style.marginRight = '10px'; + deleteSelectedBtn.onclick = deleteSelected; + + // Button: Alle auswählen/abwählen + const toggleAllBtn = document.createElement('button'); + toggleAllBtn.textContent = 'Alle auswählen'; + toggleAllBtn.className = 'btn btn-primary'; + toggleAllBtn.style.marginRight = '10px'; + toggleAllBtn.onclick = function() { + const checkboxes = historyDiv.querySelectorAll('.bulk-delete-checkbox'); + const allChecked = Array.from(checkboxes).every(cb => cb.checked); + checkboxes.forEach(cb => cb.checked = !allChecked); + toggleAllBtn.textContent = allChecked ? 'Alle auswählen' : 'Alle abwählen'; + updateCounter(); + }; + + // Button: Alle Seiten laden + const loadAllBtn = document.createElement('button'); + loadAllBtn.textContent = 'Alle Seiten laden'; + loadAllBtn.className = 'btn btn-info'; + loadAllBtn.id = 'load-all-pages-btn'; + loadAllBtn.onclick = loadAllPages; + + // Zähler für ausgewählte Einträge + const counterSpan = document.createElement('span'); + counterSpan.id = 'selection-counter'; + counterSpan.style.cssText = 'margin-left: 15px; font-weight: bold; font-size: 16px;'; + counterSpan.textContent = 'Ausgewählt: 0'; + + buttonContainer.appendChild(deleteAllBtn); + buttonContainer.appendChild(deleteSelectedBtn); + buttonContainer.appendChild(toggleAllBtn); + buttonContainer.appendChild(loadAllBtn); + buttonContainer.appendChild(counterSpan); + + // Füge Button-Container vor dem History-Div ein + historyDiv.parentNode.insertBefore(buttonContainer, historyDiv); + + // Füge Checkboxen zu jedem Eintrag hinzu + addCheckboxes(); + } + + function updateCounter() { + const counter = document.getElementById('selection-counter'); + if (!counter) return; + + const checked = document.querySelectorAll('.bulk-delete-checkbox:checked').length; + const total = document.querySelectorAll('.bulk-delete-checkbox').length; + counter.textContent = `Ausgewählt: ${checked} / ${total}`; + } + + function addCheckboxes() { + const removeButtons = document.querySelectorAll('.remove-history'); + removeButtons.forEach(btn => { + const tr = btn.closest('tr'); + if (!tr || tr.querySelector('.bulk-delete-checkbox')) return; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.className = 'bulk-delete-checkbox'; + checkbox.style.cssText = 'margin-right: 10px; transform: scale(1.3);'; + checkbox.addEventListener('change', updateCounter); + + const td = btn.closest('td'); + td.insertBefore(checkbox, td.firstChild); + }); + + updateCounter(); + } + + function showModal(message, onConfirm) { + // Erstelle Modal-Overlay + const overlay = document.createElement('div'); + overlay.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center;'; + + // Erstelle Modal-Content + const modal = document.createElement('div'); + modal.className = 'modal-content'; + modal.style.cssText = 'background: white; padding: 25px; border-radius: 8px; max-width: 500px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);'; + + const messageEl = document.createElement('p'); + messageEl.textContent = message; + messageEl.style.cssText = 'margin: 0 0 20px 0; font-size: 16px;'; + + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = 'display: flex; justify-content: flex-end; gap: 10px;'; + + const cancelBtn = document.createElement('button'); + cancelBtn.textContent = 'Abbrechen'; + cancelBtn.className = 'btn btn-default'; + cancelBtn.onclick = () => overlay.remove(); + + const confirmBtn = document.createElement('button'); + confirmBtn.textContent = 'Bestätigen'; + confirmBtn.className = 'btn btn-danger'; + confirmBtn.onclick = () => { + overlay.remove(); + onConfirm(); + }; + + buttonContainer.appendChild(cancelBtn); + buttonContainer.appendChild(confirmBtn); + modal.appendChild(messageEl); + modal.appendChild(buttonContainer); + overlay.appendChild(modal); + document.body.appendChild(overlay); + + // Schließe Modal bei Klick auf Overlay + overlay.addEventListener('click', (e) => { + if (e.target === overlay) overlay.remove(); + }); + } + + function deleteAll() { + const removeButtons = Array.from(document.querySelectorAll('.remove-history')); + showModal(`Möchtest du wirklich ALLE ${removeButtons.length} Einträge aus dem Verlauf löschen?`, () => { + deleteEntries(removeButtons); + }); + } + + function deleteSelected() { + const checkboxes = Array.from(document.querySelectorAll('.bulk-delete-checkbox:checked')); + if (checkboxes.length === 0) { + showModal('Bitte wähle mindestens einen Eintrag aus.', () => {}); + return; + } + + showModal(`Möchtest du ${checkboxes.length} Einträge aus dem Verlauf löschen?`, () => { + const removeButtons = checkboxes.map(cb => cb.closest('td').querySelector('.remove-history')); + deleteEntries(removeButtons); + }); + } + + function deleteEntries(buttons) { + let deleted = 0; + const total = buttons.length; + + buttons.forEach((btn, index) => { + setTimeout(() => { + btn.click(); + deleted++; + + if (deleted === total) { + setTimeout(() => { + // Seite neu laden oder Checkboxen neu hinzufügen + location.reload(); + }, 500); + } + }, index * 200); // 200ms Verzögerung zwischen jedem Klick + }); + } + + async function loadAllPages() { + const btn = document.getElementById('load-all-pages-btn'); + const pagination = document.querySelector('.pagination'); + + if (!pagination) { + showModal('Keine Pagination gefunden.', () => {}); + return; + } + + // Finde die letzte Seitenzahl + const pageLinks = Array.from(pagination.querySelectorAll('li a')); + const pageNumbers = pageLinks + .map(link => { + const match = link.href.match(/\/page\/(\d+)/); + return match ? parseInt(match[1]) : 0; + }) + .filter(num => num > 0); + + if (pageNumbers.length === 0) { + showModal('Konnte letzte Seite nicht ermitteln.', () => {}); + return; + } + + const lastPage = Math.max(...pageNumbers); + const currentPage = parseInt(window.location.pathname.match(/\/page\/(\d+)/)?.[1] || 1); + + if (currentPage !== 1) { + showModal('Bitte gehe zuerst zur ersten Seite.', () => {}); + return; + } + + showModal(`Möchtest du ${lastPage - 1} weitere Seiten laden? Dies kann eine Weile dauern.`, async () => { + btn.disabled = true; + btn.textContent = 'Lade Seiten...'; + + const historyDiv = document.getElementById('history'); + const table = historyDiv.querySelector('table tbody'); + + for (let page = 2; page <= lastPage; page++) { + try { + btn.textContent = `Lade Seite ${page}/${lastPage}...`; + + const response = await fetch(`https://www.anime-loads.org/history/page/${page}`); + const html = await response.text(); + + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const newRows = doc.querySelectorAll('#history table tbody tr'); + + newRows.forEach(row => { + table.appendChild(row.cloneNode(true)); + }); + + // Kleine Verzögerung, um Server nicht zu überlasten + await new Promise(resolve => setTimeout(resolve, 500)); + } catch (error) { + console.error(`Fehler beim Laden von Seite ${page}:`, error); + } + } + + // Pagination verstecken + pagination.style.display = 'none'; + + // Checkboxen zu neuen Einträgen hinzufügen + addCheckboxes(); + + btn.textContent = 'Alle Seiten geladen!'; + setTimeout(() => { + btn.textContent = 'Alle Seiten laden'; + btn.disabled = false; + }, 3000); + }); + } +})();