Files
cardpuller/main.py
T

93 lines
3.0 KiB
Python

import asyncio
import json
import secrets
from collections import Counter
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import db
import worker
from config import OUTPUTS, USERNAME, PASSWORD, PORT
from routes.decks import router as decks_router
from routes.cards import router as cards_router, public_router
from worker import safe_filename
security = HTTPBasic()
def auth(creds: HTTPBasicCredentials = Depends(security)):
ok = (
secrets.compare_digest(creds.username.encode(), USERNAME.encode())
and secrets.compare_digest(creds.password.encode(), PASSWORD.encode())
)
if not ok:
raise HTTPException(401, headers={"WWW-Authenticate": "Basic"})
@asynccontextmanager
async def lifespan(app: FastAPI):
db.init()
OUTPUTS.mkdir(exist_ok=True)
for d in OUTPUTS.iterdir():
if not d.is_dir() or not (d / "cards.txt").exists():
continue
slug = d.name
if db.get_deck(slug):
continue
card_names = [l for l in (d / "cards.txt").read_text().splitlines() if l]
status = "failed"
deck_name = slug.replace("_", " ").title()
if (d / "deck.tts.json").exists():
status = "complete"
try:
deck_name = json.loads((d / "deck.tts.json").read_text()).get("SaveName", deck_name)
except Exception:
pass
counts = Counter(card_names)
seen: Counter = Counter()
with db.conn() as c:
c.execute(
"INSERT OR IGNORE INTO decks (slug, deck_name, status, commander, price_usd, done, total) VALUES (?,?,?,?,0,?,?)",
(slug, deck_name, status, card_names[0] if card_names else None, len(card_names), len(card_names))
)
for i, name in enumerate(card_names):
seen[name] += 1
occ = seen[name]
total = counts[name]
base = safe_filename(name)
suffix = f"_{occ}" if total > 1 else ""
filename = f"{base}{suffix}.png"
exists = (d / filename).exists()
c.execute(
"INSERT INTO cards (deck_slug, name, position, filename, fetch_status) VALUES (?,?,?,?,?)",
(slug, name, i + 1, filename if exists else None, "done" if exists else "failed")
)
c.execute("INSERT INTO logs (deck_slug, line) VALUES (?,?)", (slug, "Loaded from disk."))
asyncio.create_task(worker.run())
yield
app = FastAPI(lifespan=lifespan)
app.include_router(decks_router, dependencies=[Depends(auth)])
app.include_router(cards_router, dependencies=[Depends(auth)])
app.include_router(public_router) # no auth — TTS app fetches card images directly
@app.get("/")
async def index(_=Depends(auth)):
return FileResponse("static/index.html")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=PORT)