mrSpectrum's picture
Upload 2 files
2577d67 verified
raw
history blame
9.82 kB
import requests
from xml.etree import ElementTree as ET
BGG_BASE_URL = 'https://boardgamegeek.com/xmlapi2'
def search(query):
"""
Search for board games on BoardGameGeek by title.
Args:
query (str): The name (or partial name) of the board game to search for.
Returns:
List[dict]: A list of dictionaries, each representing a matching game with the following fields:
- 'id' (str): The unique BGG ID of the game.
- 'title' (str): The official title of the game.
- 'year' (str): The year the game was published (or 'Unknown' if not available).
Example:
>>> search_game("Catan")
[
{"id": "13", "title": "Catan", "year": "1995"},
...
]
"""
response = requests.get(f"{BGG_BASE_URL}/search?query={query}&exact=0&type=boardgame")
root = ET.fromstring(response.content)
results = []
for item in root.findall("item"):
game_id = item.get("id")
title = item.find("name").get("value")
year = item.find("yearpublished").get("value") if item.find("yearpublished") is not None else "Unknown"
results.append({"id": game_id, "title": title, "year": year})
return results
def get_game_details(game_ids):
"""
Get detailed information for one or more board games.
Args:
game_ids (str or list): Single game ID or comma-separated list of IDs
Returns:
List[dict]: Detailed game information including mechanics, categories, ratings, etc.
"""
if isinstance(game_ids, list):
game_ids = ",".join(game_ids)
response = requests.get(f"{BGG_BASE_URL}/thing?id={game_ids}&stats=1")
root = ET.fromstring(response.content)
games = []
for item in root.findall("item"):
game = {
"id": item.get("id"),
"title": item.find("name[@type='primary']").get("value"),
"description": item.find("description").text if item.find("description") is not None else "No description available",
"year": item.find("yearpublished").get("value") if item.find("yearpublished") is not None else "Unknown",
"min_players": item.find("minplayers").get("value") if item.find("minplayers") is not None else "Unknown",
"max_players": item.find("maxplayers").get("value") if item.find("maxplayers") is not None else "Unknown",
"playing_time": item.find("playingtime").get("value") if item.find("playingtime") is not None else "Unknown",
"complexity": None,
"rating": None,
"categories": [],
"mechanics": []
}
# Extract ratings and complexity
ratings = item.find("statistics/ratings")
if ratings is not None:
avg_rating = ratings.find("average")
if avg_rating is not None:
game["rating"] = avg_rating.get("value")
complexity_elem = ratings.find("averageweight")
if complexity_elem is not None:
game["complexity"] = complexity_elem.get("value")
# Extract categories and mechanics
for link in item.findall("link"):
link_type = link.get("type")
if link_type == "boardgamecategory":
game["categories"].append(link.get("value"))
elif link_type == "boardgamemechanic":
game["mechanics"].append(link.get("value"))
games.append(game)
return games
def get_hot_games():
"""
Get a list of the Top 50 trending games today from BoardGameGeek.
Returns:
List[dict]: A list of dictionaries, each representing a popular game with the following fields:
- 'id' (str): The unique BGG ID of the game.
- 'title' (str): The official title of the game.
- 'rank' (str): The current hotness rank of the game on BGG.
- 'year' (str): The year the game was published (or 'Unknown' if not available).
- 'url' (str): URL to the similar game's page on BoardGameGeek.
"""
response = requests.get(f"{BGG_BASE_URL}/hot?type=boardgame")
root = ET.fromstring(response.content)
results = []
for item in root.findall("item"):
game_id = item.get("id")
rank = item.get("rank")
title = item.find("name").get("value")
year = item.find("yearpublished").get("value") if item.find("yearpublished") is not None else "Unknown"
url = f"https://boardgamegeek.com/boardgame/{game_id}"
results.append({"id": game_id, "title": title, "rank": rank, "year": year, "url": url})
return results
def get_similar_games(game_id, limit=5):
"""
Get a list of games similar to the specified game from the recommend games API.
Args:
game_id (str): The unique BGG ID of the game to find similar games for.
limit (int): The number of BGG IDs to retrieve.
Returns:
List[dict]: A list of dictionaries, each representing a similar game with the following fields:
- 'id' (str): The unique BGG ID of the similar game.
- 'title' (str): The official title of the similar game.
- 'year' (str): The year the similar game was published (or 'Unknown' if not available).
- 'description' (str): A brief description of the similar game.
- 'url' (str): URL to the similar game's page on BoardGameGeek.
"""
recommended_games = []
api_url = f"https://recommend.games/api/games/{game_id}/similar.json"
try:
response = requests.get(api_url)
response.raise_for_status()
api_data = response.json()
results = api_data['results']
# Filter out all games with less than 30 votes (num_votes)
results = [game for game in results if game.get('num_votes', 0) >= 30]
# Filter out all games with a rec_rating of 0
results = [game for game in results if game.get('rec_rating', 0) > 0]
# Sort results by rec_rating, bayes_rating and avg_rating
results.sort(key=lambda x: (x.get('rec_rating', 0), x.get('bayes_rating', 0), x.get('avg_rating', 0)), reverse=True)
for game_data in results[:limit]:
bgg_id = game_data.get('bgg_id')
title = game_data.get('name')
year = game_data.get('year')
description = game_data.get('description', 'No description available')
url = game_data.get('url')
if bgg_id and title:
formatted_game = {
'id': str(bgg_id),
'title': str(title),
'year': str(year) if year is not None else 'Unknown',
'description': str(description),
'url': str(url),
}
recommended_games.append(formatted_game)
return recommended_games
except requests.RequestException as e:
print(f"Error fetching similar games: {e}")
return []
def get_similar_games_v2(game_id, limit=5, start=0, end=25, noblock=False):
"""
Retrieves a list of games similar to a specified board game from the RecommendGames API.
Args:
game_id (str): The unique BGG ID of the game to find similar games for.
limit (int): The number of similar games to retrieve.
start (int, optional): The starting index for the desired range of results. Defaults to 0.
end (int, optional): The ending index for the desired range of results. Defaults to 25.
noblock (bool, optional): If True, the request will not timeout. Defaults to False, which sets a 10-second timeout.
Returns:
List[dict]: A list of dictionaries, each representing a similar game with the following fields:
- 'id' (str): The unique BGG ID of the similar game.
- 'title' (str): The official title of the similar game.
- 'year' (str): The year the similar game was published (or 'Unknown' if not available).
- 'description' (str): A brief description of the similar game.
- 'url' (str): URL to the similar game's page on BoardGameGeek.
Returns an empty list if there are any errors during the API request or data processing.
"""
api_url = f"https://recommend.games/api/games/{game_id}/similar.json"
params = {
'num_votes__gte': 30,
'ordering': '-rec_rating,-bayes_rating,-avg_rating'
}
all_games = []
try:
for i in range(start+1, end+1):
params['page'] = i
response = requests.get(api_url, params=params, timeout=None if noblock else 10)
response.raise_for_status()
api_data = response.json()
games = api_data.get('results', [])
if not games:
break
processed_games = [
{
'id': str(game.get('bgg_id', '')),
'title': str(game.get('name', '')),
'year': str(game.get('year', 'Unknown')),
'description': str(game.get('description', 'No description available')),
'url': str(game.get('url', '')),
}
for game in games
if game.get('num_votes', 0) >= 30 and game.get('rec_rating', 0) > 0.001
]
all_games.extend(processed_games)
# Check if we have enough results
if len(all_games) >= end or not api_data.get('next'):
break
return all_games[:limit] if limit > 0 else all_games
except requests.RequestException as e:
print(f"Error fetching similar games: {e}")
return []
except ValueError as e:
print(f"Error: {e}")
return []