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 @@