From be776d418f23f72d5305b7122c5b9dd70f3a4a54 Mon Sep 17 00:00:00 2001 From: Akamaru Date: Tue, 11 Nov 2025 00:42:35 +0100 Subject: [PATCH] =?UTF-8?q?MangaDex=20Cover=20Mass-Downloader:=20Button=20?= =?UTF-8?q?immer=20sichtbar=20//=20Checkbox=20f=C3=BCr=20Dezimal-Volumes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- mangadex-cover-downloader.user.js | 106 ++++++++++++++++++++++-------- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 664ece5..365d561 100644 --- a/README.md +++ b/README.md @@ -226,10 +226,12 @@ Ersetzt Client-Namen in den "Top Clients" Tabellen des Pi-hole Admin-Dashboards Lädt Cover von MangaDex im Bulk als ZIP-Datei herunter. Perfekt um Manga-Cover für mehrere Volumes auf einmal zu sammeln. **Funktionen:** -- Download-Button auf Cover-Seiten (`?tab=art`) +- Download-Button in der Buttonleiste (immer sichtbar, funktioniert auf allen Tabs) - Modal zum Eingeben des Volume-Bereichs (z.B. "1-10") +- Checkbox für Dezimal-Volumes (z.B. 1.2, 2.5) - optional ein/ausschaltbar - Automatisches Laden aller Cover-Informationen via MangaDex API -- Cover werden mit Volume-Nummer benannt: `00-01.jpg`, `00-02.jpg`, etc. +- Cover werden mit Volume-Nummer benannt: `00-01.jpg`, `00-02.jpg`, `00-01.2.jpg`, etc. +- Intelligente Filterung: nur ganze Zahlen oder inklusive Dezimal-Volumes - Fortschrittsanzeige während des Downloads (API-Aufruf, Download, ZIP-Erstellung) - Lädt nur Full-Cover herunter (keine komprimierten `.512.jpg` Versionen) - Automatischer ZIP-Download mit Manga-Titel im Dateinamen diff --git a/mangadex-cover-downloader.user.js b/mangadex-cover-downloader.user.js index f56d12d..3005e60 100644 --- a/mangadex-cover-downloader.user.js +++ b/mangadex-cover-downloader.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name MangaDex Cover Mass-Downloader // @namespace https://git.ponywave.de/Akamaru/Userscripts -// @version 1.0 +// @version 1.1 // @description Lädt Cover von MangaDex im Bulk als ZIP-Datei herunter // @author Akamaru // @match https://mangadex.org/title/* @@ -28,15 +28,10 @@ } function init() { - // Prüfe ob wir auf einer Art-Seite sind - if (!window.location.search.includes('tab=art')) { - return; - } - // Warte bis die Seite vollständig geladen ist const checkAndAddButton = () => { - const artSection = document.querySelector('[style*="grid-area: art"]'); - if (artSection) { + const buttonContainer = document.querySelector('.flex.gap-2.sm\\:mb-0.mb-2.flex-wrap'); + if (buttonContainer) { addDownloadButton(); } else { setTimeout(checkAndAddButton, 500); @@ -44,6 +39,16 @@ }; checkAndAddButton(); + + // Beobachte DOM-Änderungen für SPA-Navigation (z.B. Tab-Wechsel) + const observer = new MutationObserver(() => { + checkAndAddButton(); + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); } function addDownloadButton() { @@ -52,21 +57,21 @@ return; } - // Finde die Button-Container-Section - const buttonSection = document.querySelector('[style*="grid-area: buttons"]'); - if (!buttonSection) { + // Finde den Button-Container + const buttonContainer = document.querySelector('.flex.gap-2.sm\\:mb-0.mb-2.flex-wrap'); + if (!buttonContainer) { return; } - // Erstelle Download-Button + // Erstelle Download-Button im Stil von "Start Reading" const downloadBtn = document.createElement('button'); downloadBtn.id = 'cover-download-btn'; - downloadBtn.className = 'rounded custom-opacity relative md-btn flex items-center px-3 overflow-hidden accent'; - downloadBtn.style.cssText = 'min-height: 3rem; min-width: 3rem; margin-top: 0.5rem;'; + downloadBtn.className = 'flex-grow sm:flex-grow-0 rounded custom-opacity relative md-btn flex items-center px-3 overflow-hidden accent flex-grow sm:flex-grow-0'; + downloadBtn.style.cssText = 'min-height: 3rem; min-width: 3rem;'; downloadBtn.title = 'Cover herunterladen'; downloadBtn.innerHTML = ` - - + + @@ -74,8 +79,8 @@ downloadBtn.addEventListener('click', showDownloadModal); - // Füge Button zur Button-Section hinzu - buttonSection.appendChild(downloadBtn); + // Füge Button am Ende des Containers hinzu (ganz rechts) + buttonContainer.appendChild(downloadBtn); } function showDownloadModal() { @@ -116,6 +121,23 @@ inputContainer.appendChild(document.createTextNode('–')); inputContainer.appendChild(toInput); + // Checkbox für Dezimal-Volumes + const checkboxContainer = document.createElement('div'); + checkboxContainer.style.cssText = 'margin-bottom: 15px; display: flex; align-items: center; justify-content: center; gap: 8px;'; + + const decimalCheckbox = document.createElement('input'); + decimalCheckbox.type = 'checkbox'; + decimalCheckbox.id = 'include-decimal-volumes'; + decimalCheckbox.style.cssText = 'cursor: pointer; width: 18px; height: 18px;'; + + const checkboxLabel = document.createElement('label'); + checkboxLabel.htmlFor = 'include-decimal-volumes'; + checkboxLabel.textContent = 'Dezimal-Volumes einschließen (z.B. 1.2, 2.5)'; + checkboxLabel.style.cssText = 'color: #ccc; cursor: pointer; user-select: none;'; + + checkboxContainer.appendChild(decimalCheckbox); + checkboxContainer.appendChild(checkboxLabel); + // Progress-Container (initially hidden) const progressContainer = document.createElement('div'); progressContainer.id = 'download-progress'; @@ -169,13 +191,14 @@ downloadBtn.onclick = () => { const from = parseInt(fromInput.value); const to = parseInt(toInput.value); + const includeDecimals = decimalCheckbox.checked; if (!from || !to || from > to) { showError('Bitte gültige Volume-Nummern eingeben (Von ≤ Bis).'); return; } - startDownload(from, to); + startDownload(from, to, includeDecimals); }; buttonContainer.appendChild(cancelBtn); @@ -184,6 +207,7 @@ modal.appendChild(title); modal.appendChild(description); modal.appendChild(inputContainer); + modal.appendChild(checkboxContainer); modal.appendChild(errorContainer); modal.appendChild(progressContainer); modal.appendChild(buttonContainer); @@ -236,7 +260,7 @@ } } - async function startDownload(fromVolume, toVolume) { + async function startDownload(fromVolume, toVolume, includeDecimals = false) { // Disable download button const downloadBtn = document.getElementById('start-download-btn'); if (downloadBtn) { @@ -264,18 +288,34 @@ // Filtere Cover nach Volume-Bereich const selectedCovers = allCovers.filter(cover => { - const volume = parseInt(cover.attributes.volume); - return volume >= fromVolume && volume <= toVolume; + const volumeStr = cover.attributes.volume; + const volumeNum = parseFloat(volumeStr); + + // Prüfe ob Volume im Bereich liegt + if (volumeNum < fromVolume || volumeNum > toVolume) { + return false; + } + + // Wenn Dezimal-Volumes nicht eingeschlossen werden sollen, + // filtere alle Volumes mit Dezimalpunkt heraus + if (!includeDecimals && volumeStr.includes('.')) { + return false; + } + + return true; }); if (selectedCovers.length === 0) { - throw new Error(`Keine Cover für Volume ${fromVolume}-${toVolume} gefunden.`); + const msg = includeDecimals + ? `Keine Cover für Volume ${fromVolume}-${toVolume} gefunden.` + : `Keine Cover für Volume ${fromVolume}-${toVolume} gefunden (Dezimal-Volumes ausgeschlossen).`; + throw new Error(msg); } // Sortiere nach Volume selectedCovers.sort((a, b) => { - const volA = parseInt(a.attributes.volume); - const volB = parseInt(b.attributes.volume); + const volA = parseFloat(a.attributes.volume); + const volB = parseFloat(b.attributes.volume); return volA - volB; }); @@ -288,7 +328,19 @@ // Download Cover for (const cover of selectedCovers) { const fileName = cover.attributes.fileName; - const volume = cover.attributes.volume.padStart(2, '0'); // Pad with leading zeros + const volumeStr = cover.attributes.volume; + + // Format Volume-Nummer: Pad den ganzzahligen Teil mit Nullen + let formattedVolume; + if (volumeStr.includes('.')) { + // Dezimal-Volume: "1.2" -> "01.2" + const parts = volumeStr.split('.'); + formattedVolume = parts[0].padStart(2, '0') + '.' + parts.slice(1).join('.'); + } else { + // Ganze Zahl: "1" -> "01" + formattedVolume = volumeStr.padStart(2, '0'); + } + const extension = fileName.split('.').pop(); const coverUrl = `${CONFIG.coverBase}/${mangaId}/${fileName}`; @@ -300,7 +352,7 @@ } const blob = await coverResponse.blob(); - const newFileName = `00-${volume}.${extension}`; + const newFileName = `00-${formattedVolume}.${extension}`; zip.file(newFileName, blob); downloadedCount++;