Skip to content

Commit 27a5b63

Browse files
Add more data fields to game details: old price, release date, and reviews (#196)
1 parent 2783b13 commit 27a5b63

File tree

2 files changed

+94
-42
lines changed

2 files changed

+94
-42
lines changed

discord_free_game_notifier/steam.py

Lines changed: 85 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,17 @@ class MoreData:
3737
# ["Tibba Games"]
3838
publishers: list[str] = field(default_factory=list)
3939

40+
# "4,99€"
41+
old_price: str = ""
4042

41-
def get_more_data(game_id: str) -> MoreData:
43+
# "11 Feb, 2022"
44+
release_date: str = ""
45+
46+
# "Mixed"
47+
reviews: str = ""
48+
49+
50+
def get_more_data(game_id: str) -> MoreData: # noqa: C901, PLR0912, PLR0915
4251
"""Get more data about the game.
4352
4453
Args:
@@ -52,49 +61,76 @@ def get_more_data(game_id: str) -> MoreData:
5261
return MoreData()
5362

5463
logger.debug(f"Getting more data for {game_id=}")
55-
response: requests.Response = requests.get(
64+
appdetails_response: requests.Response = requests.get(
5665
url=f"https://store.steampowered.com/api/appdetails?appids={game_id}",
5766
headers={"User-Agent": DEFAULT_USER_AGENT},
5867
timeout=30,
5968
)
6069

61-
if not response.ok:
62-
logger.error(f"Error when checking game for Steam:\n{response.status_code} - {response.reason}: {response.text}")
63-
return MoreData()
64-
65-
response_data: dict = response.json()
66-
if not response_data:
67-
logger.error(f"JSON data is empty for {game_id=}")
68-
return MoreData()
69-
70-
logger.debug(f"{response_data=}")
71-
72-
game_data: dict = response_data.get(game_id, {}).get("data", {})
73-
if game_data:
74-
more_data = MoreData()
75-
header_image: str | None = game_data.get("header_image")
76-
if header_image:
77-
logger.debug(f"{header_image=} for {game_id=}")
78-
more_data.header_image = header_image
70+
more_data = MoreData()
71+
if appdetails_response.ok:
72+
game_data_from_api: dict = appdetails_response.json()
73+
if game_data_from_api and game_data_from_api.get(game_id, {}).get("success", False):
74+
logger.debug(f"{game_data_from_api=}")
75+
76+
game_data: dict = game_data_from_api.get(game_id, {}).get("data", {})
77+
if game_data:
78+
header_image: str | None = game_data.get("header_image")
79+
if header_image:
80+
logger.debug(f"{header_image=} for {game_id=}")
81+
more_data.header_image = header_image
82+
83+
short_description: str | None = game_data.get("short_description")
84+
if short_description:
85+
logger.debug(f"{short_description=} for {game_id=}")
86+
more_data.short_description = short_description
87+
88+
developers: list[str] | None = game_data.get("developers")
89+
if developers:
90+
logger.debug(f"{developers=} for {game_id=}")
91+
more_data.developers = developers
92+
93+
publishers: list[str] | None = game_data.get("publishers")
94+
if publishers:
95+
logger.debug(f"{publishers=} for {game_id=}")
96+
more_data.publishers = publishers
97+
98+
price_overview: dict | None = game_data.get("price_overview")
99+
if price_overview:
100+
more_data.old_price = price_overview.get("initial_formatted", "")
101+
102+
release_date: dict | None = game_data.get("release_date")
103+
if release_date:
104+
more_data.release_date = release_date.get("date", "")
79105

80-
short_description: str | None = game_data.get("short_description")
81-
if short_description:
82-
logger.debug(f"{short_description=} for {game_id=}")
83-
more_data.short_description = short_description
106+
else:
107+
logger.error(f"Failed to get more data for {game_id=}: {game_data_from_api=}")
108+
else:
109+
logger.error(f"Failed to get more data for {game_id=}: {appdetails_response.status_code} - {appdetails_response.reason}")
84110

85-
developers: list[str] | None = game_data.get("developers")
86-
if developers:
87-
logger.debug(f"{developers=} for {game_id=}")
88-
more_data.developers = developers
111+
logger.debug(f"Getting reviews for {game_id=}")
89112

90-
publishers: list[str] | None = game_data.get("publishers")
91-
if publishers:
92-
logger.debug(f"{publishers=} for {game_id=}")
93-
more_data.publishers = publishers
113+
# {"success":1,"query_summary":{"num_reviews":0,"review_score":5,"review_score_desc":"Mixed","total_positive":125,"total_negative":74,"total_reviews":199},"reviews":[],"cursor":"*"} # noqa: E501
114+
reviews_response: requests.Response = requests.get(
115+
url=f"https://store.steampowered.com/appreviews/{game_id}?json=1&language=all&num_per_page=0&purchase_type=all",
116+
headers={"User-Agent": DEFAULT_USER_AGENT},
117+
timeout=30,
118+
)
94119

95-
return more_data
120+
if reviews_response.ok:
121+
game_data: dict = reviews_response.json()
122+
if game_data and game_data.get("success", False):
123+
logger.debug(f"{game_data=}")
124+
reviews: str | None = game_data.get("query_summary", {}).get("review_score_desc")
125+
if reviews:
126+
logger.debug(f"{reviews=} for {game_id=}")
127+
more_data.reviews = reviews
128+
else:
129+
logger.error(f"Failed to get reviews for {game_id=}: {game_data=}")
130+
else:
131+
logger.error(f"Failed to get reviews for {game_id=}: {reviews_response.status_code} - {reviews_response.reason}")
96132

97-
return MoreData()
133+
return more_data
98134

99135

100136
def get_free_steam_games() -> Generator[DiscordEmbed, Any, None]:
@@ -108,7 +144,12 @@ def get_free_steam_games() -> Generator[DiscordEmbed, Any, None]:
108144
games: list[Node] = parser.css("a.search_result_row")
109145

110146
for game in games:
111-
game_name: str = game.css_first("span.title").text(strip=True)
147+
title_element = game.css_first("span.title")
148+
if title_element is None:
149+
logger.warning("Could not find title for game, skipping")
150+
continue
151+
152+
game_name: str = title_element.text(strip=True)
112153

113154
logger.info(f"Checking game: {game_name}")
114155
previous_games_file: Path = Path(settings.app_dir) / "steam.txt"
@@ -130,6 +171,15 @@ def get_free_steam_games() -> Generator[DiscordEmbed, Any, None]:
130171
if more_data.short_description:
131172
embed.description = html.unescape(more_data.short_description)
132173

174+
if more_data.old_price:
175+
embed.add_embed_field(name="Old Price", value=more_data.old_price)
176+
177+
if more_data.release_date:
178+
embed.add_embed_field(name="Release Date", value=more_data.release_date)
179+
180+
if more_data.reviews:
181+
embed.add_embed_field(name="Reviews", value=more_data.reviews)
182+
133183
set_game_footer(more_data, embed)
134184

135185
logger.debug(f"More data for {game_name}: {more_data}")

tests/test_steam.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(self, text: str, status_code: int) -> None:
4242
self.text: str = text
4343
self.status_code: int = status_code
4444
self.json_data: dict[str, dict[str, Any]] = {}
45+
self.reason: str = {200: "OK", 404: "Not Found"}.get(status_code, "Unknown")
4546

4647
@property
4748
def ok(self) -> bool:
@@ -64,14 +65,14 @@ def fake_get(
6465
"""A fake requests.get function that returns local HTML for the Steam search page and fake JSON data for API calls.
6566
6667
Args:
67-
url (str): The URL to fetch.
68+
url (str): The URL to check.
6869
headers (dict[str, str] | None): The headers to use for the request.
6970
timeout (int | None): The timeout for the request.
70-
empty_mode (bool): Flag to determine which HTML file to return.
71-
**kwargs (dict[str, Any]): Additional keyword arguments.
71+
empty_mode (bool): Whether to return an empty response or not.
72+
kwargs (dict[str, Any]): Additional keyword arguments.
7273
7374
Returns:
74-
FakeResponse: A fake Response object with the appropriate data for the given URL.
75+
FakeResponse: A fake response object with the appropriate data.
7576
"""
7677
if url.startswith(STEAM_URL.split("?")[0]):
7778
# steam.html has 2 games, steam_empty.html has 0 games
@@ -86,8 +87,9 @@ def fake_get(
8687
appids: list[str] = query.get("appids", ["753"])
8788
appid: str = appids[0]
8889

89-
data: dict[str, dict[str, dict[str, str | list[str]]]] = {
90+
data: dict[str, dict[str, Any]] = {
9091
appid: {
92+
"success": True,
9193
"data": {
9294
"header_image": "http://example.com/header.jpg",
9395
"short_description": "A free game description",
@@ -115,7 +117,7 @@ def patch_requests_get_with_mode(monkeypatch: pytest.MonkeyPatch) -> Callable[..
115117
"""
116118

117119
def _patch(*, empty_mode: bool = False) -> None:
118-
monkeypatch.setattr(requests, "get", lambda url, **kwargs: fake_get(url, empty_mode=empty_mode, **kwargs)) # pyright: ignore[ reportCallIssue ]
120+
monkeypatch.setattr(requests, "get", lambda url, **kwargs: fake_get(url, empty_mode=empty_mode, **kwargs)) # pyright: ignore[reportCallIssue]
119121

120122
return _patch
121123

@@ -166,7 +168,7 @@ def test_get_free_steam_games(tmp_path: Path, patch_requests_get_with_mode: Call
166168
image: dict[str, str | int | None] | None = first_embed.image
167169
assert image, "Embed image is missing"
168170
assert "url" in image, "Embed image URL is missing"
169-
assert str(image.get("url", "")).startswith("http://example.com/header.jpg"), "Embed image URL not as expected"
171+
assert str(image.get("url", "")).startswith("http://example.com/header.jpg"), f"Embed image URL is incorrect: {image.get('url')}"
170172

171173
# If a description was set (via the short_description), verify it contains the expected text.
172174
if first_embed.description:

0 commit comments

Comments
 (0)