All checks were successful
Test and deploy staging / staging (push) Successful in 18s
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>
50 lines
1.9 KiB
Python
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()
|