Commit Graph

963 Commits

Author SHA1 Message Date
neon 0b89e5a4a1 fix(series): count duplicate formats as one volume
Preserve raw book counts for compatibility while reporting logical ownership and reading progress for duplicate series positions.

Closes #1871
2026-10-27 03:55:23 +00:00
neon 1c27e5dd05 fix(koreader): keep manual links to a different copy matched and suggest links
Report a linked copy whose bytes differ from the library file as positionIncompatible instead of
unmatched, so the plugin keeps the match, skips highlights without a failure and still syncs progress.
Treat a position-less place within a page as the same place and jump to the nearest page.
Suggest library books with the same title and authors for books to review, with bulk linking.
See docs/KOREADER_HASH_MATCHING.md.

Closes #1872
Closes #1919
2026-10-26 15:06:04 +00:00
neon b9e5fe3a29 fix(koreader): keep the configured file name on downloads
Plugins from 1.7.0 request a delivery variant, and every such download had
" - bo-<id>-<variant>" added to its name. Only copies that would share a path are
renamed now: a narrated EPUB's text-only copy and later same-format files. A new
<a|b|c> chained default also stops the default pattern naming authorless
standalone books "Standalone/ - <title>". See docs/READ_ALOUD_DELIVERY.md.

Closes #1893
2026-10-24 21:06:09 +00:00
neon 946855cd30 feat(library): read series from folders for comics and manga
Offer the one-folder-per-series layout Komga and Kavita use as a File as Book path-rule preset
rather than a new organization mode, so released iOS builds and both existing modes are unaffected.
Add a job that applies saved rules to books already in a library, and keep a run year such as
"Batman (2016)" when ComicInfo or a provider names the series without it.

Ref #1167
Ref #1918
Ref #839
2026-10-24 04:43:17 +00:00
neon faee78370d feat(library): rebuild the folder picker as a tree with a live plan
Pick folders from a lazily loaded tree with find-by-name and typed paths, beside a plan showing
the books each organization mode would import, overlaps with other libraries, and folders no
library reads yet. New ManageLibraries /path routes walk folders within entry and time bounds.
Fix the scanner filing a disc-named library folder such as /mnt/disk1 under the folder above it,
and move books already filed there onto their primary file on the next scan.
2026-10-24 04:04:00 +00:00
neon d29b06c6e8 fix(auth): restore metadata editing lost to the 3.5.0 library roles
Promote Viewer grants to Editor on book libraries for non-superusers who hold Edit metadata but
can edit nowhere, which is how every such account was left after the upgrade. Accounts with any
Editor or Owner role are left alone. The Users drawer now says where Edit metadata takes effect,
read from a new GET /users/:id/library-access route.

Closes #1902
2026-10-24 03:56:25 +00:00
neon b50b8da56c feat(metadata): add external HTTP metadata providers
Register book-metadata-provider services by address, with no code change per
provider; Settings lists ready-made ones to install. Ranking prefers a record of
the book's own medium. LubimyCzytac moves out to its own provider and Aladin,
whose API closes, is retired; their ids stay. Adds migrations 0129 and 0130.
See docs/METADATA_PROVIDERS.md.

Ref #1405
2026-10-23 06:11:04 +00:00
neon 9e49f496f1 feat(reader): add grouped reading progress routes
Add GET and PUT /reading-progress and DELETE /reading-progress/:fileId: positions grouped by
kind, up to 25 files saved in one request, and an optional editedAt so the server keeps the newer
of two saves. Move the web reader and book page to them, and list reading-progress in
SERVER_FEATURES for the iOS and Android apps, which adopt the routes in the same release.
2026-10-22 17:28:07 +00:00
neon 6b6b662e79 fix(books): deliver plain EPUBs that fail text-only inspection
A text-only download of an EPUB with no narration returned 400 when the
strict inspection tripped on a structural fault such as duplicate manifest
ids, a missing resource or a chapter over 16 MiB. Serve such books as stored,
and log why inspection failed or why a text-only delivery was refused.

Closes #1888
2026-10-22 15:31:35 +00:00
neon 1c593d7403 feat(stats): add a reading history of every finished read
Add a History page listing every read newest first, rereads included, as a
virtualized ledger or month cover walls, with period totals, per-read ratings,
export and a share image. Deleting a book keeps its finished reads under a
title and author snapshot (migration 0128).

