Files
personal-website/scripts/build-cod-catalog.py
Stan Runge a10585a4d2
All checks were successful
Test and deploy staging / staging (push) Successful in 18s
add game tracker with owned pokemon data and call of duty mastery
Fold catchemall into /projects/game-tracker (old URL redirects) and add
Call of Duty tracking for Black Ops through WWII.

- Pokémon catalog is now owned: locations, encounters and per-game
  evolution methods are built from the pinned PokéAPI snapshot and seeded.
- Pokémon you can evolve or breed in a game count as obtainable; mythicals
  without another route are marked event-only.
- Detail dialog with evolution chain and where to find it, location pages
  with admin notes and pictures, and admin corrections that survive reseeds.
- Call of Duty weapons, milestones and Steam achievements per game, with
  derived Diamond / Dark Matter / Solar / Black Sky / Chrome tiers.
- New migration (db:migrate:game-tracker), seeds (db:seed, db:seed:cod) and
  deploy preflight checks for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:36:18 +00:00

50 lines
1.9 KiB
Python

"""Refresh the achievement lists in data/call-of-duty.json from Steam Community.
Weapons, classes and mastery rules in that file are hand-maintained; this only
replaces each game's `achievements` array with the public global achievement
pages of its Steam apps. Keys are `<app id>:<slug>` so progress survives renames
of descriptions or icons.
"""
import html
import json
from pathlib import Path
import re
import urllib.request
TARGET = Path(__file__).resolve().parent.parent / 'data/call-of-duty.json'
ROW = re.compile(r'<div class="achieveRow.*?<img src="([^"]+)".*?<h3>(.*?)</h3>\s*<h5>(.*?)</h5>', re.S)
def slug(text):
return re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-')
def achievements(app):
request = urllib.request.Request(f"https://steamcommunity.com/stats/{app['id']}/achievements/?l=english",
headers={'User-Agent': 'stanrunge.dev game-tracker catalog builder'})
with urllib.request.urlopen(request, timeout=60) as response:
page = response.read().decode('utf-8')
rows, seen = [], {}
for icon, name, description in ROW.findall(page):
name, description = html.unescape(name).strip(), html.unescape(description).strip()
key = f"{app['id']}:{slug(name)}"
seen[key] = seen.get(key, 0) + 1
if seen[key] > 1:
key += f'-{seen[key]}'
rows.append(dict(key=key, category=app['label'], name=name, description=description or None, icon=icon))
if not rows:
raise RuntimeError(f"No achievements parsed for Steam app {app['id']}")
return rows
def main():
catalog = json.loads(TARGET.read_text())
for game in catalog['games']:
game['achievements'] = [row for app in game['steamApps'] for row in achievements(app)]
print(f"{game['shortName']}: {len(game['achievements'])} achievements")
TARGET.write_text(json.dumps(catalog, ensure_ascii=False, indent='\t') + '\n')
if __name__ == '__main__':
main()