| import io |
| import os |
|
|
| from fastapi import FastAPI, HTTPException, Query |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import StreamingResponse |
| import uvicorn |
|
|
| from config import logger, validate_web_env |
| from database import init_db |
| from scraper import ( |
| download_images_as_pdf_async, |
| download_images_as_zip_async, |
| fetch_chapter_images_api, |
| get_genres_list, |
| get_manga_chapters_api, |
| ) |
|
|
|
|
| validate_web_env() |
| init_db() |
|
|
| app = FastAPI(title="Utoon Web API", version="1.0.0") |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
|
|
| @app.get("/") |
| async def home(): |
| return {"status": "online"} |
|
|
|
|
| @app.get("/health") |
| async def health(): |
| return {"ok": True} |
|
|
|
|
| @app.get("/manga/chapters") |
| async def manga_chapters(url: str = Query(..., description="Utoon manga URL")): |
| chapters = await get_manga_chapters_api(url) |
| if not chapters: |
| raise HTTPException(status_code=404, detail="No chapters found for this URL") |
| return {"url": url, "count": len(chapters), "chapters": chapters} |
|
|
|
|
| @app.get("/genres") |
| async def genres(): |
| return {"genres": get_genres_list()} |
|
|
|
|
| @app.get("/chapter/download") |
| async def chapter_download( |
| chapter_id: str = Query(..., description="Chapter ID"), |
| manga_slug: str = Query("manga", description="Slug used in output filename"), |
| chapter_slug: str = Query("chapter", description="Chapter slug used in output filename"), |
| file_format: str = Query("pdf", pattern="^(pdf|zip)$"), |
| ): |
| images = await fetch_chapter_images_api(chapter_id) |
| if not images: |
| raise HTTPException(status_code=404, detail="Could not fetch chapter images") |
|
|
| try: |
| if file_format == "pdf": |
| content = await download_images_as_pdf_async(images, manga_slug, chapter_id) |
| filename = f"{manga_slug}_chapter-{chapter_slug}.pdf" |
| media_type = "application/pdf" |
| else: |
| content = await download_images_as_zip_async(images, manga_slug, chapter_id) |
| filename = f"{manga_slug}_chapter-{chapter_slug}.zip" |
| media_type = "application/zip" |
| except Exception as exc: |
| logger.error("Failed to build %s for chapter %s: %s", file_format, chapter_id, exc) |
| raise HTTPException(status_code=500, detail="Failed to generate chapter file") from exc |
|
|
| return StreamingResponse( |
| io.BytesIO(content), |
| media_type=media_type, |
| headers={"Content-Disposition": f'attachment; filename="{filename}"'}, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| port = int(os.getenv("PORT", "7860")) |
| uvicorn.run("app:app", host="0.0.0.0", port=port) |
|
|