from fastapi import FastAPI, Request, Form, BackgroundTasks from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles from functools import partial import json, download, asyncio from typing import Optional import logging, os from logging.handlers import TimedRotatingFileHandler # Ensure log directory exists os.makedirs("/data/logs", exist_ok=True) # Setup timed rotating logger log_path = "/data/logs/syllabus.log" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # Remove any default handlers logger.handlers = [] # Set up TimedRotatingFileHandler handler = TimedRotatingFileHandler( filename=log_path, when="midnight", # Rotate at midnight interval=1, # Every 1 day backupCount=30, # Keep last 7 logs encoding="utf-8", utc=False # Use UTC for time reference ) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) # App setup app = FastAPI() app.mount("/data", StaticFiles(directory="/data"), name="data") templates = Jinja2Templates(directory="templates") loop = asyncio.get_event_loop() # Optional cache cached_data = None # Middleware @app.middleware("http") async def log_requests(request: Request, call_next): try: response = await call_next(request) except Exception as e: logger.exception(f"EXCEPTION: {request.method} {request.url} - {str(e)}") return JSONResponse( status_code=500, content={"detail": "Internal Server Error"}, ) logger.info( f"request_client={request.client.host}:{request.client.port}, " f"request_method={request.method}, request_url={request.url}, " f"status_code={response.status_code}" ) return response # api @app.post("/ebook/download", description="Download an ebook via a url.") async def ebookDownload( background_tasks: BackgroundTasks, url: str = Form(...), author: str = Form(...) ): try: background_tasks.add_task(download.ebook,url,author) # download.dropout.show(show,season,episode) return JSONResponse(status_code=200, content={"status": "success", "message": "Book downloaded."}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.get("/dropout/update") async def dropoutUpdate(force: bool = False): global cached_data try: download.dropout.series(force) with open('/data/dropout.json') as f: cached_data = json.load(f) return JSONResponse(status_code=200, content={"status": "success", "message": "Series grab complete."}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.get("/dropout/series") async def dropoutSeries(): global cached_data if cached_data is None: await dropoutUpdate() return JSONResponse(content=cached_data) return JSONResponse(content={"error": "File not found"}, status_code=404) @app.post("/dropout/download", description="Download an entire season from episode 1. Ignores behind the scenes and trailers.") async def dropoutDownload( # request: Request, background_tasks: BackgroundTasks, show: str = Form(...), season: int = Form(...) ): # data = await request.json() # logger.info(f"Parsed JSON Body: {json.dumps(data, indent=2)}") try: background_tasks.add_task(download.dropout.show,show,season,None) # download.dropout.show(show,season,episode) return JSONResponse(status_code=200, content={"status": "success", "message": "Series downloaded."}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.post("/dropout/download/specials", description="Downloads a seasons behind the scenes and trailers, ignores main episodes.") async def dropoutDownload( background_tasks: BackgroundTasks, show: str = Form(...), season: int = Form(...), episode: Optional[int] = Form(None) ): try: background_tasks.add_task(download.dropout.specials,show,season,episode) # download.dropout.show(show,season,episode) return JSONResponse(status_code=200, content={"status": "success", "message": "Series downloaded."}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.post("/ydl") async def ydl(background_tasks: BackgroundTasks, url: str = Form(...), location: str = Form(...)): try: background_tasks.add_task(download.youtube.ydl, url, location) # download.youtube.ydl(url,location) # grab.thumbnail(ydl,url,location) return JSONResponse(status_code=200, content={"status": "success", "message": "Video download completed."}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) #web ui @app.get("/", include_in_schema=False, response_class=HTMLResponse) async def index(request: Request): global cached_data try: if cached_data is None: await dropoutUpdate() return templates.TemplateResponse("index.html", {"request": request, "data": cached_data}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.get("/show/{show}", include_in_schema=False, response_class=HTMLResponse) async def index(request: Request, show = str): try: global cached_data if cached_data is None: await dropoutUpdate() try: for item in cached_data: if show == item['LINK']: if "SEASONS" not in item: item['SEASONS'] = download.grab.season(item['URL']) return templates.TemplateResponse("show.html", {"request": request, "show": item}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.get("/ydl", include_in_schema=False) async def webpage(request: Request): try: return templates.TemplateResponse("ydl.html", {"request": request}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)}) @app.get("/dropout", include_in_schema=False) async def webpage(request: Request): global cached_data if cached_data is None: await dropoutUpdate() try: return templates.TemplateResponse("dropout.html", {"request": request, "data": cached_data}) except Exception as e: return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})