import subprocess, sys, time, xml.etree.ElementTree as ET with open('/tmp/.bggkey') as f: bgg_key = f.read().strip() HEADER = 'Authorization: Bearer *** + bgg_key BASE_URL = 'https://boardgamegeek.com/xmlapi2/search' def search_bgg(query_word): url = 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: return [] xml_text = result.stdout.strip() if not xml_text: return [] try: root = ET.fromstring(xml_text) except ET.ParseError: return [] items = [] for item in root.findall('item'): if item.get('type', '') != 'boardgame': continue name_el = item.find('name') name_val = name_el.get('value', '') if name_el is not None else '' year_el = item.find('yearpublished') year_val = year_el.get('value', '') if year_el is not None else '' items.append((item.get('id', ''), name_val, year_val)) return items def find_exact(items, full_name): 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_containing(items, substring): sub = substring.lower() for item_id, name, year in items: if sub in name.lower(): return (item_id, name, year) return None def find_best(items, full_name, alt_names=None): m = find_exact(items, full_name) if m: return m if alt_names: for alt in alt_names: m = find_exact(items, alt) if m: return m m = find_containing(items, full_name) if m: return m return None GAMES = [ ('Concordia Special Edition', 'Concordia', None, ['Concordia: Special Edition']), ('Altera', 'Altera', None, None), ("Aeon's End: System Overload", "Aeon's", 'Overload', ["Aeon's End: System Overload"]), ('Entropy', 'Entropy', None, None), ('NARCOS', 'Narcos', None, None), ('Stonefall', 'Stonefall', None, None), ('Capitals of Tactoria', 'Capitals', 'Tactoria', None), ('A Life', 'Life', None, None), ('The Council of Anderian', 'Anderian', 'Council', None), ('Ancient Blood', 'Ancient', 'Blood', ['Ancient Blood: The Order of Vampire Hunters']), ('Growing Season', 'Growing', 'Season', None), ('Believe in me!', 'Believe', None, ['Believe in Me! (Please)']), ('Wilderfeast', 'Wilderfeast', 'wilder', None), ('TATANKA', 'Tatanka', None, None), ] results = [] for full_name, primary_word, fallback_word, alt_names in GAMES: print('\nSearching: ' + full_name + ' (word: ' + primary_word + ')', file=sys.stderr) items = search_bgg(primary_word) print(' Got ' + str(len(items)) + ' results', file=sys.stderr) for i, (item_id, name, year) in enumerate(items[:10]): print(' [' + str(i) + '] id=' + item_id + ' name=' + repr(name) + ' year=' + year, file=sys.stderr) match = find_best(items, full_name, alt_names) if match: item_id, name, year = match url = 'https://boardgamegeek.com/boardgame/' + item_id results.append(full_name + '|' + item_id + '|' + url) print(' MATCH: ' + item_id + ' - ' + name + ' (' + year + ')', file=sys.stderr) elif fallback_word: time.sleep(3) print(' No match, trying fallback: ' + fallback_word, file=sys.stderr) items2 = search_bgg(fallback_word) print(' Got ' + str(len(items2)) + ' results', file=sys.stderr) for i, (item_id, name, year) in enumerate(items2[:10]): print(' [' + str(i) + '] id=' + item_id + ' name=' + repr(name) + ' year=' + year, file=sys.stderr) match2 = find_best(items2, full_name, alt_names) if match2: item_id, name, year = match2 url = 'https://boardgamegeek.com/boardgame/' + item_id results.append(full_name + '|' + item_id + '|' + url) print(' MATCH (fallback): ' + item_id + ' - ' + name + ' (' + year + ')', file=sys.stderr) else: results.append(full_name + '|NOT_FOUND') print(' NOT FOUND', file=sys.stderr) else: results.append(full_name + '|NOT_FOUND') print(' NOT FOUND', file=sys.stderr) if full_name != GAMES[-1][0]: time.sleep(3) print('\n=== FINAL RESULTS ===', file=sys.stderr) for r in results: print(r)