WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
ArmoryImporter.cpp
Go to the documentation of this file.
1/*----------------------------------------------------------------------*\
2| This file is part of WoW Model Viewer |
3| |
4| WoW Model Viewer is free software: you can redistribute it and/or |
5| modify it under the terms of the GNU General Public License as |
6| published by the Free Software Foundation, either version 3 of the |
7| License, or (at your option) any later version. |
8| |
9| WoW Model Viewer is distributed in the hope that it will be useful, |
10| but WITHOUT ANY WARRANTY; without even the implied warranty of |
11| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12| GNU General Public License for more details. |
13| |
14| You should have received a copy of the GNU General Public License |
15| along with WoW Model Viewer. |
16| If not, see <http://www.gnu.org/licenses/>. |
17\*----------------------------------------------------------------------*/
18
19/*
20 * ArmoryImporter.cpp
21 *
22 * Created on: 9 dec. 2013
23 * Copyright: 2013 , WoW Model Viewer (http://wowmodelviewer.net)
24 */
25
26#include "ArmoryImporter.h"
27
28#include "HttpClient.h"
29#include "Logger.h"
30
31//#include "charcontrol.h"
32#include "CharInfos.h"
33#include "database.h" // ItemRecord
34#include "wow_enums.h"
35
36#include <sstream>
37#include <vector>
38
40
41enum
42{
44};
45
46bool ArmoryImporter::acceptURL(const std::string& url) const
47{
48 return (url.find("battle.net") != std::string::npos) ||
49 (url.find("worldofwarcraft.com") != std::string::npos) ||
50 (url.find("blizzard.com") != std::string::npos);
51}
52
54{
55 if (slot == 0)
56 return CS_HEAD;
57 if (slot == 2)
58 return CS_SHOULDER;
59 if (slot == 3)
60 return CS_SHIRT;
61 if (slot == 4)
62 return CS_CHEST;
63 if (slot == 5)
64 return CS_BELT;
65 if (slot == 6)
66 return CS_PANTS;
67 if (slot == 7)
68 return CS_BOOTS;
69 if (slot == 8)
70 return CS_BRACERS;
71 if (slot == 9)
72 return CS_GLOVES;
73 if (slot == 14)
74 return CS_CAPE;
75 if (slot == 15)
76 return CS_HAND_RIGHT;
77 if (slot == 16)
78 return CS_HAND_LEFT;
79 if (slot == 18)
80 return CS_TABARD;
81
82 return {};
83}
84
85CharInfos* ArmoryImporter::importChar(const std::string& url) const
86{
87 auto* result = new CharInfos();
88 nlohmann::json root;
89
90 const auto readStatus = readJSONValues(CHARACTER, url, root);
91 // LOG_INFO << "JSON Read Status:" << readStatus << "Root Count:" << root.count();
92
93 if (readStatus == 0 && root.size() > 0)
94 {
95 LOG_INFO << "Processing JSON Values...";
96
97 // No Gathering Errors Detected.
98 result->equipment.resize(NUM_CHAR_SLOTS);
99 result->itemModifierIds.resize(NUM_CHAR_SLOTS);
100
101 // Gather Race & Gender
102 result->raceId = root["playable_race"]["id"].get<int>();
103 result->gender = root["gender"]["name"].get<std::string>();
104
105 // Gather character customizations
106 for (const auto& customization : root["customizations"])
107 {
108 auto optionid = customization["option"]["id"].get<int>();
109 auto choiceid = customization["choice"]["id"].get<int>();
110 result->customizations.emplace_back(optionid, choiceid);
111 }
112
113
114 // Gather Items
115 result->hasTransmogGear = false;
116 for (const auto& item : root["items"])
117 {
118 const auto slot = armorySlotToCharSlot(item["internal_slot_id"].get<int>());
119 result->equipment[slot] = item["id"].get<int>();
120 result->itemModifierIds[slot] = item["item_appearance_modifier_id"].get<int>();
121 }
122
123
124 // Set proper eyeglow
125 if (root["class"].get<int>() == 6) // 6 = DEATH KNIGHT
126 result->eyeGlowType = EGT_DEATHKNIGHT;
127 else
128 result->eyeGlowType = EGT_DEFAULT;
129
130
131 // tabard (useful if guild tabard)
132 if (root.contains("guild_crest") && !root["guild_crest"].empty())
133 {
134 const auto& guildTabard = root["guild_crest"];
135 result->tabardIcon = guildTabard["emblem"]["id"].get<int>();
136 result->iconColor = guildTabard["emblem"]["color"]["id"].get<int>();
137 result->tabardBorder = guildTabard["border"]["id"].get<int>();
138 result->borderColor = guildTabard["border"]["color"]["id"].get<int>();
139 result->background = guildTabard["background"]["color"]["id"].get<int>();
140
141 result->customTabard = true;
142 }
143
144 result->valid = true;
145 }
146 else
147 {
148 LOG_ERROR << "Bad JSON Results:" << readStatus << "Root Size:" << root.size();
149 }
150
151 return result;
152}
153
154ItemRecord* ArmoryImporter::importItem(const std::string& url) const
155{
156 nlohmann::json root;
157 ItemRecord* result = nullptr;
158
159 if (readJSONValues(ITEM, url, root) == 0 && root.size() != 0)
160 {
161 // No Gathering Errors Detected.
162 result = new ItemRecord();
163
164 // Gather Race & Gender
165 result->id = root["id"].get<int>();
166 result->model = root["displayInfoId"].get<int>();
167 result->name = root["name"].get<std::string>();
168 result->itemclass = root["itemClass"].get<int>();
169 result->subclass = root["itemSubClass"].get<int>();
170 result->quality = root["quality"].get<int>();
171 result->type = root["inventoryType"].get<int>();
172 }
173
174 return result;
175}
176
177int ArmoryImporter::readJSONValues(ImportType type, const std::string& url, nlohmann::json& result) const
178{
179 std::string apiPage;
180 switch (type)
181 {
182 case CHARACTER:
183 {
184 /*
185 blizzard's API is mostly RESTful, with data being returned as JSON arrays.
186 Full documentation available here: http://blizzard.github.com/api-wow-docs/
187
188 Example: https://eu.api.blizzard.com/profile/wow/character/les-sentinelles/jeromnimo/appearance?namespace=profile-eu&locale=fr_FR
189
190 This will give us all the information we need inside of a JSON array.
191 (see JSON sample in previous version)
192
193 As you can see, this will give us almost all the data we need to properly rebuild the character.
194
195 */
196
197 const auto& strUrl(url);
198
199 std::string region;
200 std::string realm;
201 std::string charName;
202
203 // Helper to split a string by a delimiter
204 auto splitString = [](const std::string& s, char delim) {
205 std::vector<std::string> tokens;
206 std::istringstream stream(s);
207 std::string token;
208 while (std::getline(stream, token, delim))
209 tokens.push_back(token);
210 return tokens;
211 };
212
213 // Seems to redirect to worldofwarcraft.com as of Sept 2018.
214 if (strUrl.find("battle.net") != std::string::npos)
215 {
216 // Import from http://us.battle.net/wow/en/character/steamwheedle-cartel/Kjasi/simple
217
218 if ((strUrl.find("simple") == std::string::npos) &&
219 (strUrl.find("advanced") == std::string::npos))
220 {
221 LOG_ERROR << "Improperly Formatted URL. Lacks /simple and /advanced";
222 return 2;
223 }
224
225 const auto strList = splitString(strUrl.substr(7), '/');
226
227 auto dotPos = strList.at(0).find('.');
228 region = (dotPos != std::string::npos) ? strList.at(0).substr(0, dotPos) : strList.at(0);
229 realm = strList.at(strList.size() - 3);
230 auto qPos = strUrl.rfind('?');
231 charName = strList.at(strList.size() - 2);
232 if (qPos != std::string::npos)
233 charName = charName.substr(0, qPos - 1);
234 LOG_INFO << "Battle Net, CharName: " << charName << " Realm: " << realm << " Region: " << region;
235 }
236 else if ((strUrl.find("worldofwarcraft.com") != std::string::npos) || (url.find("blizzard.com") != std::string::npos))
237 {
238 // Import from https://worldofwarcraft.com/fr-fr/character/les-sentinelles/jeromnimo
239 // or (new form) https://worldofwarcraft.com/fr-fr/character/eu/les-sentinelles/jeromnimo
240 // or (new in 2023) https://worldofwarcraft.blizzard.com/en-gb/character/eu/silvermoon/n%C3%A1tnat
241
242 LOG_INFO << strUrl;
243 const auto strList = splitString(strUrl.substr(8), '/');
244
245 if (strList.size() > 5) // new form
246 region = strList.at(3);
247 else
248 region = strList.at(1);
249
250 realm = strList.at(strList.size() - 2);
251 charName = strList.at(strList.size() - 1);
252 auto qPos = charName.rfind('?');
253 if (qPos != std::string::npos)
254 charName = charName.substr(0, qPos);
255 LOG_INFO << "WoW.com, CharName:" << charName << "Realm:" << realm << "Region:" << region;
256
257 // I don't believe these should be translated, as websites tend not to translate URLs...
258 if ((region == "fr-fr") || (region == "en-gb"))
259 region = "eu";
260 else if (region == "en-us")
261 region = "us";
262 else if (region == "zh-tw")
263 region = "tw";
264 else if (region == "ko-kr")
265 region = "kr";
266 }
267 else
268 {
269 LOG_ERROR << "Improperly Formatted URL. The domain should be worldofwarcraft.com or blizzard.com";
270 return 2;
271 }
272
273 LOG_INFO << "Loading Battle.Net Armory. Region:" << region
274 << ", Realm:" << realm
275 << ", Character:" << charName;
276
277 apiPage = "https://wowmodelviewer.net/armory2.php?region=" + region + "&realm=" + realm + "&char=" + charName;
278 break;
279 }
280 case ITEM:
281 {
282 // url given is something like http://eu.battle.net/wow/fr/item/104673
283 // we need :
284 // 1. base battle.net address
285 // 2. locale (fr in above example) - Later
286 // 3. item number
287
288 // for the sake of simplicity, only handle english name for now
289
290 const auto& strUrl(url);
291 auto lastSlash = strUrl.rfind('/');
292 const auto itemNumber = (lastSlash != std::string::npos) ? strUrl.substr(lastSlash) : strUrl;
293
294 LOG_INFO << "Loading Battle.Net Armory. Item: " << itemNumber;
295
296 apiPage = "https://wowmodelviewer.net/armory.php?item=" + itemNumber;
297
298 break;
299 }
300 default:
301 LOG_ERROR << "Invalid Import Type: " << type;
302 return 3;
303 }
304
305 LOG_INFO << "Final API Page:" << apiPage;
306
307 const auto bts = getURLData(apiPage);
308 LOG_INFO << bts;
309 result = nlohmann::json::parse(bts, nullptr, false);
310 if (result.is_discarded())
311 return 1;
312 return 0;
313}
314
315std::string ArmoryImporter::getURLData(const std::string& inputUrl) const
316{
317 const auto resp = HttpClient::Get(inputUrl);
318 if (!resp.success)
319 {
320 LOG_ERROR << "HTTP request failed: " << resp.error;
321 return {};
322 }
323 return resp.body;
324}
325
326bool ArmoryImporter::hasMember(const nlohmann::json& check, const std::string& lookfor)
327{
328 return check.contains(lookfor);
329}
330
331bool ArmoryImporter::hasTransmog(const nlohmann::json& check)
332{
333 return check.contains("tooltipParams") && check["tooltipParams"].contains("transmogItem");
334}
CharSlots armorySlotToCharSlot(const int slot)
@ DEBUG_RESULTS
#define LOG_ERROR
Definition Logger.h:11
#define LOG_INFO
Definition Logger.h:10
static bool hasMember(const nlohmann::json &check, const std::string &lookfor)
static bool hasTransmog(const nlohmann::json &check)
CharInfos * importChar(const std::string &url) const override
bool acceptURL(const std::string &url) const override
int readJSONValues(ImportType type, const std::string &url, nlohmann::json &result) const
std::string getURLData(const std::string &inputUrl) const
ItemRecord * importItem(const std::string &url) const override
Stores imported character information (race, gender, equipment, customisations, tabard).
Definition CharInfos.h:8
Response Get(const std::string &url, const ProgressCallback &progress=nullptr)
Perform a synchronous HTTP(S) GET request.
A single equipment item record from the item database.
Definition database.h:26
int itemclass
Definition database.h:28
int quality
Definition database.h:28
int model
Definition database.h:28
std::string name
Display name of the item.
Definition database.h:27
int subclass
Definition database.h:28
CharSlots
Character equipment slot indices.
Definition wow_enums.h:5
@ NUM_CHAR_SLOTS
Definition wow_enums.h:21
@ CS_BRACERS
Definition wow_enums.h:13
@ CS_HAND_LEFT
Definition wow_enums.h:16
@ CS_BELT
Definition wow_enums.h:9
@ CS_CAPE
Definition wow_enums.h:17
@ CS_SHOULDER
Definition wow_enums.h:7
@ CS_PANTS
Definition wow_enums.h:11
@ CS_TABARD
Definition wow_enums.h:18
@ CS_CHEST
Definition wow_enums.h:12
@ CS_HAND_RIGHT
Definition wow_enums.h:15
@ CS_HEAD
Definition wow_enums.h:6
@ CS_BOOTS
Definition wow_enums.h:8
@ CS_SHIRT
Definition wow_enums.h:10
@ CS_GLOVES
Definition wow_enums.h:14
@ EGT_DEATHKNIGHT
Definition wow_enums.h:336
@ EGT_DEFAULT
Definition wow_enums.h:335