diff --git a/docs/developer/api/koreader/sync_bookmarks.md b/docs/developer/api/koreader/sync_bookmarks.md index c7e4eb4..c1808d4 100644 --- a/docs/developer/api/koreader/sync_bookmarks.md +++ b/docs/developer/api/koreader/sync_bookmarks.md @@ -1,49 +1,81 @@ # Sync Bookmarks -Sync bookmarks from KOReader device. +Sync bookmarks, notes, and highlights from a KOReader device (bidirectional — the response also returns the server's current state for the book so the device can reconcile). **Endpoint**: `POST /api/sync/koreader/bookmarks` -**Auth**: Required (Device authentication) - -## Device Authentication - -This endpoint requires device authentication (not user JWT). Devices authenticate using their device credentials. +**Auth**: Device token (Bearer) ## Request Body -| Field | Type | Required | Description | -| --------- | ------------- | -------- | ------------------------- | -| device_id | string (UUID) | Yes | Device UUID | -| bookmarks | array | Yes | Array of bookmark objects | +| Field | Type | Required | Description | +| ------------ | ------ | --------------------- | ----------------------------------------------------------------- | +| book_uuid | string | one of uuid/sha | Book UUID (highest-confidence match) | +| book_sha256 | string | one of uuid/sha | Full-file SHA-256 (64 hex chars); format-aware (also matches `media_item_formats`, so a KEPUB/PDF download matches) | +| bookmarks | array | No | Bookmark objects | +| notes | array | No | Note objects | +| highlights | array | No | Highlight objects | -### Bookmark Object +At least one of `book_uuid` or `book_sha256` is required; `book_sha256` resolves through the shared BookResolver. -| Field | Type | Required | Description | -| ---------------- | ------- | -------- | -------------------------- | -| book | string | Yes | Book identifier | -| chapter | string | No | Chapter title | -| page | integer | No | Page number | -| position | float | Yes | Position in document (0-1) | -| notes | string | No | Bookmark notes | -| highlighted_text | string | No | Highlighted text | -| time | string | Yes | ISO 8601 timestamp | -| created_at | string | Yes | ISO 8601 timestamp | +### Bookmark / Note / Highlight Object + +All three types share the same KOReader annotation shape: + +| Field | Type | Required | Description | +| ------------ | ------- | -------- | ---------------------------------------------------- | +| chapter | int | No | Chapter index | +| datetime | string | No | ISO 8601 creation/edit timestamp | +| pos0 / pos1 | string | No | Start/end xpointer (or `page:N` / bare page) | +| page | int | No | Page number (fallback location when `pos0` is empty) | +| text | string | No | Highlighted text | +| notes | string | No | Note text attached to the annotation | +| type | string | No | Annotation type (`highlight`, `note`, `bookmark`) | +| color | string | No | Highlight color (highlights only) — KOReader palette name, see below | +| percentage | float | No | Position within the book (0-1) | +| book_sha256 | string | No | Per-annotation SHA-256; overrides the request-level book match | +| dedup_key | string | No | Stable echo key; an entry whose content is unchanged from what the server previously served is recognized as an echo rather than a new edit | + +### Color Semantics + +KOReader paints highlights from a fixed palette of color names; the web reader uses hex swatches. Colors are mapped at the boundary (unmappable values fall back to yellow on both sides): + +| KOReader name | Web hex | +| ------------- | --------- | +| yellow, orange | `#ffd54f` | +| green, olive | `#a5d6a7` | +| cyan, blue | `#90caf9` | +| purple | `#ce93d8` | +| red | `#f48fb1` | + +- An echo (device re-reporting an annotation it received from the server) carries **no color**, so the stored web color is never clobbered. +- A non-empty color means the user edited the highlight on the device; it is mapped to the nearest web swatch. ### Example Request ```json { - "device_id": "550e8400-e29b-41d4-a716-446655440000", + "book_sha256": "64-hex-char-sha256", "bookmarks": [ { - "book": "book.epub", - "chapter": "Chapter 1", + "chapter": 3, + "datetime": "2026-08-20T10:00:00Z", + "pos0": "/body/Doc[4]/Sec[2]", "page": 25, - "position": 0.125, - "notes": "Important section", - "highlighted_text": "Text to remember", - "time": "2026-02-08T10:00:00Z", - "created_at": "2026-02-08T10:00:00Z" + "text": "", + "type": "bookmark", + "percentage": 0.125 + } + ], + "highlights": [ + { + "datetime": "2026-08-20T10:05:00Z", + "pos0": "/body/Doc[4]/Sec[2]/text()[3]:0", + "pos1": "/body/Doc[4]/Sec[2]/text()[3]:42", + "text": "Text to remember", + "notes": "Why this matters", + "type": "highlight", + "color": "blue", + "dedup_key": "echo-key-from-server" } ] } @@ -53,15 +85,17 @@ This endpoint requires device authentication (not user JWT). Devices authenticat ```json { - "message": "Bookmarks synced successfully", - "synced_count": 1 + "sync_status": "ok", + "bookmarks_synced": 1, + "notes_synced": 0, + "highlights_synced": 1 } ``` ## Error Responses -| Code | Description | -| ---- | ---------------------------- | -| 401 | Device authentication failed | -| 400 | Invalid request data | -| 404 | Device not found | +| Code | Description | +| ---- | -------------------------------------------------- | +| 400 | Invalid request, or neither uuid nor SHA provided | +| 401 | Missing/invalid device token | +| 404 | Book not found by SHA-256 | diff --git a/docs/developer/api/sync/koreader-protocol.md b/docs/developer/api/sync/koreader-protocol.md index 5ab71e9..ee1d620 100644 --- a/docs/developer/api/sync/koreader-protocol.md +++ b/docs/developer/api/sync/koreader-protocol.md @@ -32,7 +32,7 @@ KOReader uses a custom JSON-based sync protocol. | books[].chapter | integer | No | Current chapter | | books[].epubcfi | string | No | EPUB CFI location | | books[].character | integer | No | Character offset | -| books[].bookmarks | array | No | Array of bookmarks/highlights | +| books[].bookmarks | array | No | Array of bookmarks/highlights (shape, color mapping, and echo/dedup rules: see [Sync Bookmarks](../koreader/sync_bookmarks.md)) | \* At least one of `uuid` or `sha256` should be present. The server resolves the book through the shared `BookResolver` with this priority: `uuid` → `sha256` →