Closes #1630
2026-10-22 14:45:09 +00:00
neon d21c68d13c feat(books): add an audiobook chapter editor
Add a Chapters tab to edit chapter timing and titles, look chapters up on Audible or in the ebook,
snap starts to pauses, keep a version history and write chapters into M4B, M4A and MP3 files. Add a
chapter health tool with a bulk Audible lookup queue. Editing needs Editor access to the book's
library. Chapter edits keep released iOS playback working by accepting a stale manifest revision
while the track layout is unchanged.

Ref #1120
2026-10-22 04:36:49 +00:00
neon 8810d47658 feat(books): remember a default read-aloud narration language
Resolve book languages stated as names ("English") to a code, so the
dialog and the server's fallback stop calling English unsupported. The
dialog groups the ebook and audiobook in a Sources card, shows
Highlighting without the Advanced disclosure, and saves a per-user
default under /user-preferences/read-aloud for books it cannot match.
2026-10-22 00:42:06 +00:00
neon 0320210767 fix: say which download client is not responding
Show one notice above the release picker naming the client, with a
Try again that resends the same release, instead of a chip on every
row. Grab failures carry the client name in errorMeta, and transport
errors read "connection refused" rather than "fetch failed".
2026-10-22 00:05:10 +00:00
neon 992224f2e8 feat(books): add read-aloud generation
Generate a synced read-aloud EPUB from a book's EPUB and audiobook through an
optional STAlign worker (Docker on CPU, or native with MLX on Apple Silicon),
with review, re-sync, replace in place, restart recovery and live progress on
the Files tab. Book details also stack the ebook and audiobook covers when
both exist. See docs/READ_ALOUD.md.
2026-10-21 23:44:37 +00:00
neon 99aa7dce70 fix(reader): preserve third-party KOReader menu entries
Extend KOReader's cached menu order instead of persisting complete
override lists. Remove marker-owned legacy overrides during upgrade
while preserving hand-maintained entries.

Closes #1878
2026-10-21 19:51:36 +00:00
neon fbd7a2e125 feat(metadata): clean pasted identifiers instead of truncating them
ISBNs copied from Amazon carry hyphens, and copied ASINs often start with a zero-width space.
The edit tab cut a hyphenated ISBN at its maxlength, Book Dock sent it on for the server to
reject, and an invisible character left an ID that looked right but never matched.

Clean ISBN, ASIN and provider id input as it is typed or pasted, and apply the same shared
rule in the book metadata and Book Dock DTOs so every client, including released iOS builds,
gets it. Provider ids keep their punctuation because Google ids and slugs use it, and an
over-long value is kept whole so validation still names the field.

Closes #1624
2026-10-21 05:47:03 +00:00
neon edc5e41105 feat(books): filter read-along titles
Expose read-along EPUBs as an additive format filter while preserving ordinary EPUB matching and existing client compatibility.

Closes #1583
2026-10-21 05:18:37 +00:00
neon d1cc18dc51 feat(smart-scope): add rolling older-than date filters
Allow users to build bounded historical windows without hard-coded calendar dates. Keep the API additive and advertise support for independently released clients.

Closes #1751
2026-10-21 03:59:41 +00:00
neon a41d3da63e fix(email): use shared default provider as fallback
Preserve explicit and per-user provider precedence while allowing multi-user installations to use a configured shared default deterministically.

Closes #1387
2026-10-20 16:05:33 -06:00
neon 2b8917afb6 feat(authors): allow correcting metadata matches
Let users edit author facts, choose and pin a provider record, and keep later refreshes tied to that record. Preserve compatibility for older clients through additive API fields and a discoverable server feature.

Keep server typechecking deterministic by breaking circular Drizzle inference and correcting the nullability mismatches it previously hid.

Closes #1464
2026-10-20 16:05:32 -06:00
neon 0d695a7ea0 feat(metadata): expand book request discovery
Allow request searches to browse broader provider result sets, rerun queries by exact author, and submit unmatched titles for approval. Advertise the optional limit so older clients retain their existing contract.

Closes #1363
2026-10-20 16:05:32 -06:00
neon 2be79af880 fix(stats): read session archetype hours in the user's time zone
Convert started_at to the user's configured time zone before taking the
hour, minute and weekday, and include the zone in the cache key so a
changed setting is picked up.

