forked from akai/readest
6b403d019e
* 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>
51 lines
1.8 KiB
Python
51 lines
1.8 KiB
Python
__license__ = 'AGPL v3'
|
|
__copyright__ = '2026, Bilingify LLC'
|
|
|
|
from calibre.utils.config import JSONConfig
|
|
from qt.core import QCheckBox, QLabel, QLineEdit, QVBoxLayout, QWidget
|
|
|
|
from calibre_plugins.readest.api import DEFAULT_API_BASE, DEFAULT_SUPABASE_URL
|
|
|
|
prefs = JSONConfig('plugins/readest')
|
|
|
|
prefs.defaults['api_base'] = DEFAULT_API_BASE
|
|
prefs.defaults['supabase_url'] = DEFAULT_SUPABASE_URL
|
|
prefs.defaults['tokens'] = None # {access_token, refresh_token, expires_at, expires_in}
|
|
prefs.defaults['user_email'] = None
|
|
prefs.defaults['include_custom_columns'] = True
|
|
|
|
|
|
def save_tokens(tokens):
|
|
prefs['tokens'] = tokens
|
|
if tokens is None:
|
|
prefs['user_email'] = None
|
|
|
|
|
|
class ConfigWidget(QWidget):
|
|
def __init__(self):
|
|
QWidget.__init__(self)
|
|
layout = QVBoxLayout()
|
|
self.setLayout(layout)
|
|
|
|
account = prefs['user_email']
|
|
status = ('Logged in as %s.' % account) if account else 'Not logged in.'
|
|
layout.addWidget(QLabel(status + ' Use the Readest toolbar menu to log in or out.'))
|
|
|
|
self.custom_columns_checkbox = QCheckBox('Include custom columns in pushed metadata')
|
|
self.custom_columns_checkbox.setChecked(bool(prefs['include_custom_columns']))
|
|
layout.addWidget(self.custom_columns_checkbox)
|
|
|
|
layout.addWidget(QLabel('API server:'))
|
|
self.api_base_edit = QLineEdit(self)
|
|
self.api_base_edit.setText(prefs['api_base'])
|
|
self.api_base_edit.setToolTip(
|
|
'Only change this if you run a self-hosted Readest server.'
|
|
)
|
|
layout.addWidget(self.api_base_edit)
|
|
|
|
layout.addStretch()
|
|
|
|
def save_settings(self):
|
|
prefs['include_custom_columns'] = self.custom_columns_checkbox.isChecked()
|
|
prefs['api_base'] = self.api_base_edit.text().strip() or DEFAULT_API_BASE
|