Files
readest/apps/readest-calibre-plugin
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
..

Readest calibre plugin

A calibre GUI plugin that pushes selected books — with their metadata — into your Readest 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.

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, or build it yourself:

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:

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.