From ca4697057cf992b340f9b7bae7ae4c84def12c00 Mon Sep 17 00:00:00 2001 From: AaronBenDaniel <144371000+AaronBenDaniel@users.noreply.github.com> Date: Thu, 7 Nov 2024 03:37:57 -0500 Subject: [PATCH] feat: Paste Links, Deprecate IDs (#17 - @AaronBenDaniel) * deprecate Story IDs, require full URLs * added FRONT-END ONLY support for part and list URLs * add backend support for part IDs * added backend support for lists * Support enums * Simplify and remove List support * Update frontend * Frontend: Revert dialog changes * Remove List support --------- Co-authored-by: TheOnlyWayUp --- src/api/src/create_book.py | 18 ++++ src/api/src/main.py | 48 ++++++---- src/frontend/src/routes/+page.svelte | 89 +++++++++++-------- .../src/routes/download/list/+page.svelte | 0 .../src/routes/download/story/+page.svelte | 0 5 files changed, 99 insertions(+), 56 deletions(-) create mode 100644 src/frontend/src/routes/download/list/+page.svelte create mode 100644 src/frontend/src/routes/download/story/+page.svelte diff --git a/src/api/src/create_book.py b/src/api/src/create_book.py index d0459f0..1d8d75d 100644 --- a/src/api/src/create_book.py +++ b/src/api/src/create_book.py @@ -82,6 +82,24 @@ def slugify(value, allow_unicode=False) -> str: # --- API Calls --- # +@backoff.on_exception(backoff.expo, ClientResponseError, max_time=15) +async def fetch_story_id(part_id: int, cookies: Optional[dict] = None) -> int: + """Return a Story ID from a Part ID.""" + async with ( + CachedSession(headers=headers, cache=cache) + if not cookies + else ClientSession(headers=headers, cookies=cookies) + ) as session: # Don't cache requests with Cookies. + async with session.get( + f"https://www.wattpad.com/api/v3/story_parts/{part_id}?fields=groupId" + ) as response: + response.raise_for_status() + + body = await response.json() + + return body["groupId"] + + @backoff.on_exception(backoff.expo, ClientResponseError, max_time=15) async def retrieve_story(story_id: int, cookies: Optional[dict] = None) -> dict: """Taking a story_id, return its information from the Wattpad API.""" diff --git a/src/api/src/main.py b/src/api/src/main.py index 45c6883..d9242f5 100644 --- a/src/api/src/main.py +++ b/src/api/src/main.py @@ -1,6 +1,7 @@ from typing import Optional from pathlib import Path -from fastapi import FastAPI, HTTPException +from enum import Enum +from fastapi import FastAPI from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse from ebooklib import epub from create_book import ( @@ -10,6 +11,7 @@ from create_book import ( add_chapters, slugify, wp_get_cookies, + fetch_story_id, ) import tempfile from io import BytesIO @@ -18,19 +20,31 @@ from fastapi.staticfiles import StaticFiles app = FastAPI() BUILD_PATH = Path(__file__).parent / "build" +headers = { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" +} + + +class DownloadMode(Enum): + story = "story" + part = "part" + collection = "collection" + @app.get("/") def home(): return FileResponse(BUILD_PATH / "index.html") -@app.get("/download/{story_id}") -async def download_book( - story_id: int, +@app.get("/download/{download_id}") +async def handle_download( + download_id: int, download_images: bool = False, + mode: DownloadMode = DownloadMode.story, username: Optional[str] = None, password: Optional[str] = None, ): + if username and not password or password and not username: return HTMLResponse( status_code=422, @@ -49,25 +63,21 @@ async def download_book( else: cookies = None - data = await retrieve_story(story_id, cookies=cookies) + match mode: + case DownloadMode.story: + story_id = download_id + case DownloadMode.part: + story_id = await fetch_story_id(download_id, cookies) + + metadata = await retrieve_story(story_id, cookies) book = epub.EpubBook() - try: - set_metadata(book, data) - except KeyError: - return HTMLResponse( - status_code=404, - content='Story not found. Check the ID - Support is available on the Discord', - ) + set_metadata(book, metadata) + await set_cover(book, metadata, cookies=cookies) - await set_cover(book, data, cookies=cookies) - # print("Metadata Downloaded") - - # Chapters are downloaded async for title in add_chapters( - book, data, download_images=download_images, cookies=cookies + book, metadata, download_images=download_images, cookies=cookies ): - # print(f"Part ({title}) downloaded") ... # Book is compiled @@ -85,7 +95,7 @@ async def download_book( BytesIO(book_data), media_type="application/epub+zip", headers={ - "Content-Disposition": f'attachment; filename="{slugify(data["title"])}_{story_id}_{"images" if download_images else ""}.epub"' # Thanks https://stackoverflow.com/a/72729058 + "Content-Disposition": f'attachment; filename="{slugify(metadata["title"])}_{story_id}_{"images" if download_images else ""}.epub"' # Thanks https://stackoverflow.com/a/72729058 }, ) diff --git a/src/frontend/src/routes/+page.svelte b/src/frontend/src/routes/+page.svelte index 10388e3..452b2dd 100644 --- a/src/frontend/src/routes/+page.svelte +++ b/src/frontend/src/routes/+page.svelte @@ -1,52 +1,66 @@ @@ -68,6 +82,7 @@