#!/usr/bin/env python3 """Parse MPMB all_WotC_published.js into structured JSON files""" import re, json, os INPUT = os.environ.get("MPMB_INPUT", "/home/nilsb/Downloads/all_WotC_published.js") # Output path (the app reads from src/data/srd). Override with MPMB_OUT for a dry run. OUT_DIR = os.environ.get("MPMB_OUT", "/home/nilsb/Documents/Projects/TTRPG_manager/src/data/srd") def parse_mpmb(filepath): with open(filepath, 'r', encoding='utf-8') as f: content = f.read() # Find all list assignments: ListName["key"] = { ... }; # Strategy: extract each list type by finding assignment patterns lists = {} list_names = [ "SourceList", "MagicItemsList", "SpellsList", "ClassList", "RaceList", "FeatsList", "BackgroundList", "BackgroundFeatureList", "CreatureList", "WeaponsList", "AmmoList", "GearList", "CompanionList" ] for list_name in list_names: entries = {} # Pattern: ListName["key"] = { ... }; # Match multi-line objects by counting braces pattern = re.escape(list_name) + r'\["([^"]+)"\]\s*=\s*\{' for match in re.finditer(pattern, content): key = match.group(1) start = match.end() - 1 # position of opening { # Find matching closing brace depth = 0 i = start in_string = False string_char = None while i < len(content): c = content[i] if in_string: if c == '\\': i += 2 continue if c == string_char: in_string = False i += 1 continue if c in ('"', "'"): in_string = True string_char = c elif c == '{': depth += 1 elif c == '}': depth -= 1 if depth == 0: obj_str = content[start:i+1] break elif c == '/' and i+1 < len(content) and content[i+1] in ('/', '*'): # Skip comments if content[i+1] == '/': while i < len(content) and content[i] != '\n': i += 1 continue elif content[i+1] == '*': i += 2 while i < len(content) and not (content[i] == '*' and content[i+1] == '/'): i += 1 i += 2 continue i += 1 else: continue # no closing brace found # Store raw object text (can't safely eval, but we can extract key fields) entries[key] = obj_str if entries: lists[list_name] = entries print(f" {list_name}: {len(entries)} entries") return lists def safe_extract_fields(obj_str): """Extract common fields from a JS object literal string.""" fields = {} patterns = { 'name': r'name\s*:\s*["\']([^"\']+)["\']', 'type': r'type\s*:\s*["\']([^"\']+)["\']', 'rarity': r'rarity\s*:\s*["\']([^"\']+)["\']', 'source': r'source\s*:\s*(\[\[.*?\]\])', 'description': r'description\s*:\s*["\']([^"]+)["\']', 'weight': r'weight\s*:\s*(-?\d+)', 'prerequisite': r'prerequisite\s*:\s*["\']([^"\']+)["\']', # --- spell fields (additive; only present on SpellsList entries) --- 'level': r'\blevel\s*:\s*(\d+)', 'school': r'\bschool\s*:\s*["\']([^"\']+)["\']', 'time': r'\btime\s*:\s*["\']([^"\']+)["\']', 'range': r'\brange\s*:\s*["\']([^"\']+)["\']', 'components': r'\bcomponents\s*:\s*["\']([^"\']+)["\']', 'duration': r'\bduration\s*:\s*["\']([^"\']+)["\']', 'save': r'\bsave\s*:\s*["\']([^"\']+)["\']', 'ritual': r'\britual\s*:\s*(true|false)', 'classes': r'\bclasses\s*:\s*(\[[^\]]*\])', } for field, pattern in patterns.items(): m = re.search(pattern, obj_str) if m: val = m.group(1) if field == 'weight': val = int(val) elif field == 'level': val = int(val) elif field == 'ritual': val = (val == 'true') elif field == 'classes': # ["cleric","warlock"] -> list of class names val = re.findall(r'["\']([^"\']+)["\']', val) elif field == 'source': # Parse [[SourceName, page], [SourceName2, page2]] sources = re.findall(r'\["([^"]+)",?\s*(\d+)?\]', val) val = [{"source": s[0], "page": int(s[1]) if s[1] else None} for s in sources] fields[field] = val return fields if __name__ == "__main__": print("Parsing MPMB data file...") lists = parse_mpmb(INPUT) os.makedirs(OUT_DIR, exist_ok=True) # Save raw entry counts for name, entries in lists.items(): # Extract key fields for each entry extracted = {} for key, obj_str in entries.items(): extracted[key] = safe_extract_fields(obj_str) outname = f"mpmb-{name.lower().replace('list','')}.json" outpath = os.path.join(OUT_DIR, outname) with open(outpath, 'w') as f: json.dump(extracted, f, indent=2) print(f" Saved {outname}: {len(extracted)} entries") print("\nDone! Files saved to", OUT_DIR)