Anime-Loads: Verlauf-Massenlöschung
This commit is contained in:
17
README.md
17
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`
|
||||
|
||||
267
anime-loads-bulk-delete-history.user.js
Normal file
267
anime-loads-bulk-delete-history.user.js
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user