Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ Team : Théo Pirouelle
<img src="https://img.shields.io/badge/language-python-blue?style=flat-square" alt="laguage-python" />
</a>

![TestsResult](https://github.com/Dayonixe/SteamToNotion/actions/workflows/python-tests.yml/badge.svg)

---

## Preamble

To use the script, you'll need a Notion API key and a Notion database ID.
You'll need to create a `.env` file containing the variables `NOTION_TOKEN` and `NOTION_DATABASE_ID` with your API key and database ID for the script to work correctly.
You'll need to create a `.env` file containing the variables `NOTION_TOKEN`, `NOTION_DATABASE_ID` and `RAWG_API_KEY` with your API key and database ID for the script to work correctly.

> [!IMPORTANT]
> To use the application, you need to be connected to the internet so that it can call the Steam and Notion APIs.
Expand All @@ -26,6 +28,7 @@ You'll need to create a `.env` file containing the variables `NOTION_TOKEN` and
> | Library | Version |
> | --- | --- |
> | requests | 2.32.5 |
> | pytest | 9.0.1 |

Please also remember to install Python. The code was developed and works with Python 3.11.

Expand All @@ -36,15 +39,18 @@ Please also remember to install Python. The code was developed and works with Py
In the Notion database configured in the `.env`, the following properties are required for the script to work:
- Platform (select): For the page to be processed, the selected platform must be ‘Steam’.
- ID (number): Similarly, for the page to be processed, there must be a game ID (or at least the game title as the page title).
- Price (number)
- Released (checkbox)
- Release date (date)
- Genres (multi_select)
- Price (number)
- Estimated duration (number)
- Metacritic (number)
- Release date (date)

To easily find the game ID, simply go to the game's page in the Steam store and retrieve the ID from the page's URL.
For example, in the URL `https://store.steampowered.com/app/3097560/Liars_Bar/`, the game ID is `3097560`.

It is important to note that the estimated duration of a game is calculated in the script because there is no API that returns the real duration of a game. The value is therefore extracted from RAWG.io, and then, based on the tags and genres of the game, a multiplier ratio calculation is applied to this value.

To run the script in a Linux or PowerShell terminal:
```bash
python[3] [path/to/]export_data.py
Expand All @@ -53,17 +59,17 @@ python[3] [path/to/]export_data.py
The script will run and you should see the following lines displayed, for example:
```
🔍 Récupération Steam pour app_id = 1172620
📥 Données récupérées : {'app_id': 1172620, 'name': 'Sea of Thieves: 2025 Edition', 'price': 39.99, 'released': True, 'release_date': '2020-06-03', 'genres': ['Action', 'Adventure'], 'metacritic_score': None, 'cover_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1172620/library_hero.jpg', 'icon_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1172620/logo.png'}
📥 Données récupérées : {'app_id': 1172620, 'name': 'Sea of Thieves: 2025 Edition', 'price': 39.99, 'released': True, 'release_date': '2020-06-03', 'genres': ['Action', 'Adventure'], 'hltb_time': None, 'metacritic_score': None, 'cover_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1172620/library_hero.jpg', 'icon_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1172620/logo.png'}
✅ Page mise à jour : xxxxxxxxxxxxxxxx

🔍 Récupération Steam pour app_id = 1203180
📥 Données récupérées : {'app_id': 1203180, 'name': 'Breakwaters: Crystal Tides', 'price': 16.79, 'released': True, 'release_date': '2021-12-09', 'genres': ['Action', 'Adventure', 'Indie', 'Simulation', 'Early Access'], 'metacritic_score': None, 'cover_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1203180/library_hero.jpg', 'icon_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1203180/logo.png'}
📥 Données récupérées : {'app_id': 1203180, 'name': 'Breakwaters: Crystal Tides', 'price': 16.79, 'released': True, 'release_date': '2021-12-09', 'genres': ['Action', 'Adventure', 'Indie', 'Simulation', 'Early Access'], 'hltb_time': 13.2, 'metacritic_score': None, 'cover_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1203180/library_hero.jpg', 'icon_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1203180/logo.png'}
✅ Page mise à jour : xxxxxxxxxxxxxxxx

⏭️ Page ignorée (xxxxxxxxxxxxxxxx) — Platform = Epic Games

🔍 Récupération Steam pour app_id = 113200
📥 Données récupérées : {'app_id': 113200, 'name': 'The Binding of Isaac', 'price': 4.99, 'released': True, 'release_date': '2011-09-28', 'genres': ['Action', 'Adventure', 'Indie', 'RPG'], 'metacritic_score': 84, 'cover_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/113200/library_hero.jpg', 'icon_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/113200/logo.png'}
📥 Données récupérées : {'app_id': 113200, 'name': 'The Binding of Isaac', 'price': 4.99, 'released': True, 'release_date': '2011-09-28', 'genres': ['Action', 'Adventure', 'Indie', 'RPG'], 'hltb_time': 10.0, 'metacritic_score': 84, 'cover_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/113200/library_hero.jpg', 'icon_image': 'https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/113200/logo.png'}
✅ Page mise à jour : xxxxxxxxxxxxxxxx

[...]
Expand Down
3 changes: 2 additions & 1 deletion src/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
NOTION_TOKEN=secret_xxxxxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxx
RAWG_API_KEY=xxxxxxxxxxxxxxxx
100 changes: 100 additions & 0 deletions src/export_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,95 @@ def search_app_id_by_name(name: str):
return best_app_id


def get_rawg_data(game_name: str):
"""
Recherche les données d'un jeu dans RAWG
:param game_name: Nom du jeu
:return: playtime, genres, tags
"""
url = (
f"https://api.rawg.io/api/games"
f"?search={game_name}"
f"&key={RAWG_API_KEY}"
)

try:
res = requests.get(url).json()
except Exception as e:
print(f"⚠️ Erreur RAWG : {e}")
return None, [], []

if "results" not in res or len(res["results"]) == 0:
print(f"⚠️ No res")
return None, [], []

game = res["results"][0] # meilleur match automatique RAWG

playtime = game.get("playtime") # durée médiane communautaire

genres = [g["name"] for g in game.get("genres", [])]
tags = [t["name"] for t in game.get("tags", [])]

return playtime, genres, tags


def estimate_hltb_from_rawg(rawg_playtime, genres, tags):
"""
Calcul une estimation de la durée d'un jeu à partir de la durée extraite de RAWG
:param rawg_playtime: Durée du jeu de RAWG
:param genres: Liste des genres du jeu de RAWG
:param tags: Liste des tags du jeu de RAWG
:return: Estimation de la durée
"""
if rawg_playtime is None or rawg_playtime <= 0:
return None

genres = [g.lower() for g in genres]
tags = [t.lower() for t in tags]

# Ratio basé sur RAWG -> HLTB
ratio = 2.5

# Protection sur les jeux courts
if rawg_playtime <= 2:
return round(rawg_playtime * 1.2, 1)
elif rawg_playtime <= 3:
return round(rawg_playtime * 1.5, 1)
elif rawg_playtime <= 5:
return round(rawg_playtime * 2.0, 1)

# Ratios selon genre principal
if any(g in genres for g in ["rpg", "role-playing"]):
ratio = 5.0
elif any(g in genres for g in ["strategy"]):
ratio = 3.0
elif any(g in genres for g in ["adventure"]):
ratio = 2.5
elif any(g in genres for g in ["action"]):
ratio = 2.2
elif any(g in genres for g in ["indie"]):
ratio = 1.8
elif any(g in genres for g in ["platformer"]):
ratio = 1.5
elif any(g in genres for g in ["roguelike"]):
ratio = 2.5

# survival sandbox long / survival horror court
if any(t in tags for t in ["open world", "sandbox", "crafting", "exploration", "base building"]):
ratio *= 3.5
elif any(t in tags for t in ["survival", "horror"]):
ratio *= 1.2

# Simulation / factory games
if any(g in genres for g in ["simulation"]):
ratio = max(ratio, 3.5)

if any(t in tags for t in ["automation", "factory", "management"]):
ratio = max(ratio, 4.0)

return round(rawg_playtime * ratio, 1)



######################################################
# Fonctions Principales #
Expand Down Expand Up @@ -238,6 +327,13 @@ def get_steam_game_details(app_id: int):
if "genres" in info:
genres = [g["description"] for g in info["genres"]]

# Durée du jeu
if released:
rawg_playtime, rawg_genres, rawg_tags = get_rawg_data(name)
hltb_time = estimate_hltb_from_rawg(rawg_playtime, rawg_genres, rawg_tags)
else:
hltb_time = None

# Note du jeu
metacritic_score = None
if "metacritic" in info and "score" in info["metacritic"]:
Expand All @@ -254,6 +350,7 @@ def get_steam_game_details(app_id: int):
"released": released,
"release_date": release_date,
"genres": genres,
"hltb_time": hltb_time,
"metacritic_score": metacritic_score,
"cover_image": wallpaper,
"icon_image": icon
Expand Down Expand Up @@ -286,6 +383,9 @@ def update_notion_page(page_id, game):
"Genres": {
"multi_select": [{"name": genre} for genre in game["genres"]]
},
"Estimated duration": {
"number": float(game["hltb_time"]) if game["hltb_time"] is not None else None
},
"Metacritic": {
"number": int(game["metacritic_score"]) if game["metacritic_score"] is not None else None
}
Expand Down