#!/usr/bin/env python3 """Search BGG for board game IDs using XML API v2.""" import subprocess import sys import time import xml.etree.ElementTree as ET # Read BGG key with open('/tmp/.bggkey') as f: bgg_key = f.read().strip() HEADER = f"Authorization: Bearer {bgg_key}" BASE_URL = "https://boardgamegeek.com/xmlapi2/search" # Games to search: (full_name, primary_search_word, fallback_search_word) GAMES = [ ("Concordia Special Edition", "Concordia", None), ("Altera", "Altera", None), ("Aeon's End: System Overload", "Aeon's", "Overload"), ("Entropy", "Entropy", None), ("NARCOS", "Narcos", None), ("Stonefall", "Stonefall", None), ("Capitals of Tactoria", "Capitals", "Tactoria"), ("A Life", "Life", None), ("The Council of Anderian", "Anderian", "Council"), ("Ancient Blood", "Ancient", "Blood"), ("Growing Season", "Growing", "Season"), ("Believe in me!", "Believe", None), ("Wilderfeast", "Wilderfeast", None), ("TATANKA", "Tatanka", None), ] def search_bgg(query_word): """Search BGG API and return list of (id, name, year) tuples.""" url = f"{BASE_URL}?query={query_word}&type=boardgame" result = subprocess.run( ["curl", "-s", "-H", HEADER, url], capture_output=True, text=True, timeout=30 ) if result.returncode != 0: print(f" curl error: {result.stderr}", file=sys.stderr) return [] xml_text = result.stdout.strip() if not xml_text: print(f" Empty response", file=sys.stderr) return [] try: root = ET.fromstring(xml_text) except ET.ParseError as e: print(f" XML parse error: {e}", file=sys.stderr) return [] items = [] for item in root.findall('item'): item_id = item.get('id', '') item_type = item.get('type', '') if item_type != 'boardgame': continue name_el = item.find('name') if name_el is not None: name_val = name_el.get('value', '') else: name_val = '' year_el = item.find('yearpublished') if year_el is not None: year_val = year_el.get('value', '') else: year_val = '' items.append((item_id, name_val, year_val)) return items def find_exact_match(items, full_name): """Find exact match (case-insensitive) in search results.""" target = full_name.lower().strip() for item_id, name, year in items: if name.lower().strip() == target: return item_id, name, year return None def find_best_match(items, full_name): """Find best match - first try exact, then first result.""" exact = find_exact_match(items, full_name) if exact: return exact if items: return items[0] return None def main(): results = [] for full_name, primary_word, fallback_word in GAMES: print(f"\nSearching: {full_name} (word: {primary_word})", file=sys.stderr) # Primary search items = search_bgg(primary_word) print(f" Got {len(items)} results", file=sys.stderr) for i, (item_id, name, year) in enumerate(items[:5]): print(f" [{i}] id={item_id} name='{name}' year={year}", file=sys.stderr) match = find_best_match(items, full_name) if match: item_id, name, year = match url = f"https://boardgamegeek.com/boardgame/{item_id}" results.append(f"{full_name}|{item_id}|{url}") print(f" MATCH: {item_id} - {name} ({year})", file=sys.stderr) elif fallback_word: # Try fallback time.sleep(3) print(f" No match, trying fallback: {fallback_word}", file=sys.stderr) items2 = search_bgg(fallback_word) print(f" Got {len(items2)} results", file=sys.stderr) for i, (item_id, name, year) in enumerate(items2[:5]): print(f" [{i}] id={item_id} name='{name}' year={year}", file=sys.stderr) match2 = find_best_match(items2, full_name) if match2: item_id, name, year = match2 url = f"https://boardgamegeek.com/boardgame/{item_id}" results.append(f"{full_name}|{item_id}|{url}") print(f" MATCH (fallback): {item_id} - {name} ({year})", file=sys.stderr) else: results.append(f"{full_name}|NOT_FOUND") print(f" NOT FOUND", file=sys.stderr) else: results.append(f"{full_name}|NOT_FOUND") print(f" NOT FOUND", file=sys.stderr) # Wait 3 seconds before next call if full_name != GAMES[-1][0]: time.sleep(3) print("\n=== RESULTS ===", file=sys.stderr) for r in results: print(r) if __name__ == "__main__": main()