File size: 9,815 Bytes
2577d67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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 []