Closes #716
2026-10-20 16:05:32 -06:00
neon cc6bac696b fix(book-requests): tell a plugin indexer when its connection changes
Add an optional forget(indexerId) to the plugin contract and forward the
host's existing invalidation to it, so a plugin holding a live session
drops it when the credential, base URL or private-address setting changes.
A plugin that throws or rejects in forget is logged and never fails the save.

Fixes #1325
2026-10-20 16:05:32 -06:00
neon b95a285c1b feat(koreader): hide audiobook-only books from the KOReader plugin
Add a per-login setting, on by default, that leaves books whose content files are all audio out
of the plugin catalog, its counts and bulk downloads, and drops a library holding nothing else.
The plugin detail page also names the first file the device can open as the main file.

Closes #1345
2026-10-20 16:05:32 -06:00
neon a728bf293b feat(book-requests): link releases to their source page and hold imports for review
Torznab and Newznab read the feed's details page, plugins may send infoUrl, and the
search drops any link that could carry a credential. A grab can ask to be held for
review whatever its score, and the held review shows the file's own edition facts,
its cover and the release page. Torznab guids are now digested like Newznab's.

Refs #1438
2026-10-20 16:05:31 -06:00
neon 568614265f fix(books): list series stacks before standalones when sorting by series
With series collapsed, the series sort used the same key as title, so stacks
and standalone books interleaved alphabetically. Order stacks by series name
first, then standalones by title, in both directions. The jump rail now only
letters series, matching the flat view.

Fixes #1456
2026-10-20 16:05:31 -06:00
neon 2e454cc846 fix(koreader): rank synced books by when the device read them
Stamp reading_progress.last_read_at with the device's reading time instead of the
server's write time. A sweep pushes a whole shelf at once, so sorting by last read
came out in processing order. Keep the later of the stored and pushed times, so a
late or offline push never sinks a newer read from another client.

Fixes #1433
2026-10-20 16:05:30 -06:00
neon 98415b90cc fix(dashboard): keep abandoned books out of up next in series
Offer a series' next volume only when its read status is unset, unread or want to read, on the
shelf and in the is up next filter. Abandoned, on-hold and in-progress volumes at 0% no longer
show, and an abandoned volume ends its series there instead of surfacing the one after it.

Fixes #1649
2026-10-20 16:05:30 -06:00
neon a5c4dfac16 fix(series): stop counting unreleased and extra books as missing
Hardcover's books_count included novellas, split editions, box sets and announced books, so
series named books as missing that are not out yet. Count only released main books, clear the
inflated Hardcover totals once on boot, and stop a series row saved without a total from wiping
that series' total.

Fixes #1861
2026-10-20 16:05:29 -06:00
neon 6440281e65 fix(books): count played read-along narration in continue listening
Continue Listening listed only books with audiobook progress, so a book
whose only audio is a read-along EPUB never appeared there, on the web,
on the phone or in the car, however much of its narration was heard.

The shelf now also takes read-along EPUBs whose narration progress was
saved by playing it, which is when its seconds are set. Reading on any
device carries the narration marker along and clears the seconds, so a
book only read stays in Continue Reading. A book with both an audiobook
and narration under way is listed once, at its latest listening.

The web and the iOS Home shelf show these as ordinary book cards. iOS
CarPlay builds before this change leave them out of Continue, which
there lists only audiobooks; the current build plays their narration.

Ref #1847
2026-10-20 16:05:28 -06:00
neon 80244ea4e2 feat(koreader): rebuild the plugin interface and fix four-way annotation sync
Squash of feat/koreader-widget-board. KOReader plugin 2.0.0 gets the widget-board dashboard, redesigned
book details, menus, lists and dialogs, Decor page and reading companions. Highlights, notes and bookmarks
now stay in step across web, Kobo, KOReader and the phones (defects D-00 to D-20, migration 0121), and
reading progress and narration hand off cleanly between devices.
2026-10-20 16:05:27 -06:00
neon c1b4627e0d fix(kobo): take the Kobo's port from APP_URL when Host omits it
A Kobo sends Host without a port, so cover, reading state and download
links got the port the server listens on. Behind a published mapping
such as 3010:3000 that is the internal port, which the Kobo cannot
reach. Use APP_URL's origin when it names the same host; other hosts,
explicit ports and forwarded headers behave as before.

Fixes #1281
2026-10-17 05:19:20 +00:00
neon 7f172fd8fb fix(kobo): convert chapters listed by percent-encoded hrefs
kepubify looks a manifest href up exactly as written, so a chapter stored
as "chapter one.xhtml" but listed as "chapter%20one.xhtml" shipped with no
KoboSpans and every synced position snapped back to the chapter start.
Convert a copy with those hrefs decoded and restore the book's spelling
afterwards; only affected books get a new cache key. Position conversion
now matches and loads such chapters too.

Fixes #1136
2026-10-17 05:01:43 +00:00
neon 06cfc2a074 fix(kobo): hand the Kobo a sync token in a shape it recognises
Kobos on firmware 4.45 were reported failing every sync on BookOrbit's
prefixed PX tokens and passing on Calibre-Web's. Send Calibre-Web's base64
JSON token, or Kobo's own cursor verbatim when store titles carry one. The
token never located the snapshot, and PX and PX2 tokens still decode.

Fixes #1175
Fixes #435
2026-10-17 04:23:23 +00:00
neon edfa20e19f fix(kobo): deliver a changed cover to a Kobo that already has the book
A Kobo downloads a cover only for a new entitlement, so a changed cover
sent as changed metadata left the book with a text-only cover until the
home screen fetched a small tile. Record the cover id on each device
snapshot row and re-announce the book when it moves; rows from before
the change learn it silently. Serve tile-size requests at a full-screen
size so the sleep screen never stretches a tile.
2026-10-17 04:03:34 +00:00
neon 2dca4bc85c fix(kobo): send Kobo covers at the device's screen size
A Kobo asks for each cover at its screen size but always got the
400x600 grid thumbnail, so sleep screens and full-screen covers were
blurry. Scale the full cover to fit the requested box instead, behind a
new Full-size covers setting that is on by default. Drop the stale
UserStorage entries from the authorization matrix gap baseline.

Fixes #924
2026-10-17 03:34:18 +00:00
neon 51b53d4581 fix(kobo): relay Kobo notebooks to Kobo when store titles are on
A Kobo syncs its notebooks through Kobo's reading services, which
BookOrbit answered with an empty listing, so notebooks never synced.
When Include Kobo store titles is on, relay every UserStorage call to
readingservices.kobo.com; otherwise keep the empty listing.

Fixes #1787
2026-10-17 03:05:40 +00:00
neon b189bf2edd fix(reading): file late Kobo sessions into the read they finished
When a Kobo syncs after finishing a book, its finished state closes the
reading attempt before the offline sessions arrive, and those sessions
opened a second attempt, marking the book as reread or read twice.
Activity dated within the finished read now joins it, activity from
before it began joins nothing, and an undated start never widens the
range. Update the reading-attempts E2E Kobo test to use a real device
that holds the book.

Fixes #1224
Fixes #1182
2026-10-17 02:55:13 +00:00
neon 058cd6e7e4 fix(kobo): sync Kobo bookmarks as bookmarks and keep annotation sync converging
A Kobo dogear was stored as an empty yellow highlight. Land it as a
BookOrbit bookmark linked to the device's id, serve it back as a dogear,
sync deletions both ways, and move dogears already stored as highlights.
Report only books on that Kobo from checkforchanges: a re-registered
device was told about books it never had, so real changes were fetched
outside its sync and never applied. Serve a known annotation at its last
location instead of failing the whole pull when this copy cannot place it.

Fixes #1374
2026-10-17 02:42:42 +00:00
neon 5397825003 fix(kobo): open a newly downloaded book at its saved position
A book's first announcement went to the Kobo before it was downloaded,
when no Kobo location could be computed, so it carried a bare percentage
and the book opened at its start. Compute the location from the copy
being offered for bookmarks sent to the device; positions it reports
still wait for the transfer.
2026-10-17 01:20:03 +00:00
neon ff8759b9a0 fix(kobo): keep a Kobo's progress on the file its copy was built from
When a book's primary file changed, the Kobo kept the copy built from the
old file but its positions no longer converted, so it was sent a bare
percentage, reset to the start, and that reset was saved over real
progress. Sync each device against its copy's source file instead, which
BookOrbit's sibling sync carries to the other EPUBs, and serve the new
primary when the device downloads the book again.

