Download badge and boss sprite images locally during export
The seed export command now downloads badge images and boss sprites from remote URLs and stores them in frontend/public/, rewriting the JSON URLs to local paths. Sprites are namespaced by game version (e.g. /boss-sprites/red/brock.png) so each generation can have its own sprite style. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"""Seed runner — reads JSON files and upserts into the database."""
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import func, select
|
||||
@@ -403,11 +405,58 @@ async def _export_routes(session: AsyncSession, vg_data: dict):
|
||||
print(f"Routes: {exported} game files exported")
|
||||
|
||||
|
||||
FRONTEND_PUBLIC = Path(__file__).resolve().parents[4] / "frontend" / "public"
|
||||
|
||||
|
||||
def _slugify(name: str) -> str:
|
||||
"""Convert a name to a filename-safe slug: lowercase, hyphens, no special chars."""
|
||||
slug = name.lower().replace(" ", "-")
|
||||
slug = re.sub(r"[^a-z0-9-]", "", slug)
|
||||
return slug
|
||||
|
||||
|
||||
def _download_image(
|
||||
url: str,
|
||||
output_dir: Path,
|
||||
slug: str,
|
||||
downloaded: set[str],
|
||||
) -> str:
|
||||
"""Download an image to output_dir/slug.ext if not already downloaded.
|
||||
|
||||
Returns the local path (relative to frontend/public).
|
||||
"""
|
||||
url_ext = url.rsplit(".", 1)[-1].split("?")[0].lower()
|
||||
if url_ext in ("png", "jpg", "jpeg", "gif", "webp", "svg"):
|
||||
ext = f".{url_ext}"
|
||||
else:
|
||||
ext = ".png"
|
||||
|
||||
filename = f"{slug}{ext}"
|
||||
dest = output_dir / filename
|
||||
|
||||
if filename not in downloaded:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "nuzlocke-tracker/1.0"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
dest.write_bytes(resp.read())
|
||||
except (urllib.error.URLError, OSError) as exc:
|
||||
print(f" Warning: failed to download {url}: {exc}")
|
||||
return url
|
||||
downloaded.add(filename)
|
||||
print(f" Downloaded: {dest.relative_to(FRONTEND_PUBLIC)}")
|
||||
|
||||
return f"/{dest.relative_to(FRONTEND_PUBLIC)}"
|
||||
|
||||
|
||||
async def _export_bosses(session: AsyncSession, vg_data: dict):
|
||||
"""Export boss battles per version group."""
|
||||
vg_result = await session.execute(select(VersionGroup))
|
||||
slug_to_vg = {vg.slug: vg for vg in vg_result.scalars().all()}
|
||||
|
||||
badge_dir = FRONTEND_PUBLIC / "badges"
|
||||
downloaded_badges: set[str] = set()
|
||||
|
||||
exported = 0
|
||||
for vg_slug, vg_info in vg_data.items():
|
||||
vg = slug_to_vg.get(vg_slug)
|
||||
@@ -429,19 +478,37 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
|
||||
continue
|
||||
|
||||
first_game_slug = list(vg_info["games"].keys())[0]
|
||||
data = [
|
||||
{
|
||||
sprite_dir = FRONTEND_PUBLIC / "boss-sprites" / first_game_slug
|
||||
downloaded_sprites: set[str] = set()
|
||||
data = []
|
||||
for b in bosses:
|
||||
badge_image_url = b.badge_image_url
|
||||
sprite_url = b.sprite_url
|
||||
|
||||
if badge_image_url and b.badge_name:
|
||||
badge_slug = _slugify(b.badge_name)
|
||||
badge_image_url = _download_image(
|
||||
badge_image_url, badge_dir, badge_slug, downloaded_badges,
|
||||
)
|
||||
|
||||
if sprite_url:
|
||||
sprite_slug = _slugify(b.name)
|
||||
sprite_url = _download_image(
|
||||
sprite_url, sprite_dir, sprite_slug, downloaded_sprites,
|
||||
)
|
||||
|
||||
data.append({
|
||||
"name": b.name,
|
||||
"boss_type": b.boss_type,
|
||||
"specialty_type": b.specialty_type,
|
||||
"badge_name": b.badge_name,
|
||||
"badge_image_url": b.badge_image_url,
|
||||
"badge_image_url": badge_image_url,
|
||||
"level_cap": b.level_cap,
|
||||
"order": b.order,
|
||||
"after_route_name": b.after_route.name if b.after_route else None,
|
||||
"location": b.location,
|
||||
"section": b.section,
|
||||
"sprite_url": b.sprite_url,
|
||||
"sprite_url": sprite_url,
|
||||
"pokemon": [
|
||||
{
|
||||
"pokeapi_id": bp.pokemon.pokeapi_id,
|
||||
@@ -451,9 +518,7 @@ async def _export_bosses(session: AsyncSession, vg_data: dict):
|
||||
}
|
||||
for bp in sorted(b.pokemon, key=lambda p: p.order)
|
||||
],
|
||||
}
|
||||
for b in bosses
|
||||
]
|
||||
})
|
||||
|
||||
_write_json(f"{first_game_slug}-bosses.json", data)
|
||||
exported += 1
|
||||
|
||||
Reference in New Issue
Block a user