From bf2de7c07d60c74144f94f390d71caf7f24aa365 Mon Sep 17 00:00:00 2001 From: Mattias Svensson Date: Sun, 28 Jun 2026 10:55:32 +0200 Subject: [PATCH] nextory: add want-to-read download + stop leaking login tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a want-to-read flow so the whole 'Saved for later' list can be downloaded at once: a URL containing 'want-to-read' resolves the user's want_to_read product list into a Series of book ids, skipping entries with no epub format (audiobook-only). Mirrors the equivalent feature in audiobook-dl. Also remove three leftover rich.print() debug calls in login() that printed the full session/profile/authorize responses — including the login and profile tokens — to stdout on every run, and drop a duplicated header update. Co-Authored-By: Claude Fable 5 --- grawlix/sources/nextory.py | 47 ++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/grawlix/sources/nextory.py b/grawlix/sources/nextory.py index 4c88f28..257f91a 100644 --- a/grawlix/sources/nextory.py +++ b/grawlix/sources/nextory.py @@ -47,7 +47,6 @@ class Nextory(Source): }, ) session_response = session_response.json() - rich.print(session_response) login_token = session_response["login_token"] country = session_response["country"] self._client.headers.update( @@ -62,7 +61,6 @@ class Nextory(Source): "https://api.nextory.com/user/v1/me/profiles", ) profiles_response = profiles_response.json() - rich.print(profiles_response) profile = profiles_response["profiles"][0] login_key = profile["login_key"] authorize_response = await self._client.post( @@ -72,10 +70,8 @@ class Nextory(Source): } ) authorize_response = authorize_response.json() - rich.print(authorize_response) profile_token = authorize_response["profile_token"] self._client.headers.update({"X-Profile-Token": profile_token}) - self._client.headers.update({"X-Profile-Token": profile_token}) @staticmethod @@ -100,6 +96,8 @@ class Nextory(Source): async def download(self, url: str) -> Result: + if "want-to-read" in url.lower() or "saved-for-later" in url.lower(): + return await self._download_want_to_read() url_id = self._extract_id_from_url(url) if "serier" in url: return await self._download_series(url_id) @@ -111,6 +109,47 @@ class Nextory(Source): return await self._download_book(book_id) + async def _download_want_to_read(self) -> Series: + """ + Download every ebook saved in the user's want-to-read ("Saved for + later") list. Entries without an epub format (e.g. audiobook-only) are + skipped, since grawlix downloads ebooks. + + :returns: Series of ebook ids + """ + lists_response = await self._client.get( + "https://api.nextory.com/library/v1/me/product_lists", + params = { "page": 0, "per": 50 }, + ) + want_to_read_id = None + for product_list in lists_response.json()["product_lists"]: + if product_list["type"] == "want_to_read": + want_to_read_id = product_list["id"] + break + if want_to_read_id is None: + raise InvalidUrl + book_ids = [] + page = 0 + while True: + response = await self._client.get( + "https://api.nextory.com/library/v1/me/product_lists/want_to_read/products", + params = { "page": page, "per": 1000, "id": want_to_read_id }, + ) + products = response.json().get("products", []) + if not products: + break + for product in products: + if any(f.get("type") == "epub" for f in product.get("formats", [])): + book_ids.append(product["id"]) + if len(products) < 1000: + break + page += 1 + return Series( + title = "Nextory - Saved for later", + book_ids = book_ids, + ) + + async def _download_series(self, series_id: str) -> Series: """ Download series from Nextory