Fixes #1786
2026-10-17 01:02:57 +00:00
neon 60ad70ff5c fix(kobo): trust a confirmed replacement for positions and annotations
A replacement stayed ambiguous after the user confirmed it, so every
position the Kobo sent was discarded and annotation pulls failed with 409.
Confirming now makes it the device's copy and moves the stored bookmark
onto it. It still cannot drag saved progress backwards, and that is
judged by position, since its percentages run on a different scale.

Ref #1786
2026-10-17 00:45:21 +00:00
neon 27edb5e25c perf(kobo): stop re-hashing a delivery's source on every Kobo sync
Every position conversion resolved the delivered artifact by hashing the
whole source file, so a multi-gigabyte read-along outlasted the Kobo's 30 s
request timeout. Remember each source's hash by its file signature and
hash again only after the file changes. Reusing the stamped Kobo location
on device syncs (defect 3) is left out: that stamp is not per device.

Ref #1786
2026-10-17 00:19:14 +00:00
neon abb44aa47a fix(kobo): refuse a replacement with the bytes the device already has
A replacement byte-identical to the device's copy was never downloaded, so
it stayed offered and every bookmark the device pushed was discarded.
Keep the current delivery instead, and tell the user in the replacement
panel that the device already has this copy.

Ref #1786
2026-10-17 00:10:32 +00:00
neon 5051ec0913 fix(kobo): open a read-along book at its narrated sentence on the Kobo
A read-along save can hold the sentence being narrated without a text
position, and the Kobo was then sent a bare percentage, so it opened the
book at its start. Turn the narration marker into a text position and a
Kobo location when the saved progress has no cfi.

Ref #1786
2026-10-16 23:59:51 +00:00
neon dd004a3fa7 fix(kobo): stop a replacement with no earlier copy from blocking delivery
A replacement prepared before the device ever received the book had no
earlier delivery to fall back to, so every sync failed the book. It now
stops standing for the book and the book gets an ordinary first offer.
Cancelling a replacement the device has not fetched now clears it, hands
the book back to the earlier delivery, and re-announces it. Update the
download E2E suite for the stale-offer replacement from #1816.

Ref #1786
2026-10-16 23:47:40 +00:00
neon d02b4fcb8a fix(kobo): stop store sync looping when Kobo's cursor is too large to carry
A store cursor over the carry limit was dropped while the device was still
told to continue, so the store restarted at its first page forever. Stop
store paging for that sync instead, and carry Kobo's cursor verbatim in a
compact PX2 token so the limit can rise to 7,000 characters. Tokens in the
old format still decode.

Fixes #1679
2026-10-16 23:34:28 +00:00
neon e070460876 fix(kobo): replace a stale delivery instead of failing its download forever
When a book's file changed after it was offered to a Kobo, every download
returned 409 because the offer could no longer be rebuilt. A device download
now retires that offer and serves a fresh one from the current file, and the
device bookmark adopts the copy it downloaded. Log the conflict code when a
sync defers a delivery. Also drop a dev-only relocate log from the reader.

Fixes #1816
2026-10-16 23:34:28 +00:00
François-Xavier Payet 7f12e8cd6d fix(kobo): reconcile counter estimates on their counter interval (#1796)
fix(kobo): reconcile counter estimates on their counter interval (#1796)

Stop counting offline Kobo reading twice. Record when each counter
estimate's interval began, split measured sessions across intervals by
wall-clock overlap, and subtract measured seconds instead of dropping the
estimate, so both arrival orders converge.

Closes #1667

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-10-16 17:33:05 -06:00
Dennis 428aefe8d1 feat(scanner): regex metadata extraction (#1669)
feat(metadata): extract metadata from file paths with regex rules (#1669)

Let libraries define ordered regex rules that read title, authors, series,
series number and dates from each book's path relative to its library folder.
Run rules in a bounded worker pool, honour source precedence and field locks,
and apply them on scans, uploads and watcher moves. Also retry unprobed M4B
tag writes with the mp4 muxer.

Closes #1351

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-10-16 16:24:11 -06:00