Documentation de l'API Jeuxvideo.com
L'API de jeuxvideo.com, utilisée à l'origine par les applications mobiles de jeuxvideo.com, permet de développer plus facilement des applications, sites Web, et autres scripts en rapport avec jeuxvideo.com.
L'API est hébergée sur le sous-domaine api, et est actuellement à sa quatrième version (v4). Les anciennes versions sont toujours exploitables avec les tokens qui leur sont propres potentiellement obsolète , mais certains endpoints renvoient des erreurs HTTP 403.
L'API actuelle est privée et ne peut être documentée que par rétro-ingéniérie.
Rétro-ingénierie de l'application mobile JVC
Pour connaître les endpoints de l'API, il faut décompiler l'application mobile JVC ou intercepter les requêtes qu'elle envoie.
Décompilation
Mise en place du processus de décompilation
Pour pouvoir décompiler l'application mobile, vous aurez besoin des outils suivants :
Il faut également le fichier .apk de l'application mobile, téléchargeable ici (privilégiez les versions supérieures à la 5.4.3, sans quoi vous devrez extraire le fichier .xapk et appliquer le processus de décompilation sur le fichier .apk principal).
Il vaut mieux renommer le fichier .apk obtenu pour éviter d'avoir à être embarrassé par de trops longs noms de fichiers. Ici, nous l'avons enregistré sous jeuxvideo.apk.
Suivez les étapes ci-dessous (en remplaçant jeuxvideo.apk par le nom du fichier .apk à votre disposition) :
- Dans un terminal ouvert au dossier de l'APK, exécutez la commande suivante :
$ apktool d jeuxvideo.apk jeuxvideo-decompiled
- Ouvrez JADX, activez l'option Show flatten packages (dans la barre d'outils), dans les préférences du logiciel veillez à avoir changé l'option Code comments level sur DEBUG.
- Importez les fichiers .dex de jeuxvideo-decompiled dans JADX.
JvApiService
Une fois le code Java disponible, on peut ouvrir l'interface JvApiService (située dans com.jeuxvideo.api.web), qui contient tous les endpoints. L'application utilise le bibliothèque Retrofit 2.x pour gérer l'API, et la classe contient tous les endpoints formels de l'API.
Extrait de l'interface JvApiService
@POST("contents/{contentID}/comments") Call<UserComment> addComment(@Path("contentID") String str, @Body UserComment.Body body); @POST("contents/{contentID}/comments/{commentID}/vote") Call<UserComment> addCommentVote(@Path("contentID") String str, @Path("commentID") String str2, @Body UserComment.Vote vote);
Interface complète disponible ici.
SigningInterceptor
La classe SigningInterceptor (du package com.jeuxvideo.api.utils), permet de déduire la valeur du header Jvc-Authorization à envoyer dans les requêtes à l'API.
public static String m27149f(String str, String str2, String str3, String str4, String str5) { StringBuilder sb2 = new StringBuilder(); sb2.append("550c04bf5cb2b\n"); sb2.append(str); sb2.append("\n"); sb2.append(str2); sb2.append("\n"); sb2.append(str3); sb2.append("\n"); sb2.append(str4); sb2.append("\n"); if (str5 == null) { str5 = ""; } sb2.append(str5); String m14428b = CryptoUtils.m14428b("d84e9e5f191ea4ffc39c22d11c77dd6c", sb2.toString()); return "PartnerKey=550c04bf5cb2b, Signature=" + m14428b + ", Timestamp=" + str; }
Classe complète disponible ici.
JVApi et UserAgentInterceptor
JVApi (du package arbitrairement nommé p379s3 car nom perdu lors de la compilation) et UserAgentInterceptor (du package com.jeuxvideo.api.utils) permettent de déduire le user-agent et la valeur des headers contenant des informations sur la version et la plateforme.
JVApiManager
La classe JVApiManager (du package arbitrairement nommé p379s3 car nom perdu lors de la compilation) est celle qui envoie les requêtes à l'API. Elle permet de savoir quelles variables sont passées à la requête vers tel endpoint.
Classe complète disponible ici.
Le cas Jvc-Auth-Header
Jvc-Auth-Header est le header envoyé à l'endpoint nommé accounts/register. Cet endpoint est crucial en ce qu'il représente l'unique manière de créer un compte JVC sans le système reCaptcha.
Il est actuellement impossible que la requête soit validée par l'API sans ce header.
Il s'avère que Jvc-Auth-Header contient le token FCM, un token créé par la plateforme FireBase de Google et utilisé par l'application mobile pour accéder aux bases de données. Ce token est unique à chaque appareil et est créé à chaque installation de l'application mobile. En outre, il permet à l'API de savoir si l'application a déjà effectué une requête d'inscription dans les dernières 24 heures et, le cas échéant, de bloquer les requêtes d'inscription.
Il semble malheureusement impossible de générer de tels tokens « à la demande », en tout cas sans connaître les noms et clés des services FireBase dédiés à l'application.
Interception du trafic
Il est possible d'intercepter le trafic émis par l'application mobile lorsqu'elle est exécutée sur un appareil afin de connaître en détail les requêtes envoyées à l'API.
Cette méthode est indépendante de la précédente qui nécessitait de décompiler l'APK. Ici, vous aurez besoin du fichier APK original.
Mise en place du processus d'interception
Pour pouvoir intercepter le trafic de l'application mobile, vous aurez besoin des outils suivants :
Il faut également le fichier .apk de l'application mobile, téléchargeable ici.
Pour commencer, il vous faut créer un appareil virtuel avec Android Studio : suivez pour cela le tutoriel disponible à cette adresse. Pour que HTTP Toolkit puisse intercepter le trafic de l'application, il faut choisir un appareil ne disposant pas de Google Play. Nous vous conseillons de choisir le téléphone Pixel 6 Pro et l'image système Tiramisu (niveau d'API 33, Android 13.0). Une fois l'appareil virtuel créé, lancez-le.
Une fois ceci fait, ouvrez le logiciel HTTP Toolkit puis dans le menu principal choisissez l'option Android Device via ADB qui devrait désormais être accessible. HTTP Toolkit va automatiquement installer tous les logiciels nécessaires sur l'appareil ; vous serez invités, sur l'appareil, à autoriser une connextion VPN à HTTP Toolkit : appuyez sur « OK ».
Ensuite, vous devrez utiliser ADB pour installer l'APK sur l'appareil virtuel. Premièrement, il vous faut ajouter ADB à la variable PATH de votre ordinateur ; consultez la section « How to use ADB from any directory on your PC or Mac » de cet article, qui contient les étapes à suivre sur Windows, MacOS et Linux.
Ouvrez un terminal de commande dans le dossier où se trouve l'APK et exécutez adb install jeuxvideo.apk
(remplacez jeuxvideo.apk par le nom du fichier APK téléchargé). Une fois la commande exécutée, vous n'avez plus qu'à lancer l'application mobile et observer quelles requêtes sont envoyées dans le panel View de HTTP Toolkit.
Vous pouvez désormais voir quelles requêtes sont passées à l'API, ainsi que les headers et body associés. Par exemple lorsque l'on se connecte :
API v4
Préambule
L'API v4 utilise une sécurité, l'en-tête HTTP Jvc-Authorization
, sa valeur dépend d'une clé fixe partner_key
, d'une signature
et de la date
.
Le header de vos requêtes devra toujours être de la forme :
"Jvc-Authorization" : header, "User-Agent" : "JeuxVideo-Android/338", "Jvc-App-Platform" : "Android", "Jvc-App-Version" : 338, "Content-Type" : "application/json"
Où header est égal à une chaîne de caractères de la forme "PartnerKey=partner_key, Signature=signature, Timestamp=timestamp".
Veuillez consulter l'annexe pour trouver un script Python permettant de créer ce header.
Authentification
Comme pour l'ancienne API, le cookie coniunctio est toujours le cookie de session utilisateur. Il doit être récupéré avec l'appel au point d'entrée accounts/login (voir tableau POST) puis il doit être envoyé dans une en-tête HTTP "Cookie" : "coniunctio={coniunctio}"
pour chaque appel.
Comportement du sous-domaine
Il est intéressant de remarquer que l'API se comporte souvent en véritable sous-domaine, de manière similaire au site www.jeuxvideo.com de base : par exemple, si vous êtes sur un topic ou un forum, il vous suffit de remplacer dans l'URL « www » par « api » et vous vous trouverez sur une page aux informations identiques quoique présentées de manière différente. Ainsi, les URL seront de la forme https://api.jeuxvideo.com/forums/0-51-0-1-0-1-0-blabla-18-25-ans.htm
.
L'API ne fonctionne donc pas que par endpoints.
Endpoints « formels »
Il s'agit des endpoints « documentés » dans le code de l'application mobile et qui se comportent différemment des services de l'API qui ne se contentent que de « copier » le contenu JVC (voir sous-section précédente).
URL de base: https://api.jeuxvideo.com/v4/
- URL: URL de l'endpoint
- PARAMS, HEADERS:
- param : variable entre accolades à remplacer dans l'URL, suivie dans la documentation de son type (str ou int). Par exemple,
contents/{contentID}/comments
doit être appelé comme suit :contents/123456/comments
.
Note : dans la plupart des cas, le paramètreaccountId
peut être remplacé par la valeurme
lorsque la requête doit pointer sur le compte connecté. - header : header particulier à inclure dans l'en-tête de la requête. Si pas de spécification, l'en-tête ne contient que les headers de base spécifiés plus haut (User-Agent, Jvc-Authorization, etc.).
- param : variable entre accolades à remplacer dans l'URL, suivie dans la documentation de son type (str ou int). Par exemple,
- QUERY: par exemple pour les deux query page, perPage int, l'URL devra être:
api.jeuxvideo.com/v4/contents/1234ID/comments?page=1&perPage=30
- BODY: Corps de la requête au format JSON
Le fichier JSON contenant les id
des params et des query strings chronicles
modes
genres
events
machine
categories
(obtenu via la requête GET config) est disponible ici.
NOM | URL | PARAMS, HEADERS | BODY |
---|---|---|---|
addComment | contents/{contentID}/comments | contentID int | "content": "commentaire" |
addCommentVote | contents/{contentID}/comments/{commentID}/vote | contentID int
commentID int |
"type": 1 ou -1 |
addReply | contents/{contentID}/comments/{commentID}/answers | contentID int
commentID int |
"content": "Super jeu !" |
addReview | games/{id}/{machine}/reviews/users | id int
machine int |
"content": "Super jeu !", "mark": 4, "onProfile": true |
login | accounts/login | "alias": "pseudo", "password": "mot_de_passe" | |
logout | accounts/logout | ||
register | accounts/register | header : "Jvc-Auth-Token" : str
|
"email": "email", "alias": "pseudo", "password": "motdepasse", "optin": false |
reinitPassword | accounts/reset | "alias": "pseudo", "password": "nouveau", "session": "", "captcha": { "imageKey": "", "imageName": "", "imageValues": [ "val1", "val2" ] } | |
reportAccount | accounts/{accountId}/report | accountId int | "reason": 1, "message": "Raison" |
reportComment | contents/{contentID}/comments/{commentID}/report | contentID int
commentID int |
"reason": 1, "message": "Raison", "captcha": { "session": "clé" "clé_captcha": "Valeur" "valeur_captcha": "Valeur" } |
reportReview | games/{id}/{machine}/reviews/users/{reviewId}/report | id int
machine int reviewId int |
"reason": 1, "message": "Raison", "captcha": { "session": "clé" "clé_captcha": "" "valeur_captcha": "" } |
restoreComment | contents/{contentID}/comments/{commentID} | contentID int
commentID int |
"content" : "comment" |
validateAccount | accounts/confirm | "id": 123, "hash": "hash", "alias": "pseudo", "password": "mdp" | |
validateSignature | general/stores/android/validation | "signature": "signature", "signed_data": "signed_data" | |
addFavoritesForums | accounts/me/favorites/forums | "forums" : [50,51,52] # ID des forums
| |
addFavoritesGames | accounts/me/favorites/games | "games" : [{"id":"ID du jeu", "machine":"ID de la machine"}]
| |
addFavoritesTopics | accounts/me/favorites/topics | "topics" : [74229153, 74229154] # ID des forums
|
Ci-dessous figurent les requêtes GET, qui se comportent comme des recherches selon les filtres passés en queries.
Les queries de type str
(chaînes de caractère) représentent des ID répertoriées dans la réponse à la requête general/config
(sous forme de fichier JSON, disponible ici).
Si l'on souhaite filtrer les résultats selon leur nature (news, vidéos, previews, etc.), on dispose du dictionnaire suivant (provenant de l'application mobile) qui à chaque nature de document associe l'ID des catégories associées :
{'videos': '6,13,14,15,16,17,145,19,20', 'news': '50', 'previews': '55', 'tests': '56,57', 'all': '6,13,14,15,16,17,145,50,19,20,53,55,56,57'}
Pour plus de détails, veuillez consulter le fichier JSON de configuration.
Comment vous pouvez le constater, contrairement aux variables de type int
, les str
peuvent contenir plusieurs valeurs, séparées par des virgules : dans ce cas, elles devront être parsées selon l'encodage URL (voir l'annexe où un script Python réalisant cette tâche est mis à disposition).
Les queries page et perPage, toujours des entiers, représentent respectivement : le numéro de la page de recherche et le nombre d'objets à afficher par page. Les ID comme accountID et commentID sont des entiers. Il est à noter que dans la plupart des cas, accountID peut être remplacé par la chaîne de caractères me qui représente l'utilisateur connecté (connu grâce au cookie coniunctio passé en en-tête de la requête).
NOM | URL | PARAMS, HEADERS | QUERY |
---|---|---|---|
config | general/config | ||
getAccount | accounts/{accountId} | accountId int | |
getAllFavorisGames | accounts/{accountId}/favorites/games/all | accountId int | |
getArticle | contents/{id} | id int | |
getCaptcha | captcha/start | nb int | |
getChroniclesSummary | contents/chronicles | machines str | |
getComment | contents/{contentID}/comments/{commentID} | contentID int
commentID int |
|
getCommentAnswers | contents/{contentID}/comments/{commentID}/answers | contentID int
commentID int |
|
getComments | contents/{contentID}/comments | contentID int | page int
perPage int |
getContentBean | contents/{id} | id int | |
getContentList | contents | categories str
types str events str machines str page int perPage int | |
getCurrentAccountReport | accounts/{accountId}/report | accountId int | |
getCurrentCommentReport | contents/{contentID}/comments/{commentID}/report | contentID int
commentID int |
|
getCurrentReviewReport | games/{id}/{machine}/reviews/users/{reviewId}/report | id int
machine str reviewId int |
|
getFavorites | accounts/{accountId}/favorites | accountId int | |
getFavoritesForum | accounts/{accountId}/favorites/forums | accountId int | page int
perPage int |
getFavoritesGames | accounts/{accountId}/favorites/games | accountId int | |
getFavoritesTopics | accounts/{accountId}/favorites/topics | accountId int | page int
perPage int |
getFolder | contents/{id} | id int | |
getGame | games/{id}/{machine} | id int
machine str |
|
getGameDetails | games/{id}/{machine}/details | id int
machine str |
|
getGameImages | games/{id}/{machine}/images | id int
machine str |
page int
perPage int |
getGameList | games/{type} | type str | machines str
machine str genre str mode str page int perPage int |
getGameNews | games/{id}/{machine}/news | id int | page int
perPage int |
getGameReleaseList | games/releases | month str
year str machines str page int perPage int | |
getGameReview | games/{id}/{machine}/reviews/users/{review} | id int
machine str review str |
|
getGameReviews | games/{id}/any/reviews | id int | |
getGameReviews | games/{id}/{machine}/reviews | id int
machine str |
|
getGameSummary | contents/games | machines str | |
getGameUserReviews | games/{id}/{machine}/reviews/users | id int
machine str |
page int
perPage int |
getGameVideos | games/{id}/{machine}/videos | id int
machine str |
page int
perPage int |
getGameWikis | games/{id}/{machine}/wikis | id int
machine str |
page int
perPage int |
getHeadlineList | contents/trending | machines str
page int perPage int | |
getHighTechSummary | contents/hightech | machines str | |
getLightGame | games/{id}/{machine}/light | id int
machine str |
|
getNews | contents/{id} | id int | |
getNewsHighTech | contents/hightech | page int
perPage int | |
getPageContents | accounts/{accountId}/page/contents | accountId int | page int
perPage int |
getPageReviews | accounts/{accountId}/page/reviews | accountId int | page int
perPage int |
getProfile | accounts/{accountId}/profile | accountId int | |
getProfilePage | accounts/{accountId}/page | accountId int | |
getRelatedNews | contents/{id}/news | id int | page int
perPage int |
getRelatedVideos | contents/{id}/videos | id int | page int
perPage int |
getRelatedWikis | contents/{id}/wikis | id int | page int
perPage int |
getStores | games/{id}/{machine}/stores | id int
machine str |
|
getTechList | contents/hightech | page int
perPage int | |
getTopWikis | contents/wikis | ||
getTopsComments | contents/{contentID}/comments/tops | contentID int | |
getVideo | videos/{id} | id int | |
getVideosSummary | contents/videos | machines str | |
search | search | q str | |
searchArticles | search/articles | q str
page int perPage int | |
searchAutocomplete | search/games/autocomplete | q str | |
searchGames | search/games | q str
page int perPage int | |
searchNews | search/news | q str
page int perPage int | |
searchVideos | search/videos | q str
page int perPage int | |
searchWikis | search/wikis | q str
page int perPage int | |
sponso | general/nativeads |
NOM | URL | PARAMS, HEADERS | BODY |
---|---|---|---|
saveDescription | accounts/me/profile/description | "description": "Ma description" | |
saveExcludedMachines | accounts/me/profile/excluded-machines | "machines" : [1, 2, 3, 4] // id des machines | |
saveMachines | accounts/me/profile/machines | "machines" : [1, 2, 3, 4] // id des machines | |
updateComment | contents/{contentID}/comments/{commentID} | contentID int
commentID int |
"content": "Super jeu !" |
uploadAvatar | accounts/me/avatar | header : "Content-Type": "application/octet-stream" |
body : objet bytes (conversion du fichier image en octets, pas de clé associée)
|
uploadCover | accounts/me/cover | header : "Content-Type": "application/octet-stream" |
body : objet bytes (conversion du fichier image en octets, pas de clé associée)
|
NOM | URL | PARAMS, HEADERS | BODY |
---|---|---|---|
deleteComment | contents/{contentID}/comments/{commentID} | contentID int
commentID int |
|
deleteCommentVote | contents/{contentID}/comments/{commentID}/vote | contentID int
commentID int |
|
deleteFavorisForum | accounts/me/favorites/forums | "forums" : [50,51,52] # ID des forums
| |
deleteFavorisGames | accounts/me/favorites/games | "forums" : [{"id":"ID du jeu", "machine":"ID de la machine"}]
| |
deleteFavorisTopics | accounts/me/favorites/topics | "topics" : [74229153, 74229154] # ID des topics
|
Annexe
Jvc-Authorization
Voici un script Python qui permet de construire le header Jvc-Authorization :
import hashlib, hmac, datetime, urllib.parse PARTNER_KEY = '550c04bf5cb2b' HMAC_SECRET = b'd84e9e5f191ea4ffc39c22d11c77dd6c' DOMAIN = 'api.jeuxvideo.com' API_VERSION = 4 def auth_header(path: str, method: str = 'GET', query: dict = None) -> str: # Conversion de path en URL avec urllib.parse.urlparse date = datetime.datetime.now().isoformat() parsed_url = urllib.parse.urlparse(f"https://{DOMAIN}/v{API_VERSION}/{path}") if query: parsed_url = parsed_url._replace(query=urllib.parse.urlencode(query)) # Création de la chaîne à hash string_to_hash = [PARTNER_KEY, date, method, parsed_url.netloc, parsed_url.path] if parsed_url.query: string_to_hash.append(parsed_url.query) else: string_to_hash[-1] += '\n' string_to_hash = '\n'.join(string_to_hash) # Hash de la chaîne puis renvoi sous forme de Jvc-Authorization signature = hmac.new(HMAC_SECRET, msg=string_to_hash.encode('utf-8'), digestmod=hashlib.sha256).hexdigest() return f"PartnerKey={PARTNER_KEY}, Signature={signature}, Timestamp={date}"
Requêtes à l'API
Voici un script Python permettant d'envoyer des requêtes à l'API et qui utilise la fonction précédente :
import requests, json from requests import Response DOMAIN = 'api.jeuxvideo.com' API_VERSION = 4 def call(path: str, method: str = 'GET', query: dict = None, data: dict = None, cookies: dict = None) -> Response: url = f"https://{DOMAIN}/v{API_VERSION}/{path}" jv_auth = auth_header(path, method=method, query=query) headers = { "Jvc-Authorization":jv_auth, "Content-Type": "application/json", "jvc-app-platform":"Android", "jvc-app-version":"338", "user-agent":"JeuxVideo-Android/338", "host":"api.jeuxvideo.com" } if data: data = json.dumps(data) return requests.request(url=url, method=method, data=data, headers=headers, params=query, cookies=cookies)
Parsing des queries
Lors de l'appel à contents par exemple, vous pouvez passer en query des chaînes de caractère comme categories ou types. Ces queries, s'il contiennent plusieurs valeurs, doivent être encodés au format d'encodage URL, ce qui peut être fait en Python :
from urllib.parse import quote categories = "50,53,56" # la recherche récupérera les articles de catégories 50, 53 et 56. categories_parsed = quote(categories) query = {"categories": categories_parsed} # vous pouvez désormais passer ce dictionnaire en query
Accès aux forums
Étrangement, il n'existe pas d'endpoint particulier pour envoyer un message ou un topic sur les forums de jeuxvideo.com.
Lorsque l'application mobile souhaite récupérer les pages des forums, elle envoie une requête au lien usuel de la ressource à ceci près que www est remplacé par api. La requête renvoie alors une page HTML en guise de réponse, voir la rubrique en question.
Ainsi, lorsque l'on cherche à interagir avec les forums, il n'y a pas d'avantage à envoyer des requêtes à l'API plutôt qu'au site de base en l'état.
Principe général
En premier lieu, afin d'envoyer un message ou un topic sur un forum, il faut bien évidemment que vous soyez connecté et vus devrez donc passer en en-tête de la requête votre cookie coniunctio, récupérable à partir d'une requête à l'endpoint accounts/login
envoyant vos identifiants.
En second lieu, vous ne devrez pas seulement envoyer aux serveurs le contenu de votre message (et le titre du topic si besoin) : la requête POST devra aussi contenir des paires clés-valeurs générées par le site lui-même et qui agissent comme une sorte de protection. Celles-ci sont : fs_session, fs_timestamp, fs_version, form_alias_rang ainsi qu'un hash.
Heureusement, ces valeurs sont directement disponibles depuis des inputs cachées dans le code HTML des pages de forums/topics et situées dans la div nommée js-form-session-data.
Si l'on veut envoyer un message sur un topic (ou un forum), il faudra d'abord envoyer une requête GET à l'URL associée et récupérer ces valeurs chiffrées avant d'envoyer une requête POST avec le contenu du message en plus de ces dernières.
Script Python
La classe Python suivante permet d'interagir avec les forums de cette manière. Il vous suffit d'entrer la valeur de votre cookie coniunctio et d'appeler les fonctions souhaitées avec le contenu des messages ou topics.
Bien que cette méthode permette d'outrepasser les captchas, il se peut que vous soyez bloqué par le site en envoyant un trop grand nombre de requêtes dans un trop court laps de temps.
import requests, json from requests import Response from bs4 import BeautifulSoup DIV_CLASS = 'js-form-session-data' class ForumClient: def __init__(self, coniuntio: str): self._cookies = {'coniunctio':coniuntio} def _scrape_input_tokens(self, res: Response) -> dict: soup = BeautifulSoup(res.text, 'html.parser') form = soup.find('div', {'class':DIV_CLASS}) jsi = form.findChildren("input") tokens = { "fs_session":jsi[0].attrs["value"], "fs_timestamp":jsi[1].attrs["value"], "fs_version":jsi[2].attrs["value"], jsi[3].attrs["name"]:jsi[3].attrs["value"], "form_alias_rang":"1", "g-recaptcha-response":"" } return tokens def _get_forum_URL(self, forum_id) -> str: return requests.get(f'https://www.jeuxvideo.com/forums/0-{forum_id}-0-1-0-1-0-a.htm').url def _get_topic_URL(self, topic_id) -> str: return requests.get(f'https://www.jeuxvideo.com/forums/42-1-{topic_id}-1-0-1-0-a.htm').url def post_message(self, topic_id: int, message: str) -> Response: """ topic_id, int : ID du topic message, str : votre message renvoie un objet Response """ topic_URL = self._get_topic_URL(topic_id) res = requests.get(topic_URL, cookies=self._cookies) data = self._scrape_input_tokens(res) data['message_topic'] = message res = requests.post(topic_URL, data=data, cookies=self._cookies) return res def post_topic(self, forum_id: int, titre: str, message: str, sondage: dict = None) -> Response: """ forum_id, int : ID du forum titre, str : titre du topic message, str : corps du topic [sondage], dict : dictionnaire de la forme { "question_sondage" : "Question ?", "reponse_sondage[]" : ["reponse 1", "reponse 2"] } """ forum_URL = self._get_forum_URL(forum_id) res = requests.get(forum_URL, cookies=self._cookies) data = self._scrape_input_tokens(res) data['titre_topic'] = titre data['message_topic'] = message if sondage: data |= sondage data['submit_sondage'] = '1' res = requests.post(forum_URL, data=data, cookies=self._cookies) return res if __name__ == '__main__': client = ForumClient("votre coniunctio ici") # Exemples d'utilisation # Pour poster un message client.post_message(74204156, "Je poste depuis Python :)") # Pour poster un topic client.post_topic(51, 'Je poste depuis Python', 'Et toi ? :)')
Limitations
L'API v4 comporte plusieurs limitations :
- Il est impossible d'outrepasser les protections captchas requises par le site notamment pour signaler un message.
- Il est impossible de s'inscrire en utilisation l'endpoint
accounts/register
car celui-ci nécessite un header contenant le token FireBase généré lors de l'installation de l'application (voir le paragraphe à ce sujet). Une solution serait de créer un grand nombre de machines virtuelles (ou d'en automatiser la création), et pour chacune d'entre elles d'intercepter le trafic de l'application (voir section associée) pour récupérer et conserver les tokens dans un fichier. Cette méthode, longue et fastidieuse, ne permettrait donc pas de générer un nombre infini de comptes. Une autre méthode possible, bien qu'elle n'utilise pas l'API, serait de créer un compte sur le site à l'aide d'un webdriver. Celle-ci fonctionne (l'auteur de ce paragraphe l'a déjà implémentée) mais il serait difficile de la rendre compatible pour toutes les plateformes (du fait de la nécessité d'avoir un VPN gérable depuis un script). - Il est particulièrement hardu de faire du web scraping sur les pages de forum puisque l'API renvoie une page HTML et non un fichier JSON dans ce cas.
Ressources
- APK 5.4.7 décompilable avec JADX.
- Classe PHP exploitant l'API (cette classe est incomplète et sera possiblement rendue obsolète dans le temps).
API jvc.gg
Cette API est une API distincte de la v4 et utilisée par la partie Phoenix du site, c'est-à-dire les pages listant les jeux dont la principale est celle-ci. Elle est hébergée au sous-domaine api.jvc.gg.
Préambule
L'API jvc.gg est basée sur l'utilisation de JSON Web Token (JWT), un standard utilisé pour créer des jetons de sécurité qui permettent de transférer de l'information de manière sécurisée entre deux parties au format JSON.
Lorsque vous visitez pour la première fois une page Phoenix, le serveur de JVC renvoie une requête comportant un cookie accessToken. La valeur de ce cookie est une chaîne de caractères encodée au format JWT qui contient notamment votre ID de compte et la valeur de votre cookie coniunctio. La fonction Python suivante permet de récupérer le cookie accessToken en fonction de votre cookie coniunctio :
def get_access_cookie(coniunctio: str) -> str: cookies = {'coniunctio':coniunctio} res = requests.get('https://www.jeuxvideo.com/tous-les-jeux/', cookies=cookies) access_cookie = res.headers['set-cookie'].split(';')[0].replace('accessToken=', '') return access_cookie
Ce cookie vous sera utile lorsque les requêtes envoyées sont destinées à opérer sur votre compte.
Endpoints
Le tableau suivant répertorie les endpoints connus de cette API. Voici la signification de ses colonnes :
- PATH: chemin de l'endpoint.
- PARAMS, HEADERS:
- param : variable entre accolades à remplacer dans l'URL, suivie dans la documentation de son type (str ou int). Par exemple,
contents/{contentID}/comments
doit être appelé comme suit :contents/123456/comments
. - header : header particulier à inclure dans l'en-tête de la requête. Si pas de spécification, l'en-tête ne contient que les headers de base spécifiés plus haut (User-Agent, Jvc-Authorization, etc.).
- param : variable entre accolades à remplacer dans l'URL, suivie dans la documentation de son type (str ou int). Par exemple,
- QUERY: valeur à ajouter à l'URL permettant si précisée de filtrer les résultats. Par exemple pour les deux query page, perPage int, l'URL devra être:
api.jeuxvideo.com/v4/contents/1234ID/comments?page=1&perPage=30
- BODY: Corps de la requête au format JSON
Les paramètres spéciaux
- Le paramètre
{accessToken}
devra toujours être remplacé par la valeur du cookie éponyme. - Le paramètre
{gameCategory}
peut être remplacée par les valeurs suivantes, qui proviennent évidemment du système de classification de JVC :all
pour avoir la liste de tous les jeux ;awaited
pour avoir la liste des jeux attendus ;popular
pour avoir la liste des jeux populaires ;best
pour avoir la liste des meilleurs jeux ;currentBest
pour avoir la liste des meilleurs jeux du moment ;releases
pour avoir la liste des jeux pas encore sortis.
Les queries
Les queries renseignent souvent la plateforme, le genre et le mode du jeu. La liste complète des queries possibles pour ces trois catégories est disponible au format JSON ici.
Le fichier JSON contenant les id
des params et des query strings chronicles
modes
genres
events
machine
categories
(obtenu via la requête GET config) est disponible ici.
Par exemple, si vous souhaitez obtenir la liste des jeux PS4 de genre action RPG et de forme multi en ligne, votre chaîne de queries devra être platform=ps4&gameGenre=action_rpg&gameMode=multiplayer_online
.
De plus, les queries offset et limit, de type int, représentent respectivement le numéro du jeu dans la liste à partir duquel les jeux sont listés (par défaut 0) et le nombre du jeu à lister (par défaut 100).
Enfin, le query release désigne la date de sortie du jeu, compatible seulement avec les catégories best
et releases
. Il peut s'agir d'une année ou d'une décennie (dans ce cas, la valeur du query représentera la première année de la décennie suivie du caractère 'd' ; par exemple, 2010d
pour les années 2010). Pour les jeux pas encore sortis, ce query peut également contenir le mois (par exemple : 2024-07
pour juillet 2024). Incompatible avec la catégorie currentBest
.
Liste des endpoints connus
PATH | PARAMS | QUERY | DESCRIPTION |
---|---|---|---|
games/{gameCategory}/popularity.desc | gameCategory str | platform str gameGenre str |
Renvoie la liste des jeux de la catégorie {gameCategory} triés par popularité décroissante et satisfaisant les queries.
|
games/{gameCategory}/editorialRating.desc | gameCategory str | platform str gameGenre str |
Renvoie la liste des jeux tendances triés par note décroissante des rédacteurs JVC et satisfaisant les queries. |
games/{gameCategory}/releaseDate.asc | gameCategory str | platform str gameGenre str |
Renvoie la liste des jeux attendus triés par date de sortie croissante et satisfaisant les queries. |
games/{gameCategory}/title.asc | gameCategory str | platform str gameGenre str |
Renvoie la liste des jeux attendus triés par titre croissant et satisfaisant les queries. |
Ancienne API (dépréciée)
Le contenu qui va suivre concerne l'ancienne API qui n'existe plus.
Identification
L'API utilise le nom de domaine ws.jeuxvideo.com. Pour y accéder, il faut utiliser un des identifiants ci-dessous (authentification HTTP basique).
Tous ces identifiants fonctionnement actuellement, et je n'ai pas repéré de page semblant afficher un comportement différent selon.
Utilisateur | Mot de passe | |
---|---|---|
Android 1.0 | appandr | e32!cdf |
Android 2.0.3 | app_and_gnw | FC?4554? |
Android 2.5 | app_and_ms | D9!mVR4c |
Android MP | app_ag_jvmp | LXnb45=d# |
Android Tab | nex12sz | GT4!V2cT |
iPhone | app_ios_nw | W!P45-R |
iPad | ip45de | XpD5!FT |
L'API est accessible en HTTP et en HTTPS, préférez la version HTTPS !
Utilisation
Connexion
Pour vous connecter, utilisez la page mon_compte/connexion.php.
Voici les paramètres à envoyer (GET ou POST) :
Paramètre | Valeur |
---|---|
newnom | Le pseudo de l'utilisateur. |
stamp | Le timestamp (le nombre de secondes depuis le 1er janvier 1970) à l'heure où la requête est envoyée. |
hash | Un hash MD5 sous la forme : md5(pseudo + motDePasse + "OpX234" + stamp) OpX234 est un salt. |
Vous renconterez potentiellement une erreur vous demandant de remplir un captcha, avec un lien vers l'image, et une balise params_form, qu'il faudra ajouter aux paramètres de la première requête, ainsi que le paramètre code avec la valeur du captcha.
Important : n'ouvrez pas le lien du captcha dans votre navigateur si vous êtes connecté à votre compte JVC ; un captcha différent est renvoyé selon la présence du cookie wenvjgol (qui ne doit PAS être présent pour afficher un captcha de l'API, sinon vous aurez systématiquement une erreur de captcha invalide).
En réponse, vous recevez plusieurs informations présentes dans la CDV, mais surtout le cookie wenvjgol, que vous devrez utiliser pour poster sur les forums et utiliser les messages privés.
Pour information, wenvjgol signifie logjvnew à l'envers, et c'est le cookie de session de JVC dont le nom a subi plusieurs transformations au fil des années.
Pour le renvoi de mot de passe, utilisez cette URL (en remplaçant Cisla par le pseudo ou bien l'adresse e-mail) :
https://ws.jeuxvideo.com/cgi-bin/passperdu_ws.cgi?email_pseudo=Cisla
Jeux, astuces, news...
URL | Description |
---|---|
00.machines_version.xml 00.version_tablette.xml |
Dernière version de l'application, informations sur la publicité, liste des consoles |
01.flux_jeux_nouveautes.xml 01.flux_jeux_prochainement.xml |
Listes de jeux |
01.jeux/21963.xml 01.jeux/details/21963.xml 01.jeux/videos/21963.xml 01.jeux/screen/21963.xml 03.preview/43689.xml 03.test_complet/9813.xml 01.jeux/news/21963.xml 05.jeu_astuce/41030.xml 05.astuce/33258.xml 01.jeux/screen_ast/38024.xml |
Informations sur le jeu (le nombre correspond à l'id) |
03.dossier/18270.xml 03.dossier/18270/1.xml |
Un dossier : 18270 est l'id du dossier, 1 est la page (si vous n'en mettez pas, vous avez le sommaire) |
02.flux_news.xml 02.flux_news-4.xml |
La liste des dernières news. Dans le deuxième exemple, en ajoutant le nombre 4, vous retournez 4 jours en arrière (ça peut aller jusqu'à 9) |
03.flux_articles_tests.xml 03.flux_articles_tests-4.xml 03.flux_articles_apercus.xml 03.flux_articles_apercus-4.xml 03.flux_articles_dossier.xml 03.flux_articles_dossier-4.xml 04.flux_videos_cliq.xml 04.flux_videos_cliq-4.xml 04.flux_videos_gaming.xml 04.flux_videos_gaming-4.xml 04.flux_videos_autres.xml 04.flux_videos_autres-4.xml 05.flux_astuces.xml 05.flux_astuces-4.xml 04.flux_videos_chroniques.xml 04.flux_videos_chroniques-4.xml 04.flux_toutes_les_videos.xml 04.flux_toutes_les_videos-4.xml |
Même chose pour les derniers articles, tests, astuces et aperçus |
02.news/1234.xml 02.news_screen/1234.xml |
Voir une news (le nombre correspond à l'id) |
ean.php?ean=0045496830144 | Voir le jeu associé au code-barre (EAN) 0045496830144 |
forums_index.xml | Liste des forums généraux |
search_n/mario search/mario search_sug/mario search_forums/mario search_forums_sug/mario |
Effectuer une recherche dans le nom des jeux ou des forums |
tab_suggest_blocs.xml tab_suggest_forums.xml |
À compléter |
cgi-bin/liste.cgi | À compléter |
Forums
Pour les forums, les URL sont les mêmes que pour JVC, à part que :
- Le .htm est transformé en .xml
- Le www.jeuxvideo.com est remplacé par un ws.jeuxvideo.com
- La chaîne de caractères à la fin de l'URL (comme blabla-15-18-ans ou nom-du-topic) est remplacée par un 0 (sauf pour la recherche).
Par exemple :
http://www.jeuxvideo.com/forums/0-50-0-1-0-1-0-blabla-15-18-ans.htm
Devient :
https://ws.jeuxvideo.com/forums/0-50-0-1-0-1-0-0.xml
Exemples d'URL :
URL | Description |
---|---|
https://ws.jeuxvideo.com/forums/0-50-0-1-0-1-0-0.xml | La liste des sujets |
https://ws.jeuxvideo.com/forums/0-50-0-1-0-1-2-cactus.xml | Rechercher « cactus » dans le titre des topics |
https://ws.jeuxvideo.com/forums/1-50-1-1-0-1-0-0.xml | Un topic |
https://ws.jeuxvideo.com/forums/3-50-0-1-0-1-0-0.xml | Formulaire pour créer un topic |
https://ws.jeuxvideo.com/forums/3-50-128244545-1-0-1-0-0.xml | 10 derniers messages d'un topic + formulaire |
https://ws.jeuxvideo.com/forums/5-50-128244545-1-0-1-0-0.xml | Formulaire de réponse à un topic |
https://ws.jeuxvideo.com/cgi-bin/jvforums/forums.cgi | Envoyer un message (avec les données POST) |
https://ws.jeuxvideo.com/profil/cisla.xml | Voir une CDV |
Pour envoyer un message ou créer un topic : aux données de formulaires qui vous sont communiquées dans la balise params_form, vous devez ajouter le paramètre yournewmessage qui contient le message et newsujet qui contient le titre du topic (si vous créez un nouveau topic). Vous devez ensuite attendre une seconde avant d'envoyer les données POST à la page forums.cgi. En cas de captcha à gérer, vous devez répéter l'opération à partir des informations qu'on vous envoie en réponse, avec la solution du captcha en paramètre code.
Vous devez être connecté (envoyer le cookie coniunctio) pour récupérer et envoyer un formulaire.
Le même formulaire peut aussi bien être envoyé au forums.cgi de ws.jeuxvideo.com qu'à celui de www.jeuxvideo.com, ce qui vous permet de choisir entre apparaître « via mobile » ou non. Les données POST à envoyer sont donc les mêmes sur toutes les versions de JVC.
Messages privés
L'id de l'utilisateur mentionnée ci-dessous correspond à la partie du cookie coniunctio qui se trouve avant le premier "$".
Une fois connecté, vous devez envoyer le cookie coniunctio à chaque requête.
URL | Description |
---|---|
jvmp.xml | Dernière version de l'application, liste des consoles et des smileys, lien vers la charte, pub activée ou non |
messages-prives/connexion_ws.php | Les paramètres sont les mêmes que pour la connexion depuis mon_compte/connexion.php, décrite plus haut. Cependant, la réponse XML contient quelques informations supplémentaires à propos des MP. |
messages-prives/boite-reception_ws.php | Permet de connaître le contenu de la boîte de réception. Paramètre à envoyer :
|
messages-prives/envoyes_ws.php | Permet de connaître la liste des messages envoyés. Paramètre à envoyer :
|
messages-prives/message_ws.php | Permet de lire le contenu d'un message privé. Paramètres à envoyer :
|
messages-prives/nouveau_ws.php | Permet d'envoyer un nouveau message privé. Paramètres à envoyer :
Il se peut également que vous ayez à gérer un code de confirmation. |
messages-prives/repondre_ws.php | Permet de répondre à un message privé. Paramètres à envoyer :
Il se peut également que vous ayez à gérer un code de confirmation. |
messages-prives/suggest_pseudo_ws.php | Liste quelques pseudos qui commencent par ce que l'utilisateur est en train de taper. Paramètres à envoyer :
|
messages-prives/indesirable_ws.php | Liste les utilisateurs ajoutés en indésirable. Paramètres à envoyer :
|
messages-prives/add_indesirable_ws.php | Permet d'ajouter un utilisateur dans les indésirables. Certaines données nécessaires pour former la requête doivent être récupérés avec info_alerte_ws.php. Paramètres à envoyer :
|
messages-prives/del_indesirable_ws.php | Permet d'enlever un utilisateur des indésirables. Paramètres à envoyer :
|
messages-prives/info_alerte_ws.php | Permet de connaître les informations qui permettront de faire une DDB ou une mise en indésirable sur un message. Paramètres à envoyer :
|
messages-prives/alerte_ws.php | Permet d'effectuer une DDB (ce qui est inutile, étant donné qu'elles ne sont jamais traitées). Certaines données nécessaires pour former la requête doivent être récupérés avec info_alerte_ws.php. Paramètres à envoyer :
|
messages-prives/connexion_valid_ws.php | Permet d'afficher le nombre de messages non-lus ainsi que l'URL de l'avatar. Paramètres à envoyer :
|
messages-prives/add_destinataire_ws.php | Permet d'ajouter un destinataire à un message privé. Paramètres à envoyer :
|
messages-prives/del_message_ws.php | Permet de supprimer un message privé. Paramètres à envoyer :
|
Ancienne rétro-ingéniérie
Décompilation
Cette section concerne la décompilation de l'application Android de JVC sous Linux.
Pour décompiler l'application JVC, commencez par récupérer le fichier APK ici (version 2.5). Ensuite, décompressez-le avec 7-Zip :
$ 7z x com.jeuxvideo-2.5.apk
Puis, transformez le .dex en .jar à l'aide de l'utilitaire dex2jar :
$ dex2jar classes.dex
Ensuite, vous pouvez ouvrir le .jar dans jd-gui, qui s'occupera de décompiler les fichiers :
$ jd-gui classes-dex2jar.jar
Déobfuscation
En parcourant le code source, vous verrez des choses de ce genre à la place des chaînes de caractères :
this.g.getString(2131230723)
Cela correspond à des chaînes de caractères stockées dans un fichier séparé, resources.arsc. Pour en extraire le contenu, nous allons utiliser l'utilitaire apktool :
$ apktool d com.jeuxvideo-2.5.apk strings
Ensuite, vous pourrez trouver dans le fichier strings/res/values/strings.xml la liste des chaînes de caractères associées à leurs variables, et dans strings/res/values/public.xml, la liste des variables associées aux nombres (en hexadécimal) comme 2131230723 que vous voyez dans le code décompilé. Ce n'est pas très pratique mais je n'ai pas trouvé d'outil qui modifie directement le code décompilé pour y intégrer les valeurs du resources.arsc (mais je n'ai pas beaucoup cherché non plus).
Ce n'est pas tout. À partir de la version 2.0.3 de l'application JVC, et pour toutes les version de l'application JVC MP, vous pourrez également voir dans le fichier strings.xml des variables telles que :
<string name="md5_a">290B2FB20CFD682C120BBBFFFE5928D9</string>
Ces chaînes de caractères (des URL et quelques salts) sont obfusquées. Après avoir lu un peu de code, j'ai trouvé comment les restituer. Voici un bout de Python qui montre comment faire :
from Crypto.Cipher import AES from passlib.utils.pbkdf2 import pbkdf2 cle = 'package android.content' cle = AES.new(pbkdf2(cle, cle, 10, 128/8, 'hmac-sha1')) string = '290B2FB20CFD682C120BBBFFFE5928D9' string = cle.decrypt(string.decode('hex')) print repr(string[:-ord(string[-1])])
Modifiez la clé selon l'application que vous décompilez:
- Pour l'application Jeuxvideo.com (version >= 2.0.3), c'est package com.jeuxvideo.activity
- Pour l'application Jeuxvideo.com (version >= 2.5), c'est package android.content
- Pour l'application Jeuxvideo.com MP (version >= 1.0), c'est package com.jeuxvideomp.activity
L'application pour tablettes n'est pas concernée par ce mécanisme d'obfuscation.
Liens externes
- API sur Wikipédia
- Lien du topic sur JVC pour poser vos questions
- L'application Android
- L'application Android des MP
- L'application iPhone
- L'application iPhone des MP
- L'APK de l'application Android :
- L'APK de l'application Android des MP :
- L'APK de l'application Android tablette :