Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
orbs,
afflictions,
modifiers,
mods,
achievements,
badges,
epochs,
Expand Down Expand Up @@ -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)
Expand Down
44 changes: 44 additions & 0 deletions backend/app/routers/mods.py
Original file line number Diff line number Diff line change
@@ -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
`<raw_base>/<paths[type]>/<id>.<ext>` 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()}
18 changes: 18 additions & 0 deletions data/mods.json
Original file line number Diff line number Diff line change
@@ -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 <raw_base>/<paths[type]>/<id>.<ext>.",
"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"
}
}
]
}
Loading