Files
readest/apps/readest-calibre-plugin/README.md
T
Huang Xin 6b403d019e feat(calibre): add Readest calibre plugin to push books and metadata (#4918)
* feat(calibre): add Readest calibre plugin to push books and metadata (#4863)

Add apps/readest-calibre-plugin, a calibre GUI plugin that uploads
selected books with their metadata into the user's Readest cloud
library, modeled on BookFusion's open-source plugin.

- Selective manual push from the calibre toolbar with per-book status
  (uploaded / updated / up to date / failed) and quota handling
- Books are content-addressed with the same partial MD5 as the apps,
  so re-pushing updates the existing entry instead of duplicating;
  metadata edits re-push without re-uploading the file
- Metadata mapping includes series, tags, identifiers and optional
  calibre custom columns; carries over server-side fields (progress,
  reading status, grouping, cover) that POST /sync would null out
- Auth mirrors readest.koplugin and the desktop app: email/password
  plus browser OAuth (Google/Apple/GitHub/Discord) through a localhost
  callback server with the fragment-to-query relay
- Pure-logic modules (api.py, wire.py, oauth.py) are calibre-free and
  covered by 56 unit tests (make test); make zip builds the plugin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(release): package readest-calibre-plugin in releases

Mirror the KOReader plugin packaging: a build-calibre-plugin job stamps
PLUGIN_VERSION in __init__.py with the release version from
apps/readest-app/package.json, builds the zip via make, and uploads
Readest-<version>.calibre-plugin.zip to the GitHub release. The version
committed in git stays a development placeholder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(agent): add calibre plugin project memory

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(calibre): embed metadata in OPF and dedupe by calibre uuid

Rework book identity so metadata can be embedded into the uploaded file
without creating duplicates, as requested in review:

- Embed calibre metadata (including custom columns) into a temporary
  copy of the book file at upload time via calibre's set_metadata; the
  library file is never modified
- Dedupe by the calibre book uuid carried in the entry's metadata
  identifier, which survives file-byte changes, with a live-row
  preference when both hash and uuid match rows
- Detect file content changes via calibreSourceHash, the raw library
  file fingerprint stored in the pushed metadata, so detection works
  from any machine; v1 rows fall back to book_hash which equals the
  raw hash for them
- A changed file now replaces the old entry in one sync push (new row
  with carried-over reading status, grouping, progress and created
  date, plus a tombstone for the old row) and deletes the old cloud
  files to reclaim quota
- Metadata-only edits still update the library entry without
  re-uploading the file

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(calibre): set copyright holder to Bilingify LLC

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:40:16 +02:00

99 lines
4.4 KiB
Markdown

# Readest calibre plugin
A [calibre](https://calibre-ebook.com) GUI plugin that pushes selected books —
with their metadata — into your [Readest](https://readest.com) cloud library.
Re-pushing a book updates its existing entry instead of creating a duplicate,
so you can edit metadata in calibre and re-send it any time.
Implements [readest/readest#4863](https://github.com/readest/readest/issues/4863).
## Features
- **Selective, manual push**: select books in calibre, click *Readest* in the
toolbar. Nothing syncs in the background.
- **Metadata included and embedded**: title, authors, series, tags,
description, publisher, language, identifiers — and optionally calibre
custom columns. Metadata is written both to the Readest library entry
(custom columns under `customColumns`) and into the uploaded file's OPF
(calibre's own embedding, so custom columns travel as
`calibre:user_metadata`). Your calibre library files are never modified —
embedding happens on a temporary copy.
- **Update on re-push**: books already in your Readest library are recognized
by their calibre uuid and only changed entries are rewritten; unchanged
books are skipped, and a changed file replaces the old entry instead of
duplicating it. Reading progress, grouping and reading status in Readest
are preserved.
- **Per-book status report**: uploaded / updated / up to date / failed, with a
storage-quota check (the push stops cleanly when your quota is exhausted).
- **Login like the apps**: email + password, or browser sign-in with Google,
Apple, GitHub or Discord (OAuth via a temporary localhost callback, the same
flow the desktop app uses).
## Install
Download `Readest-<version>.calibre-plugin.zip` from the
[latest release](https://github.com/readest/readest/releases/latest), or build
it yourself:
```sh
make zip # builds dist/Readest-<version>.calibre-plugin.zip
calibre-customize -a dist/Readest-*.calibre-plugin.zip # or: make install
```
Or in calibre: *Preferences → Plugins → Load plugin from file*, then restart
calibre and add the *Readest* button to the main toolbar if it is not visible.
Release zips are versioned from `apps/readest-app/package.json` by the release
workflow, which stamps `PLUGIN_VERSION` in `__init__.py` before zipping; the
version committed in git is a development placeholder.
## Usage
1. Click the *Readest* toolbar button menu → *Log in to Readest…*
2. Select the books to push (any number).
3. Click the *Readest* button (or menu → *Push selected books to Readest*).
For each book the best Readest-supported format is pushed, preferring
`EPUB > PDF > AZW3 > MOBI > AZW > FB2 > FBZ > CBZ > TXT > MD`.
## How updates and duplicates work
At upload time the plugin embeds your current calibre metadata (including
custom columns) into a temporary copy of the book file, so the copy in your
Readest library is self-describing. Pushed books are then tracked by two keys:
- the **calibre book uuid**, carried in the entry's metadata
(`urn:uuid:...`) — this recognizes "this calibre book is already in
Readest" across pushes, even when the file bytes change;
- a **fingerprint of the raw calibre file** (`calibreSourceHash`), which
detects whether the file itself changed since the last push — no local
state, so it works from any machine.
Re-push behavior:
- **Nothing changed** → skipped.
- **Metadata edited** → the Readest library entry is updated in place; the
file is not re-uploaded (its embedded OPF keeps the metadata from its
upload time).
- **File changed** (e.g. re-converted) → the new file is uploaded with fresh
embedded metadata and *replaces* the old entry: reading status, grouping,
progress and the library date carry over, the old entry is removed and its
cloud files are deleted. Notes and reading positions re-attach when
title/authors are unchanged (Readest matches book versions by metadata
identity).
- Books pushed by older plugin versions (or dragged into Readest manually)
are recognized too: their entry hash doubles as the raw-file fingerprint.
## Development
Pure-logic modules (`api.py`, `wire.py`, `oauth.py`) have no calibre or Qt
dependencies and are covered by unit tests:
```sh
make test # python3 -m unittest discover -s tests
```
The wire protocol mirrors what the Readest apps and `readest.koplugin` use:
Supabase auth (`/auth/v1`), `GET/POST /api/sync` for library rows, and
`POST /api/storage/upload` + presigned PUT for file blobs.