diff --git a/backend/app/main.py b/backend/app/main.py index d4a130cc..516b59ea 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -31,6 +31,7 @@ orbs, afflictions, modifiers, + mods, achievements, badges, epochs, @@ -544,6 +545,7 @@ async def dispatch(self, request: Request, call_next): app.include_router(orbs.router) app.include_router(afflictions.router) app.include_router(modifiers.router) +app.include_router(mods.router) app.include_router(achievements.router) app.include_router(badges.router) app.include_router(epochs.router) diff --git a/backend/app/routers/mods.py b/backend/app/routers/mods.py new file mode 100644 index 00000000..fbdc53d0 --- /dev/null +++ b/backend/app/routers/mods.py @@ -0,0 +1,44 @@ +"""Mod asset directory. + +Curated list of community mods whose image repos supply entity art as the third +fallback after main and beta on the run and live pages. Each mod's images are +named by the in-game entity id (lowercased), so the frontend builds +`//.` for an id that resolves in neither the +main nor beta catalog, and shows modded card/relic/potion art with no per-mod +code. The directory lives in data/mods.json; only vetted repos go in. +""" + +import json +import os +from pathlib import Path + +from fastapi import APIRouter, Request +from slowapi import Limiter +from slowapi.util import get_remote_address + +router = APIRouter(prefix="/api/mods", tags=["Mods"]) +limiter = Limiter(key_func=get_remote_address) + +_DATA_DIR = Path( + os.environ.get("DATA_DIR", Path(__file__).resolve().parents[3] / "data") +) + + +def _load_mods() -> list[dict]: + """The vetted mod entries from data/mods.json, or [] if absent/unreadable.""" + path = _DATA_DIR / "mods.json" + if not path.exists(): + return [] + try: + with open(path, "r", encoding="utf-8") as f: + return json.load(f).get("mods", []) + except Exception: + return [] + + +@router.get("", tags=["Mods"]) +@limiter.limit("120/minute") +def list_mods(request: Request): + """The curated mod directory: each mod's repo art base and per-type + subpaths, so clients can fall back to modded entity art by id.""" + return {"mods": _load_mods()} diff --git a/data/mods.json b/data/mods.json new file mode 100644 index 00000000..d3041036 --- /dev/null +++ b/data/mods.json @@ -0,0 +1,18 @@ +{ + "_comment": "Curated directory of community mods whose asset repos supply entity art as the third fallback after main and beta. Each entry's images must be named by the in-game entity id, lowercased (e.g. relic DAMARU -> relics/damaru.png), matching the official convention. Only add vetted repos. The frontend builds //..", + "mods": [ + { + "key": "watcher", + "name": "Watcher", + "author": "lamali292", + "url": "https://github.com/lamali292/WatcherMod", + "raw_base": "https://raw.githubusercontent.com/lamali292/WatcherMod/main/Watcher/images", + "ext": "png", + "paths": { + "cards": "card_portraits", + "relics": "relics", + "potions": "potions" + } + } + ] +}