"""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 `:` 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'
(.*?)\s*
(.*?)
', 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()