Utiliser la JSR 353 pour accéder à la Web API de Guild Wars 2 - partie 5
par
, 22/10/2015 à 22h00 (2498 Affichages)
Non, je ne vous ai pas oublié depuis le temps ; j'ai juste été très très occupé par divers choses, entre autre la réécriture de la FAQ Java qui m'a un peu bouffé tout mon temps libre et toute mon énergie.
Fooo ! Quaggan est fatigué !!!!
Et il était temps que je finalise ce post ! En effet, comme l'eau a coulé sous les ponts depuis la dernière fois, on est désormais arrivé à la date de sortie de l’extension qui sera publiée ce vendredi 23 octobre 2015 !!
Fooo ! Quaggan est content !!!! Quaggan est content !!!!
Nous avons donc vu il y a quelques mois comment ajouter des clés d'application sur votre compte Guild Wars 2 de manière à pouvoir interroger la partie sécurisée de l'API. Cette fois-ci nous allons nous intéresser à l'historique des achats et des ventes de votre compte sur la Compagnie Commerciale du Lion Noir (le comptoir, les enchères ou encore l’hôtel des ventes).
JDK 9
Quelques nouvelles avant de nous pencher sur notre problème du jour : apparemment, une demande a été faite dans les propositions pour les futures extensions d'API du JDK9 pour que ce dernier supporte une "API légère pour consommer et générer des documents et des flux au format JSON." Si elle est intégrée, cette future Light-Weight JSON API permettra de se reposer sur une fonctionnalité core du JDK plutôt que sur des bibliothèques externes telles que la JSR 353 (et celle qui lui succèdera) ou des bibliothèques tierces qui sont plus orientées Java EE. Il semble de plus que cette proposition projette de se reposer sur les streams introduits dans le JDK 8, ce que ne fait pas la JSR 353 (qui semble se reposer sur des Map<String, JsonValue>). Évidement, tout comme pour les streams, ces fonctionnalités ne seront pas immédiatement disponibles sur Android sauf peut-être en passant par des compilateurs tiers tels que RoboVM. Je me pencherai sur cela en temps voulu lorsque le JDK 9 sera disponible.
Charabia légal
Guild Wars et Guild Wars 2 sont des marques déposées de ArenaNet et NCsoft. De plus, toutes les images restent la propriété de ArenaNet. De même, la Web API utilisée reste la propriété de ArenaNet qui peut décider de la retirer ou d'en interdire l'utilisation comme bon lui semble. Et bien sûr, je ne suis affilié ni avec ArenaNet, ni avec NCsoft.
NULL et plantages divers
Histoire de bien commencer, des modifications ont été opérées par ArenaNet sur l'API durant le temps qui s'est écoulé entre ces deux posts. Ainsi, désormais certains champs qui était auparavant absents dans la description des personnages retournés par l'API lorsqu'ils étaient vides (ex : guild quand le personnage n'avait pas de guilde active) contiennent désormais la valeur JavaScript null ce qui se traduit dans la JSR 353 par une valeur de type JsonValue.NULL. Cela a conduit à quelques soucis dans les applications ; en effet, lorsqu'on fait :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part String value = jsonObject.getString("guild");
Et que le JSON contient pour ce champ :
Code JSON : Sélectionner tout - Visualiser dans une fenêtre à part "guild": null;
Il serait logique cela retourne une String à la valeurnull.
Eh... ben... en fait... non ! Cela lève une ClassCastException ; ce qui est donc assez gênant. En effet, dans l'API l'objet JSON contient alors une valeur de type JsonValue.NULL ; et il en est de même lorsqu'on essaie de retirer des valeurs de type JsonObject ou JsonArray. Soit, pourquoi pas... mais le soucis est que l'API JSON-P est, je trouve, mal fichue de ce coté là et ne permet pas facilement de tester si la valeur sous-jacentes est JsonValue.NULL ou bien JsonString sans devoir écrire du code redondant pour le tester...
Sachant que la permission characters permet désormais d’accéder aux inventaires et à l’équipement des personnages et que la permission account permet désormais d’accéder à la banque du compte et que la valeur null est utilisée un peu partout pour gérer les cases d'inventaire, de banque et d’équipement vides mais aussi que certaines valeurs dans les descriptifs d'objets sont optionnelles, cela demande une gymnastique un peu particulière pour gérer ces cas de figure. Donc pour le coup, j'ai rajouté des méthodes utilitaires dans QueryUtils pour faire ces tests. Le code hébergé sur GitHub a été mis à jour pour supporter cela.
La suite a été moins joyeuse : courant juillet et août, une série de bug a frappé divers endpoints de l'API la rendant non-disponible pendant plusieurs heures ou jours (car survenant en cours de weekend). Sans qu'on y comprenne trop rien, plusieurs bugs ont provoqué la désactivation des clés d'application (marrant ça...). Et enfin, suite à une mise à jour du jeu, les identifiants des images et couleurs servant aux emblèmes de guilde, et qui sont normalement stockés sous forme d'entiers, sont soudainement devenus des chaines. Cela n'a l'air de rien mais, si le problème n'est probablement pas gênant en JavaScript compte tenu du manque de type fort de ce langage, il en est tout autre en Java : ici aussi, jsonObject.getInt(<id>) s'est mis à lever des exceptions de types ClassCastException puisque du coup la map interne de l'objet JSON contenait des valeurs de type JsonValue.STRING au lieu de JsonValue.NUMBER. Mais bon, cela été corrigé depuis ; il faut bien garder à l'esprit par contre que ce genre d'ennuis peut arriver n'importe quand puisque l'API est en construction constante.
Évolutions avec les Bêta
Suite à la mise en précommande de l'addon Heart of Thorns, début juillet, les pré-acheteurs ont pu bénéficier de plusieurs weekends de test bêta non-fermés. Ces weekends bêta ouverts (c'est-à-dire sans invitation) se sont succédés depuis à raison d'environ 1 par mois et ils ont permis de tester la nouvelle profession Revenant en modes JcE, JcJ et McM ainsi que les spécialisations des 8 autres professions. Désormais, les personnages Revenant reportent leur profession correctement (auparavant, elle était absente).
Les spécialisations élites des autres professions ont été révélées au fur et à mesure qu'on se rapproche de la date de sortie et rendues testables au fil des weekends bêta. Ces divers tests ont permis de vérifier que, au niveau de l'API, Les personnages bêta spécialisés conservent bien leur profession primaire. L'API a de plus depuis été mises à jour pour permettre de récupérer les spécialisations normales et élites des personnages via l'endpoint v2/specializations.
Ah oui, et le jeu est passé également en free to play. Cela veut dire que n'importe qui peut désormais se créer un compte Guild Wars 2 et y jouer sans bourse délier. Évidement les comptes gratuits sont soumis à des limitations (le nombre de personnages, l'envoie de courrier en jeu, etc.) mais les accès sécurisés via l'API sont tout à fait fonctionnels pour ces comptes-là. Attention cependant, il semble que les fonctionnalités des clés d'application ne deviennent actives que si le compte free to play contient au moins 1 personnage, ce qui implique de télécharger le jeu, de le lancer et de créer un nouveau perso.
Jeton de sécurité
Mais, revenons à nos moutons. Tout d'abord, votre compte doit disposer d'une clé d'application permettant d’accéder au comptoir et donc disposant de la permission tradingpost. Vous devrez créer un nouveau jeton via l'interface de gestion de votre compte si le précédent ne dispose pas des permissions requises. Vous découvrirez au passage de nouvelle permission pour la gestion des inventaires et d'autres encore pour les déblocages des skins, etc. que vous pouvez activer ou pas comme bon vous semble sur cette nouvelle clé.
Permissions disponibles aux 20 octobre 2015 :
- acccount - Informations basiques sur le compte. Cette permission est implicite. Elle permet d’accéder aux endpoints v2/account et v2/tokeninfo ;
- characters - Personnages du compte. Permet d’accéder à l'endpoint v2/characters ;
- builds - Aptitudes et spécialisations des personnages. Permet d’accéder à l'endpoint v2/characters/equipement et au champs specializations de v2/characters ;
- inventories - Contenu de la banque et au stockage des matériaux d'artisanat du compte, de l'inventaire et de l'équipement des personnages. Permet d’accéder aux endpoints v2/account/bank, v2/account/materials, v2/characters/equipement et v2/characters/inventory ;
- pvp - Statistiques JcJ du compte. Permet d’accéder aux endpoints v2/pvp/games et v2/pvp/stats ;
- tradingpost - Échanges du compte sur le comptoir. Permet d’accéder à l'endpoints v2/commerce/transactions ;
- unlocks - Skin débloqués sur le compte. Permet d’accéder aux endpoints v2/account/dyes et v2/account/skins ;
- wallet - Porte monnaie du compte. Permet d’accéder à l'endpoint v2/account/wallet.
Pour préparer la sortie de l'extension, l'interface de votre compte sur le site officiel a été modifiée, et les onglets sont désormais situés en haut de l’écran au lieu d’être sur la gauche. Cependant, l'onglet de gestion des clés d'application est toujours clairement identifié et la gestion des clés fonctionne tout comme précédemment.
Création de jeton sur la nouvelle interface de gestion du compte.
Contexte
J'avoue que sur sur ce coup là, j'ai eut les yeux plus gros que le ventre : en effet, pour afficher les ventes (initialement ; et plus tard les achats puisque tout était déjà implémenté ou presque), j'ai du implémenter le support de presque tous les types d'objets qui existent en jeu et que les joueurs peuvent s’échanger entre eux. Et cela fait un sacré paquet de classes, enums et fabriques à implémenter ! Également, il y a eut beaucoup de cas particuliers à gérer car certains champs JSON peuvent être optionnels dans certains types d'objets et pas dans d'autres. Donc du coup, vu que les exemples deviennent de plus en plus complexe, j'ai décidé que dans cette application je ne vous fournirai qu'une implémentation partielle du support des objets mais que j'allais m’atteler à réellement implémenter un mapping plus complet avec une meilleur gestion des annotations, fabriques ou des exceptions que j'utiliserai par la suite.
Parmi les autres choses à savoir et que j'ai apprises en glanant des informations ici et là sur le forum dédié au développement de l'API :
- La mise en cache des résultats des requêtes sur les historiques d'achats et de ventes est de 5 minutes (comme pour les personnages et le compte) contre 1 minute pour l'API publique permettant de récupérer les prix des objets au comptoir.
- L'historique ne conserve que vos 3 derniers mois de transaction sur le serveur.
- En accès direct (c'est-à-dire, sans pagination), on ne peut récupérer que 50 résultats maximum.
- En accès avec pagination, on peut récupérer jusqu’à 200 résultats maximum par page.
Cooodage
Dans notre requête, nous allons accéder à plusieurs endpoints nécessitant une authentification :
- https://api.guildwars2.com/v2/commerce/transactions/history/sells - Renvoie l'historique des ventes qui ont été finalisées (l'objet que vous avez mis en vente ne vous appartient plus et a été transmis à l'acheteur) ;
- https://api.guildwars2.com/v2/commerce/transactions/history/buys - Renvoie l'historique des achats qui ont été finalisées (votre demande d'achat a été validée et l'objet a été livré au comptoir) ;
- https://api.guildwars2.com/v2/commerce/transactions/current/sells - Renvoie les ventes en cours (vous avez placé une offre de vente sur le comptoir mais la vente n'est pas encore finalisée) ;
- https://api.guildwars2.com/v2/commerce/transactions/current/buys - Renvoie les achats en cours (vous avez placé une demande d'achat vente sur le comptoir mais l'achat n'est pas encore finalisé).
Ces 4 points d’accès vont vous retourner une liste d'objets de type Sale qui représentent des transactions que votre compte a effectué sur le comptoir. L'objet Sale objet est similaire au code JSON suivant :
Code JSON : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 { "id":2750477618, "item_id":24612, "price":1788, "quantity":1, "created":"2015-05-09T17:13:26+00:00", "purchased":"2015-05-09T17:24:20+00:00" }
Chaque transaction contient donc un identification, l'id d'un objet, la quantité de cet objet, le prix (en pièces de bronze), la date de la création de la transaction et sa date de clôture (cette dernière valeur est optionnelle). L'id de l'objet nous permet alors d'interroger l'endpoint non-sécurisé https://api.guildwars2.com/v2/items pour avoir les détails de l'objet ce qui inclura une URL vers son icône ou encore son nom et sa description localisées (donc en français si la requête est effectuée avec le bon paramètre).
Par exemple, un gâteau (un consommable destiné à modifier temporairement les statistiques du personnage) peut être codé comme suit :
Code JSON : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 { "name": "Barre aux baies d'Omnom", "type": "Consumable", "level": 80, "rarity": "Fine", "vendor_value": 33, "game_types": ["Dungeon", "Pve", "Wvw"], "flags": ["NoSell"], "restrictions": [], "id": 12452, "icon": "https://render.guildwars2.com/file/6BD5B65FBC6ED450219EC86DD570E59F4DA3791F/433643.png", "details": { "type": "Food", "duration_ms": 1800000, "description": "Découverte de magie +30%\nOr trouvé sur les monstres +40%\nExpérience à chaque ennemi tué +10%" } }
Un sac d'inventaire (qui est aussi un objet) peut être représenté comme :
Code JSON : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 { "name": "Boîte en carapace de karka ancestral", "description": "20 emplacements", "type": "Bag", "level": 0, "rarity": "Exotic", "vendor_value": 256, "game_types": ["Activity", "Dungeon", "Pve", "Wvw"], "flags": ["AccountBound", "NoMysticForge", "NoSalvage", "NoSell", "AccountBindOnUse"], "restrictions": [], "id": 36729, "icon": "https://render.guildwars2.com/file/CFFC295D57E0423CA5360AD4E669D2DAFCD9E580/511766.png", "details": { "no_sell_or_sort": false, "size": 20 } }
Alors qu'une arme peut être codée comme :
Code JSON : Sélectionner tout - Visualiser dans une fenêtre à part
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 { "name": "Bifrost", "type": "Weapon", "level": 80, "rarity": "Legendary", "vendor_value": 100000, "default_skin": 4683, "game_types": ["Activity", "Dungeon", "Pve", "Wvw"], "flags": ["HideSuffix", "NoSalvage", "NoSell", "AccountBindOnUse"], "restrictions": [], "id": 30698, "icon": "https://render.guildwars2.com/file/FD221A90427ADBD29B7E2DF8BDAF98BB16391162/456025.png", "details": { "type": "Staff", "damage_type": "Physical", "min_power": 1034, "max_power": 1166, "defense": 0, "infusion_slots": [{ "flags": ["Offense"] }, { "flags": ["Offense"] }], "suffix_item_id": 24572, "secondary_suffix_item_id": "" } }
Bref, étant donné que les détails d'un objet sont assez complexes et avec plein de sous-cas particuliers, je vais laisser les plus courageux d'entre vous aller fouiller dans le code dans le package test.data.item pour voir comment cela marche et comment sont implémentée les fabriques. Vous aurez également plus d'information sur le format JSON retourné en consultant le wiki officiel du jeu.
Pour le reste l'application est somme toute assez similaire à celle que nous avons faites précédemment qui listait les personnages du compte. La principale différence étant une présentation un peu plus détaillée dans les cellules affichées dans la liste et aussi au niveau des infobulles qui apparaissent en survolant une entrée pour essayer de se rapprocher de la présentation des objets en jeu. J'ai également du implémenter un cache d’icône pour conserver les icônes de chaque objet en local et éviter ainsi que l'affichage ne clignote trop à chaque rafraichissement du contenu de l'historique. Ici, les icônes sont conservée en mémoire dans une WeakHashMap ; une application un peu plus robuste voudra sans doute avoir un cache de fichiers image sur le disque ou dans une base de données.
Le package test.util contient une classe utilitaire utilisée pour encoder et décoder le descriptif des identifiants d'un objet dans le système de chat utilisé en jeu. En effet, de manière à pouvoir tester le code sur un panel assez large d'objets sur lesquels tester les divers classes et fabriques et également de manière à créer le mode démo, j'ai du implémenter, au moins partiellement, le support des chatcode. Ces code, écrits en codage Base64, qui sont un moyen, pour les joueurs, de lier des objets dans l'interface de messagerie du jeu en utilisant CRTL + clic gauche sur un objet lorsqu'ils dialoguent avec d'autres joueurs. Ils peuvent être également utilisés pour faire des requêtes sur le wiki officiel pour chercher des objets dans l'API qui n'ont pas forcement encore d'articles appropriés dans l'encyclopédie. En jeu, cela permet d'avoir un aperçu des apparences des armes ou armures ou encore de connaitre les statistiques ou les effets des objets, compétences, etc... Ici, j'ai uniquement implémenté la partie concernant le codage et le décodages des codes de descriptions d'objets car j'avais besoin de connaitre les ID d'objets à inclure pour le mode démo de l’application. Encore une fois, pour plus d'information sur ce codage, n’hésitez pas à vous référer au le wiki officiel du jeu qui est très bien documenté sur le sujet.
Au final
Comme précédemment, au lancement du programme, rien ne sera affiché ; vous devrez saisir votre jeton de sécurité dans le champ textuel approprié. Si vous voulez tester le programme en mode démo, il vous suffit d'utiliser la clé XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX au lieu d'un clé de sécurité normale. Bien sûr, si vous ne voulez retaper votre jeton d’accès dans le champ de saisi, vous pouvez également créer un fichier nommé settings.properties sur la racine du projet et y déclarer une valeur app.key contenant votre clé d'application ou la clé démo.
Code Properties : Sélectionner tout - Visualiser dans une fenêtre à part app.key=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Lorsque le programme affichera votre historique, vous devriez avoir un affichage similaire à la capture ci-dessous :
Ps : en mode démo, j'ai un peu mis n'importe quels types d'objets dans le faux historique, y compris des objets qu'on ne peut pas normalement vendre à d'autres joueurs.
Code source
La version la plus récente du projet, au format NetBeans, est accessible sur GitHub.
Réflexions sur la suite
Comme vous l'avez vu, désormais les applications sont devenues assez conséquentes et deviennent de plus en plus longues à coder ; sans parler de devoir gérer les erreurs ou bogues momentanés de l'API. De plus, la Web API de Guild Wars 2 évolue sans cesse : comme déjà mentionné, entre le moment auquel j'ai commencé cet article et sa publication, ArenaNet a ouvert de nouveaux endpoints permettant d’accéder à l’inventaire et au contenu du coffre en banque des personnages ; nous y reviendront une prochaine fois. Des modifications ont également été apportées au point d’accès character que nous avons vu précédemment pour retourner plus d’informations. Donc du coup, ça serait sans doute mieux de pouvoir désormais se reposer sur une bibliothèque qui évolue un peu plus librement que le projet de l’exemple lui-même.
La gestion même des permissions est également discutée sur le forum de l'API. Certains estimant qu'elles offrent trop de granularité (et donc que l'utilisateur s'y perdra lorsqu'il devra générer ses jetons d’accès) tandis que d'autre estiment au contraire qu'elles sont trop permissives. De nouveaux points d’accès sont en discussion, par exemple pour gérer le chat en jeu ou les membres, possessions et constructions des guildes ou encore faire évoluer les anciens point d’accès McM pour permettre de les adapter aux nouvelles cartes de l'addon.
Tout cela m’amène aux points de réflexions suivants :
- Cela fait beaucoup de factories à implémenter, ce qui implique beaucoup de code à maintenir, tester ou à réécrire en cas de modification ou de bug de l'API.
- L'approche d'utiliser un DOM (l'objet JSON est intégralement chargé en mémoire) n'est probablement pas très flexible (on suppose qu'on connait le contenu de l'objet JSON à l'avance) et consomme pas mal de mémoire. Peut-être vaudra-t-il mieux essayer un parser évènementiel de style SAX.
- Du coup, ça peut sans doute valoir le coup d'aller expérimenter du coté de l'injection pour la création des objets Java. Évidemment, on gagne en flexibilité et en découplage mais on perd toute la vérification à la compilation.
- L'approche DOM actuelle ne permet pas non-plus de gérer correctement les erreurs JSON retournées par la Web API. Il faudrait pouvoir récupérer les messages et les inclure dans des exceptions Java.
- De manière à rendre possible le fait de pouvoir coder des supports tiers pour des parser/injecteur JSON autres que la JSR 353, il va falloir bien découpler les interfaces et les implémentations des objets de la Web API mappés en Java.
- Il nous faut toujours accéder à l’entête HTTP pour récupérer certaines valeurs comme la taille et le nombre des pages ce que ne permet pas de faire l'approche simpliste actuelle.
Enfin, bon c'est assez pour cette fois-ci ^^
Et souvenez-vous, c'est Evon Grincelame qui aurait du gagner cette élection*. Ellen Kiel s'est révélée être un bien piètre capitaine. Cette élection était truquée ! Bouuuuuuuh...
*Dans l'univers du jeu, Evon Grincelame est le propriétaire de la Compagnie Commerciale du Lion Noir par laquelle passent toutes les transactions des joueurs ; donc le comptoir sur lequel nous avons fait des requêtes dans cet exemple. Vers le milieu de l’année 2013, la trame scénaristique des mises à jour a fait que, suite à un assassinat ayant laissé un poste vacant, les joueurs ont pu voter pour élire un nouveau membre au conseil des pirates de la ville de l'Arche du Lion, la ville centrale dans laquelle se rencontrent toutes les races, ce qui en fait de facto la capitale du jeu. Cette élection pirate opposait Evon, noble commerçant Charr très versé dans l'art d'engranger des bénéfices outrageux ou dans celui du versement de pot de vin, à la commandante humaine de la Garde du Lion Ellen Kiel tout juste auréolée de la gloire d'avoir appréhendé les auteurs de l'attentat (en fait ce sont les joueurs qui ont tout fait à sa place).