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)