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
This commit is contained in:
neon
2026-10-26 15:06:04 +00:00
parent 834da8c954
commit 1c27e5dd05
36 changed files with 1634 additions and 63 deletions
@@ -109,6 +109,7 @@
"GET /koreader/plugin/version",
"GET /koreader/syncs/progress/:document",
"GET /koreader/unmatched-books",
"GET /koreader/unmatched-books/suggestions",
"GET /koreader/users/auth",
"GET /libraries/:id|:libraryId",
"GET /libraries/:id|:libraryId/access",
@@ -419,7 +419,10 @@ describe('Kobo download identity in KOReader (e2e)', { timeout: 180_000 }, () =>
await ctx.db.insert(schema.koreaderBookHashLinks).values({ userId: user.userId, hash: deliveredHash, bookFileId: other.bookFileId });
try {
expect((await download()).statusCode).toBe(200);
expect((await match(deliveredHash)).matches).toEqual([{ hash: deliveredHash, bookId: other.bookId, bookFileId: other.bookFileId }]);
// The delivered bytes belong to the source book, so the linked file cannot place their positions.
expect((await match(deliveredHash)).matches).toEqual([
{ hash: deliveredHash, bookId: other.bookId, bookFileId: other.bookFileId, positionCompatible: false },
]);
const response = await ctx.app.inject({
method: 'PUT',
url: '/api/v1/koreader/syncs/progress',