144 lines
4.8 KiB
Python
144 lines
4.8 KiB
Python
#!/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()
|