From 169e84901cb769458eb061eb776d47075f6e70bb Mon Sep 17 00:00:00 2001 From: Steppenstreuner Date: Tue, 1 Sep 2026 17:20:34 +0200 Subject: [PATCH] add(metadata): request a whole ebook series from hardcover --- README.md | 3 +- static/app.js | 30 ++++++--- tests/test_ebooks.py | 74 +++++++++++++++++++++ wordarr/api/search.py | 15 +++-- wordarr/metadata/__init__.py | 14 ++++ wordarr/metadata/hardcover.py | 121 +++++++++++++++++++++++++++++++--- 6 files changed, 232 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 16362b6..7401e24 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ the same mount, otherwise moving turns into copy + delete. 2. **Search** – look up a title (ebooks: Hardcover, Google Books and Open Library, audiobooks: Audible plus MusicBrainz, manga: AniList) and request it, or add it by hand. The language filter narrows ebook and audiobook hits. For a series, - *Ganze Serie…* requests every episode at once. + *Ganze Serie…* requests every part at once — audiobooks from Audible's keyword + crawl, ebooks from Hardcover's series listing (needs the token). 3. **Import** – scan the download folder. wordarr suggests file→request matches, which you confirm or correct; entries can be merged, split, or turned into requests from their folder names. Every row names its format and size, and diff --git a/static/app.js b/static/app.js index e053937..2508867 100644 --- a/static/app.js +++ b/static/app.js @@ -239,7 +239,9 @@ $("#search-form").addEventListener("submit", async (e) => { ) .join(""); box.querySelectorAll("[data-series]").forEach((btn) => - btn.addEventListener("click", () => openSeriesDialog(results[btn.dataset.series])) + btn.addEventListener("click", () => + openSeriesDialog(results[btn.dataset.series], type, language) + ) ); box.querySelectorAll("[data-req]").forEach((btn) => btn.addEventListener("click", async () => { @@ -274,6 +276,7 @@ $("#search-form").addEventListener("submit", async (e) => { // ---- request a whole series ---- let seriesEpisodes = []; +let seriesType = "audiobook"; function renderSeriesList() { const from = parseInt($("#series-from").value); @@ -288,7 +291,9 @@ function renderSeriesList() { let gap = ""; if (e.volume != null && previous != null && e.volume > previous + 1) { const missing = e.volume - previous - 1; - gap = `
… ${missing} Folge${missing > 1 ? "n" : ""} nicht bei Audible gefunden (${previous + 1}–${e.volume - 1})
`; + const ebook = seriesType === "ebook"; + const unit = ebook ? (missing > 1 ? "Bände" : "Band") : `Folge${missing > 1 ? "n" : ""}`; + gap = `
… ${missing} ${unit} nicht ${ebook ? "bei Hardcover" : "bei Audible"} gefunden (${previous + 1}–${e.volume - 1})
`; } if (e.volume != null) previous = e.volume; return `${gap}