From 951f68ca7ddb8886f77d085683f4bec9b092ed32 Mon Sep 17 00:00:00 2001 From: Steppenstreuner Date: Fri, 28 Aug 2026 20:17:20 +0200 Subject: [PATCH] add(ui/ux): keep episode numbers out of the audible query --- README.md | 2 +- static/app.js | 31 +++++++++++++++++++++++++++++-- static/index.html | 1 + 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01afb8c..b87b873 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Sonarr/Radarr-Style Request- & Import-Manager für **Ebooks**, **Comics/Mangas** 1. **Anfragen**: Im Web-UI per Titel/Autor/ISBN suchen (Ebooks: Open Library · Audiobooks: Audible · Manga: AniList) oder manuell anlegen. Beim Request wählst du die **Ziel-Library** (z.B. deine 8 Bookorbit-Libraries oder die 3 Audiobookshelf-Libraries english/adult/kids). Der Titel erscheint als **Missing**. Gehört ein Audiobook-Treffer zu einer Serie, legt **„Ganze Serie…"** alle Folgen auf einmal an (Folgennummer + echter Titel von Audible, optional auf einen Folgenbereich eingegrenzt). Audible hat keine Serien-Abfrage — wordarr sammelt die Folgen über mehrere Suchläufe ein; sollte eine fehlen, zeigt der Dialog die Lücke an. 2. **Download-Ordner**: wordarr überwacht keinen Downloader aktiv — du legst Dateien selbst in den Download-Ordner (`/mnt/downloads`). -3. **Import missing**: Im Tab *Import* den Ordner scannen. wordarr schlägt per Fuzzy-Matching Datei→Request-Zuordnungen vor (bei Serien zählt die Folgennummer im Ordnernamen plus der Serienname im Pfad, `???` und `Fragezeichen` gelten als dasselbe); du bestätigst oder korrigierst. Beim Import wird die Datei nach dem Namensschema der Library **umbenannt und verschoben**. Ordner mit mehreren Audio-Dateien werden als ein Audiobook behandelt (`Titel - Part 01.mp3`, …). Auch Ordner, die in Disc-Unterordner (`CD`, `CD1`, `CD 2`, `Disc 3`, `Teil 1`) aufgeteilt sind, gelten als **ein** Audiobook — der Ordnername darüber liefert den Titel, die Parts werden über alle Discs hinweg durchnummeriert. Unterordner ohne Audio (`Cover`, Scans, Booklets) werden dabei ignoriert. +3. **Import missing**: Im Tab *Import* den Ordner scannen. wordarr schlägt per Fuzzy-Matching Datei→Request-Zuordnungen vor (bei Serien zählt die Folgennummer im Ordnernamen plus der Serienname im Pfad, `???` und `Fragezeichen` gelten als dasselbe); du bestätigst oder korrigierst. Über 🔍 lässt sich zu einem Ordner direkt eine Anfrage suchen — eine führende Folgennummer (`017 - Titel`) wird dabei aus der Suchanfrage genommen, weil Audible sonst schlechter trifft, und stattdessen als Band/Folge übernommen. Beim Import wird die Datei nach dem Namensschema der Library **umbenannt und verschoben**. Ordner mit mehreren Audio-Dateien werden als ein Audiobook behandelt (`Titel - Part 01.mp3`, …). Auch Ordner, die in Disc-Unterordner (`CD`, `CD1`, `CD 2`, `Disc 3`, `Teil 1`) aufgeteilt sind, gelten als **ein** Audiobook — der Ordnername darüber liefert den Titel, die Parts werden über alle Discs hinweg durchnummeriert. Unterordner ohne Audio (`Cover`, Scans, Booklets) werden dabei ignoriert. ## Setup (Docker) diff --git a/static/app.js b/static/app.js index b23a004..83414a1 100644 --- a/static/app.js +++ b/static/app.js @@ -785,6 +785,28 @@ $("#import-deselect-all").addEventListener("click", () => { // ---- quick request from a scanned file ---- let quickItemIndex = null; +let quickEpisode = null; + +// "017 - Titel", "Folge 17: Titel", "[003] Titel" -> episode number + rest. +// A bare number without a separator ("100 Stunden") or without a leading zero is +// left alone: there it is part of the title, not a numbering. +const EPISODE_PATTERNS = [ + /^\s*(?:folge|teil|band|nr\.?)\s*(\d{1,4})\s*(?:[-–—._:]+\s*|\s+)/i, // "Folge 124: Titel" + /^\s*[\[(](\d{1,4})[\])]\s*(?:[-–—._:]+\s*|\s+)/, // "[003] Titel" + /^\s*(0\d{1,3})\s*(?:[-–—._:]+\s*|\s+)/, // "001 Titel" + /^\s*(\d{1,3})\s*[-–—._:]+\s*/, // "17 - Titel" +]; + +function splitEpisodeNumber(name) { + for (const re of EPISODE_PATTERNS) { + const m = name.match(re); + if (!m) continue; + const rest = name.slice(m[0].length).trim(); + if (!rest) break; // the number *is* the name + return { number: parseInt(m[1], 10), rest }; + } + return { number: null, rest: name }; +} function cleanFileName(name) { return name @@ -797,8 +819,13 @@ function cleanFileName(name) { function openQuickDialog(i) { quickItemIndex = i; const item = scanItems[i]; + // the episode number wrecks the Audible keyword search, so it is kept out of + // the query and used as the volume instead + const { number, rest } = splitEpisodeNumber(item.name); + quickEpisode = number; $("#quick-file").textContent = (item.rel_dir ? item.rel_dir + "/" : "") + item.name; - $("#quick-q").value = cleanFileName(item.name); + $("#quick-episode").textContent = number != null ? `Folge ${number} — nicht mitgesucht` : ""; + $("#quick-q").value = cleanFileName(rest); $("#quick-library").innerHTML = libOptions(item.media_type) || ""; $("#quick-results").innerHTML = ""; @@ -893,7 +920,7 @@ async function pickQuickResult(r) { const req = await createRequest({ library_id: parseInt(libId), title: r.title, authors: r.authors, narrator: r.narrator || "", - external_id: r.external_id, volume: r.volume ?? null, + external_id: r.external_id, volume: r.volume ?? quickEpisode, year: r.year, series: r.series, cover_url: r.cover_url, }); setLastLib(scanItems[i].media_type, libId); diff --git a/static/index.html b/static/index.html index 04a6cd0..6e7c3b7 100644 --- a/static/index.html +++ b/static/index.html @@ -191,6 +191,7 @@

Suchen & Anfragen

+