Repository navigation
Keep Audiobookshelf's ASIN and ISBN as match hints (0074 book_hints) - #393
Merged
Merged
Conversation
…_hints) An applied listening import can now write the ASIN and ISBN Audiobookshelf has for a book into book_hints (source abs, owned by the import), never into books.asin/isbn, so a hinted book stays unmatched until a match run or the dialog matches it. The rows are normalized and validated, replaced whole by the last import applied, deleted by that import's undo (and by a re-import's taking back of the previous one), carried by a move and a join, kept by a rebuild and dropped with their library. A match run's subjects and GetBook read the hint; ImportBooks says whether each book is already matched. Part of #366.
matchrun.QueryFor now sends, for a book with no ASIN or ISBN of its own, each identifier field from the Audiobookshelf import's hint first and the tag's second, so a match run and the match dialog look ABS's identifier up. A hit stays confident only while the book's length, title and series agree with it, and is held as identifier_disagrees otherwise, as tag hints are. Part of #366.
The fetch keeps every ABS book with a valid identifier (payload "identified", additive; payload v stays 1), and the review, a cutoff change and the apply pair them with books here for the whole server, by path then title and author, skipping gone items, split discs, contested books and books already matched. The apply writes them as match hints owned by the import, and the summary gains an additive "identifiers" block (books, asin, isbn, by_title, already_matched) so the review says what goes over. Part of #366.
The import review lists "ASIN or ISBN for matching" with the number of books, how many were paired by title and author and how many were already matched, when the summary has any; an import planned before the server counted them shows no row. Six locales. Part of #366.
CLAUDE.md's data model gains 0074 and the importer line its identifiers; CONVENTIONS' Listening imports describes the pairing, ownership and undo, and Tag identifiers are hints the order QueryFor sends them in. Part of #366.
An Audiobookshelf user whose history matches no book here can still carry ABS's ASIN and ISBN for the server's books as match hints, but the console called that import "Nothing to import" and kept Apply off. It now says the apply brings over identifiers for N books as match hints with no listening, enables Apply with a matching confirmation and toast, and keeps "Nothing to import" for an import with neither history nor identifiers. The server never refused an empty plan; a test applies and undoes such an import end to end. Part of #366.
A newly placed LibriVox book already loses the ASIN and ISBN a deleted book left at its path (overrides and enrichment). An Audiobookshelf hint left there was a third place they lingered: the match dialog sends a hint for a book with no identifier of its own, so it would have looked the new recording up by the old book's ASIN. The hint goes with the rest; an adopted folder keeps its own. Part of #366.
…saction The pairing of Audiobookshelf's identified books with the books here reads nothing of a user's, yet it ran once per mapped user in a fetch and, on apply, inside ApplyImport's transaction, which holds the server's one writer: with no shared folders every ABS book goes to the title tier, seconds of work at tens of thousands of books while every progress save waits. A fetch now pairs once for all its users and an apply pairs beside the history's matching, before the transaction. The identified list is built with the item index (it is the same for every user), and the check for a gone item is dropped: only items ABS still has are ever listed. A cutoff change still counts the identifiers (tested). Part of #366.
Every other import-owned table (0034) marks 'no import' with 0, and code reads them with import_id = 0 / <> 0, which silently skips a NULL. Nothing writes a NULL here, so the column takes the same convention before the schema ships. The index is no longer partial: every row is an import's, and a bound import_id = ? cannot use an index WHERE import_id <> 0. Part of #366.
…s once GetBook bound the hint's source as a second positional parameter while the match run's join inlined it: both now inline the constant, so GetBook takes one argument again. The console's review asked 'identifiers only?' and then read the count again with a fallback that could not fire: the model returns the count. The catalog's import tests share one apply helper. Part of #366.
BookMatchState and coverExpected reuse the match subject's join for the recorded match alone, so extending that join with book_hints tied two reads that never look at a hint to the hint table. The book_matches join is its own constant; the subject's join is that plus the hint. Part of #366.
Three comments predated the hint or left a case out: the match dialog's lookup named only tag hints; the unknown-ASIN report cites QueryFor's rule but reads a book's tag ASIN only, never its Audiobookshelf hint; and a summary's identifiers are all zero, not absent, once an import fetched before them is planned again. Part of #366.
A hint is what a match sends, never the book's identifier: the import test now reads the hinted book through /item and the console's book list and checks neither response carries the hint's ASIN. Part of #366.
A stored payload no longer names only the books a person has history for (its identified list is the whole ABS server's); a disc join copies a match hint whole from the earliest part that has one, not field by field; and a run's identifier is a hint from an import or a tag, not always a tag's. Part of #366.
QueryFor mixed the hint sources field by field, so a book with an Audiobookshelf ISBN and a tagged ASIN sent both. metaserve asks the ASIN first and stops when it knows it (audiosilo-meta pkg/query match), so the tag's ASIN, the hint that is wrong about one time in six, was looked up ahead of the import's identifier and the ISBN never asked: the tag outranked the import, the reverse of the order the hint exists for. A tag's ASIN is now sent only when the import gave nothing. A tag's ISBN still goes beside an import's ASIN, as it is asked second. The cost: an import's ISBN the community doesn't know no longer falls back to the tag's ASIN (the book goes to a title match). Part of #366.
The console tells the admin what matching sends to the community service in three places (Settings > Metadata, the bulk match card, a held item's reason), and each said an ASIN or ISBN comes from the book's tags. After an import a match sends Audiobookshelf's identifiers too, for books whose files carry none, so the disclosure names both in all six locales. Part of #366.
An apply takes back the person's previous applied import from the same source first. Since identifier-only imports can be applied, one with no listening for a person who already had their history imported would take that history back and write none, and undoing it would not bring it back. ApplyImport now refuses a write with no session, day, progress or bookmark when there is a previous applied import to take back (ErrImportNoListening, 409 import_not_ready, the transaction rolled back so nothing is written and the import stays in review). An identifier-only import for a person with nothing applied still applies and writes its hints. The console turns Apply off in that case and says to undo the applied import first to replace it. Part of #366.
An import's ISBN brings its own pair (its ASIN, if any, beside it); without one, the import's ASIN or else the tag's goes with the tag's ISBN. Same queries as before, read off one if/else instead of an assignment overwritten by a second check. Rewraps the CONVENTIONS line that says so. Part of #366.
The apply and the keeps-Apply-off tests pair on the same summary, so they read it from one const. Part of #366.
ApplyImport counted any progress row as listening, but the importer also writes a row whose only change is an earlier start date, which the review's summary (and so the console's historyEmpty) does not count. Such a re-import showed Apply off in the console, yet a direct POST was accepted and took back the person's applied import, sessions and all, for a start-date tweak. A progress row now counts only when it is new or moves the position, finish or finish date, matching the summary. Part of #366.
This was referenced Nov 14, 2026
KodeStar
added a commit
that referenced
this pull request
Nov 14, 2026
…etup, offer Match automatically (#395) Part 1 of #366 (Audiobookshelf import). Part 2, the identifiers kept as match hints, merged as #393. ## What - **`abs_certificate`**: when Audiobookshelf's https certificate isn't trusted (self-signed or a private CA), Connect, Status and a background fetch now fail with `abs_certificate` (still 502) instead of `abs_unreachable` / `not_abs`. The console shows it under the address field and points at ABS's plain http:// address. Nothing is ever trusted or skipped. Detection is one `errors.As` on `*tls.CertificateVerificationError`: Go's client handshake wraps every verify failure (unknown authority, hostname, invalid) in that type. https to an http-only port stays `abs_unreachable` (`http.ErrSchemeMismatch`). - **Import inside setup**: `ImportFlow` (connect, map, review, history) is extracted from Settings > Import and rendered inline in the setup flow's Audiobookshelf step (`embedded`), replacing the new-tab link. Continue and Skip stay. While a books library is scanning, the step says "Wait for the scan to finish" and Connect is off (from the libraries the flow already polls, so no wire field). - **Match offer**: #390's `UnmatchedPrompt` (Match automatically) appears once an import is applied, in the Past imports card, and in the setup step once the scan is done. It links to Health (a new tab from setup) and never starts a run by itself. - #393's identifiers row and the identifier-only apply guard are unchanged inside the extracted flow. Error-code wire change is additive; no player change. ## Tests Go: a TLS fake gives `abs_certificate` from Status and Connect. A hostname mismatch with a trusted pool gives it too, and 127.0.0.1 connects as the control. https to an http port stays `abs_unreachable`, and plain http is unchanged. API: 502 on connect, and `error_code` on a failed background fetch. Console: the certificate row, ImportFlow inline in the abs step with the scan gate, the offer after an apply and held until the imports are read, and imports-model. Gate: build-admin.sh (1628 tests), go build/vet/test -race, golangci-lint 0. ## Verified live Real Audiobookshelf 2.37.2 in Docker: - **Certificate error**: http connects. Behind a private-CA proxy and a truly self-signed proxy, Connect shows the certificate message under the address field, and curl returns 502 `abs_certificate`. Switching to http:// connects. The same results came from the Linux binary inside the `:edge` image (Alpine, Go's own verifier). - **Setup step**: on a fresh server with a slowed first scan (97 s), the gate showed and lifted 0.8 s after the scan ended. Connect, map, fetch, review (the identifiers row read "4 books") and apply all ran inline. Exactly one offer appeared, in Past imports, and it opened Health in a new tab with no match run started. Continue and Skip both moved on. - **Layout**: no sideways scroll at 390 px or on desktop. Not verified: the image's native-SQLite (CGO) build. The certificate check runs before any database work. ## Notes - de/es/fr/it/pt text for `errors.absCertificate`, `imports.failure.abs_certificate`, `setup.abs.import` and `setup.abs.waitScan` is machine-translated and wants a native read. - Follow-ups go in one issue (scan gate covers Connect only; no-books-library wording; redirect-to-https hint; offer link icon; the map table is cramped on a phone; two Back buttons while mapping). Pairs with KodeStar/audiosilo-docs (docs for #366, PR to follow).
KodeStar
added a commit
to KodeStar/audiosilo-docs
that referenced
this pull request
Nov 14, 2026
…ort inside setup (#310) Docs for KodeStar/audiosilo-server#366, the Audiobookshelf import. It covers part 2 (KodeStar/audiosilo-server#393, merged) and part 1 (KodeStar/audiosilo-server#395). **Merge with or after #395**: the certificate error, the import inside setup, the match offer and `setup/abs.png` exist only there. ## User Guide - **admin/import.md**: a new "Match your books after an import" section. An import's ASINs and ISBNs are match hints. They never become the book's ASIN and never show in the apps, and the book stays unmatched until a match is applied (hence the offer). Hints come only from libraries the token can see. It also covers: - undo, and which import owns the hints; - an identifier-only import refused when it would replace an applied one; - the certificate error, with the advice to use Audiobookshelf's plain http:// address (never trust the certificate); - waiting for the scan. - **getting-started/first-run.md**: step 6 now runs the import inside the step, says to wait for the scan, and shows the Match automatically offer. - **getting-started/coming-from-audiobookshelf.md**: matches the above. ABS's "Store metadata with item" is off by default (`server/objects/settings/ServerSettings.js`, `storeMetadataWithItem = false`, v2.37.2), so that line is unchanged. - **admin/health.md**: a new "Audiobookshelf's ASINs and ISBNs" section. A `metadata.json`'s ids are the book's own; an import's are hints. A wrong hint is settled by matching the book by hand. - **admin/books.md** ("What matching sends") and **features.md**. ## Developer Docs - **api/reference.md**: - `abs_certificate`, both as a 502 and as an `error_code`; - the summary's `identifiers {books, asin, isbn, by_title, already_matched}` and a new "Identifiers (match hints)" section; - the apply's 409 `import_not_ready` refusal; - undo, the match dialog's hint order and `identifier_disagrees` from a hint. - **data-model.md**: `book_hints` (0074) and "Match hints from an import". It gives the QueryFor order: own ids win, an Audiobookshelf hint goes ahead of tag hints, a tag ASIN goes only when the import gave no identifier, and a tag ISBN may go beside an ABS ASIN. It also covers ownership and the LibriVox drop: a book placed from LibriVox drops the hints at its path, and an adopted folder keeps them. - **web-ui.md** (the abs step and `UnmatchedPrompt` sites), **scanner.md** (hints don't count as matched), **bridge/librivox.md**, **cross-repo-contract.md**, and a listening-imports row in the documentation mapping table. ## Screenshots `setup/abs.png` is recaptured against #395's server. It shows the inline Connect form and the Match automatically offer. ## Checks `npm run build` is green. The docs review (`/simplify` and `/code-review high`) applied 15 fixes, each checked against the code (`internal/matchrun/plan.go` QueryFor, `hints_test.go`, the PR1 console).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #366 (moving from Audiobookshelf ends up matched). This is the identifiers half; the certificate error, the import inside setup and the match offer follow in a second PR once #389 and #390 merge.
What
An applied Audiobookshelf import now keeps ABS's ASIN and ISBN for every ABS book it can pair with one of ours, as a match hint:
book_hints(migration 0074:library_id,path,source,asin,isbn,import_idwith 0 = none,set_at). It survives a rebuild, follows a moved folder (moveBookState) and goes with its library./admin/books(tested). Nothing shows until a match run is applied.book_matcheshow=auto); a disagreeing one goes to review.identifiersblock (books,asin,isbn,by_title,already_matched), shown as a review row; the stored payload gainsidentified(omitempty, payload version unchanged).import_not_readyand changes nothing. "Listening" matches the console's rule exactly (sessions, days, bookmarks, progress that is new or moves position, finished or finish date).Tests
Catalog apply/undo/re-import/move/library delete/rebuild of hints, the refusal (and that the applied import's rows are intact), the listening rule table, pairing tiers and skips, plan summary counts, QueryFor order, an ABS-hinted identifier hit confident vs disagreeing, migration 0074, the API's identifier-only apply and refusal, hints absent from
/itemand/admin/books, admin-only import routes. Console: identifiers row, identifier-only notice, disabled Apply + warning.Gate:
scripts/build-admin.sh,go build/vet/test -race ./...,golangci-lint run(0 issues), native-tag vet/test -race/lint.Reviews: one ★ review (two passes), one fix round.
Verified live
Scratch server from this branch against a real Audiobookshelf 2.37.2 in Docker and live meta.audiosilo.app, four tagless books with no metadata.json:
book_hintsrows,books.asinempty, no hint in/itemor/admin/books.book_matcheshow=auto on the hinted recordings (two only cleared the auto bar through the identifier hit).import_not_ready, their progress byte-identical and their import still applied.Notes