1
0
Files
Userscripts/anime-loads-bulk-delete-history.user.js
2025-11-11 00:58:22 +01:00

399 lines
16 KiB
JavaScript

// ==UserScript==
// @name Anime-Loads: Verlauf-Massenlöschung
// @namespace https://git.ponywave.de/Akamaru/Userscripts
// @version 1.4
// @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*
// @icon https://www.google.com/s2/favicons?domain=anime-loads.org&sz=32
// @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() {
// Prüfe ob Bulk-Delete läuft
checkAndContinueBulkDelete();
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();
};
// 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(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 historyDiv = document.getElementById('history');
if (!historyDiv) return;
const rows = historyDiv.querySelectorAll('table tbody tr');
rows.forEach(row => {
if (row.querySelector('.bulk-delete-checkbox')) return;
const removeBtn = row.querySelector('.remove-history');
if (!removeBtn) 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 = removeBtn.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() {
showDeleteOptionsModal();
}
function showDeleteOptionsModal() {
// 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 title = document.createElement('h3');
title.textContent = 'Verlauf löschen';
title.style.cssText = 'margin: 0 0 20px 0;';
const optionsDiv = document.createElement('div');
optionsDiv.style.cssText = 'margin-bottom: 20px;';
// Option 1: Aktuelle Seite
const currentPageBtn = document.createElement('button');
currentPageBtn.textContent = 'Aktuelle Seite löschen';
currentPageBtn.className = 'btn btn-warning';
currentPageBtn.style.cssText = 'width: 100%; margin-bottom: 10px;';
currentPageBtn.onclick = () => {
overlay.remove();
deleteCurrentPage();
};
// Option 2: Gesamter Verlauf
const allPagesBtn = document.createElement('button');
allPagesBtn.textContent = 'Gesamten Verlauf löschen';
allPagesBtn.className = 'btn btn-danger';
allPagesBtn.style.cssText = 'width: 100%; margin-bottom: 10px;';
allPagesBtn.onclick = () => {
overlay.remove();
deleteAllPages();
};
// Option 3: Seite x bis x
const rangeDiv = document.createElement('div');
rangeDiv.style.cssText = 'display: flex; gap: 10px; align-items: center; margin-bottom: 10px;';
const fromInput = document.createElement('input');
fromInput.type = 'number';
fromInput.placeholder = 'Von Seite';
fromInput.className = 'form-control';
fromInput.style.cssText = 'flex: 1;';
fromInput.min = '1';
const toInput = document.createElement('input');
toInput.type = 'number';
toInput.placeholder = 'Bis Seite';
toInput.className = 'form-control';
toInput.style.cssText = 'flex: 1;';
toInput.min = '1';
const rangeBtn = document.createElement('button');
rangeBtn.textContent = 'Löschen';
rangeBtn.className = 'btn btn-primary';
rangeBtn.onclick = () => {
const from = parseInt(fromInput.value);
const to = parseInt(toInput.value);
if (from && to && from <= to) {
overlay.remove();
deletePageRange(from, to);
} else {
alert('Bitte gültige Seitenzahlen eingeben.');
}
};
rangeDiv.appendChild(fromInput);
rangeDiv.appendChild(toInput);
rangeDiv.appendChild(rangeBtn);
// Abbrechen Button
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Abbrechen';
cancelBtn.className = 'btn btn-default';
cancelBtn.style.cssText = 'width: 100%;';
cancelBtn.onclick = () => overlay.remove();
optionsDiv.appendChild(currentPageBtn);
optionsDiv.appendChild(allPagesBtn);
optionsDiv.appendChild(rangeDiv);
optionsDiv.appendChild(cancelBtn);
modal.appendChild(title);
modal.appendChild(optionsDiv);
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 deleteCurrentPage() {
const removeButtons = Array.from(document.querySelectorAll('.remove-history'));
showModal(`Möchtest du wirklich alle ${removeButtons.length} Einträge auf dieser Seite löschen?`, () => {
deleteEntries(removeButtons);
});
}
async function deleteAllPages() {
const pagination = document.querySelector('.pagination');
if (!pagination) {
showModal('Keine Pagination gefunden.', () => {});
return;
}
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);
const lastPage = pageNumbers.length > 0 ? Math.max(...pageNumbers) : 1;
showModal(`Möchtest du wirklich den GESAMTEN Verlauf löschen (${lastPage} Seiten)?`, async () => {
await deletePageRange(1, lastPage);
});
}
function deletePageRange(fromPage, toPage) {
// Speichere Lösch-Status in localStorage
localStorage.setItem('bulkDeleteFrom', fromPage);
localStorage.setItem('bulkDeleteTo', toPage);
localStorage.setItem('bulkDeleteCurrent', fromPage);
localStorage.setItem('bulkDeletePagesProcessed', '0');
// Starte Löschvorgang
const url = fromPage === 1 ? 'https://www.anime-loads.org/history' : `https://www.anime-loads.org/history/page/${fromPage}`;
window.location.href = url;
}
function checkAndContinueBulkDelete() {
const from = parseInt(localStorage.getItem('bulkDeleteFrom'));
const to = parseInt(localStorage.getItem('bulkDeleteTo'));
const pagesProcessed = parseInt(localStorage.getItem('bulkDeletePagesProcessed')) || 0;
if (!from || !to) return;
const totalPages = to - from + 1;
// Zeige Fortschritt mit Abbrechen-Button
const progress = document.createElement('div');
progress.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #f0ad4e; color: white; padding: 15px; border-radius: 5px; z-index: 10000; font-weight: bold;';
const progressText = document.createElement('div');
progressText.textContent = `Lösche Seiten... (${pagesProcessed + 1}/${totalPages})`;
progressText.style.marginBottom = '10px';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Abbrechen';
cancelBtn.className = 'btn btn-sm btn-default';
cancelBtn.onclick = () => {
localStorage.removeItem('bulkDeleteFrom');
localStorage.removeItem('bulkDeleteTo');
localStorage.removeItem('bulkDeleteCurrent');
localStorage.removeItem('bulkDeletePagesProcessed');
window.location.href = 'https://www.anime-loads.org/history';
};
progress.appendChild(progressText);
progress.appendChild(cancelBtn);
document.body.appendChild(progress);
// Lösche alle Einträge auf der aktuellen Seite
const removeButtons = Array.from(document.querySelectorAll('.remove-history'));
if (removeButtons.length === 0) {
// Keine Einträge mehr - fertig
localStorage.removeItem('bulkDeleteFrom');
localStorage.removeItem('bulkDeleteTo');
localStorage.removeItem('bulkDeleteCurrent');
localStorage.removeItem('bulkDeletePagesProcessed');
window.location.href = 'https://www.anime-loads.org/history';
return;
}
removeButtons.forEach(btn => btn.click());
// Warte, dann lade die aktuelle Seite neu (Einträge rutschen nach)
setTimeout(() => {
const newPagesProcessed = pagesProcessed + 1;
if (newPagesProcessed < totalPages) {
localStorage.setItem('bulkDeletePagesProcessed', newPagesProcessed.toString());
// Bleibe auf der gleichen Seite, da Einträge nachrutschen
const url = from === 1 ? 'https://www.anime-loads.org/history' : `https://www.anime-loads.org/history/page/${from}`;
window.location.href = url;
} else {
// Fertig - lösche Status
localStorage.removeItem('bulkDeleteFrom');
localStorage.removeItem('bulkDeleteTo');
localStorage.removeItem('bulkDeleteCurrent');
localStorage.removeItem('bulkDeletePagesProcessed');
window.location.href = 'https://www.anime-loads.org/history';
}
}, 2000);
}
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 processed = 0;
const total = buttons.length;
buttons.forEach((btn, index) => {
setTimeout(() => {
// Prüfe ob Button noch im DOM existiert
if (document.body.contains(btn)) {
btn.click();
}
processed++;
if (processed === total) {
setTimeout(() => {
// Seite neu laden
location.reload();
}, 1000); // Längere Wartezeit für alle Lösch-Requests
}
}, index * 300); // Längere Verzögerung zwischen Klicks
});
}
})();