diff --git a/main.py b/main.py index 4d5c52a..9b70385 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ import db import sftp as sftp_module import sync import uploader -from uploader import delete_book, fetch_all_books, find_duplicate_groups +from uploader import CalibreClient, delete_book, fetch_all_books, find_duplicate_groups logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s — %(message)s") log = logging.getLogger(__name__) @@ -222,13 +222,15 @@ def _run_dedup(): try: cfg = config.load() log.info("Dedup: fetching all books ...") + client = CalibreClient(cfg.calibre) + client._ensure_auth() books = fetch_all_books(cfg.calibre) groups = find_duplicate_groups(books) to_delete = [b for group in groups for b in sorted(group, key=lambda x: x.get("id", 0))[1:]] _dedup_state.update({"total": len(to_delete), "deleted": 0, "failed": 0}) log.info("Dedup: %d duplicate(s) to delete across %d group(s)", len(to_delete), len(groups)) for book in to_delete: - ok, msg = delete_book(cfg.calibre, book["id"]) + ok, msg = delete_book(cfg.calibre, book["id"], client) if ok: _dedup_state["deleted"] += 1 else: diff --git a/uploader.py b/uploader.py index fdf1463..ad6491d 100644 --- a/uploader.py +++ b/uploader.py @@ -174,7 +174,10 @@ def fetch_all_books(cfg: CalibreConfig) -> list[dict]: data = resp.json() # Calibre-Web uses DataTables format: "data"/"recordsTotal", older versions use "rows"/"total_count" rows = data.get("rows") or data.get("data") or [] - total = data.get("total") or data.get("totalNotFiltered") or 0 + total = ( + data.get("recordsTotal") or data.get("total_count") or + data.get("total") or data.get("totalNotFiltered") or 0 + ) all_books.extend(rows) log.info("Books fetched: %d / %d", len(all_books), total) if not rows or len(all_books) >= total: @@ -183,13 +186,23 @@ def fetch_all_books(cfg: CalibreConfig) -> list[dict]: return all_books -def delete_book(cfg: CalibreConfig, book_id: int) -> tuple[bool, str]: - """Delete a book from Calibre-Web by ID.""" - client = CalibreClient(cfg) - client._ensure_auth() +def delete_book(cfg: CalibreConfig, book_id: int, client: "CalibreClient | None" = None) -> tuple[bool, str]: + """Delete a book from Calibre-Web by ID. Pass a pre-authenticated client to avoid re-auth overhead.""" + if client is None: + client = CalibreClient(cfg) + client._ensure_auth() + csrf = client._upload_csrf + if not csrf: + # Try to fetch a CSRF token from the book detail page + try: + page = client._session.get(f"{cfg.url}/book/{book_id}", timeout=15) + csrf = _extract_csrf(page.text) + client._upload_csrf = csrf + except Exception: + pass resp = client._session.post( f"{cfg.url}/delete/{book_id}", - data={"csrf_token": client._upload_csrf} if client._upload_csrf else {}, + data={"csrf_token": csrf} if csrf else {}, timeout=30, ) if resp.ok: