comparison src/services/anilist.cc @ 369:47c9f8502269

*: clang-format all the things I've edited the formatting a bit. Now pointer asterisks (and reference ampersands) are on the variable instead of the type, as well as having newlines for function braces (but nothing else)
author Paper <paper@tflc.us>
date Fri, 25 Jul 2025 10:16:02 -0400
parents f81bed4e04ac
children
comparison
equal deleted inserted replaced
368:6d37a998cf91 369:47c9f8502269
1 #include "services/anilist.h" 1 #include "services/anilist.h"
2 #include "core/anime.h" 2 #include "core/anime.h"
3 #include "core/anime_db.h" 3 #include "core/anime_db.h"
4 #include "core/config.h"
4 #include "core/date.h" 5 #include "core/date.h"
5 #include "core/config.h"
6 #include "core/http.h" 6 #include "core/http.h"
7 #include "core/json.h" 7 #include "core/json.h"
8 #include "core/session.h" 8 #include "core/session.h"
9 #include "core/strings.h" 9 #include "core/strings.h"
10 #include "gui/translate/anilist.h" 10 #include "gui/translate/anilist.h"
68 "synonyms\n" \ 68 "synonyms\n" \
69 "description(asHtml: false)\n" 69 "description(asHtml: false)\n"
70 70
71 /* FIXME: why is this here */ 71 /* FIXME: why is this here */
72 72
73 static std::optional<nlohmann::json> SendJSONRequest(const nlohmann::json& data) { 73 static std::optional<nlohmann::json> SendJSONRequest(const nlohmann::json &data)
74 {
74 std::vector<std::string> headers = { 75 std::vector<std::string> headers = {
75 "Accept: application/json", 76 "Accept: application/json",
76 "Content-Type: application/json", 77 "Content-Type: application/json",
77 }; 78 };
78 79
79 if (!session.config.auth.anilist.auth_token.empty()) 80 if (!session.config.auth.anilist.auth_token.empty())
80 headers.push_back("Authorization: Bearer " + session.config.auth.anilist.auth_token); 81 headers.push_back("Authorization: Bearer " + session.config.auth.anilist.auth_token);
81 82
82 const std::string response = Strings::ToUtf8String(HTTP::Request("https://graphql.anilist.co", headers, data.dump(), HTTP::Type::Post)); 83 const std::string response =
84 Strings::ToUtf8String(HTTP::Request("https://graphql.anilist.co", headers, data.dump(), HTTP::Type::Post));
83 if (response.empty()) { 85 if (response.empty()) {
84 session.SetStatusBar(Strings::Translate("AniList: JSON request returned an empty result!")); 86 session.SetStatusBar(Strings::Translate("AniList: JSON request returned an empty result!"));
85 return std::nullopt; 87 return std::nullopt;
86 } 88 }
87 89
88 nlohmann::json out; 90 nlohmann::json out;
89 91
90 try { 92 try {
91 out = nlohmann::json::parse(response); 93 out = nlohmann::json::parse(response);
92 } catch (const std::exception& ex) { 94 } catch (const std::exception &ex) {
93 session.SetStatusBar(fmt::format(Strings::Translate("AniList: Failed to parse request JSON with error \"{}\"!"), ex.what())); 95 session.SetStatusBar(
96 fmt::format(Strings::Translate("AniList: Failed to parse request JSON with error \"{}\"!"), ex.what()));
94 return std::nullopt; 97 return std::nullopt;
95 } 98 }
96 99
97 if (out.contains("/errors"_json_pointer) && out.at("/errors"_json_pointer).is_array()) { 100 if (out.contains("/errors"_json_pointer) && out.at("/errors"_json_pointer).is_array()) {
98 for (const auto& error : out.at("/errors"_json_pointer)) 101 for (const auto &error : out.at("/errors"_json_pointer))
99 std::cerr << "AniList: Received an error in response: " 102 std::cerr << "AniList: Received an error in response: "
100 << JSON::GetString<std::string>(error, "/message"_json_pointer, "") << std::endl; 103 << JSON::GetString<std::string>(error, "/message"_json_pointer, "") << std::endl;
101 104
102 session.SetStatusBar(Strings::Translate("AniList: Received an error in response!")); 105 session.SetStatusBar(Strings::Translate("AniList: Received an error in response!"));
103 return std::nullopt; 106 return std::nullopt;
104 } 107 }
105 108
106 return out; 109 return out;
107 } 110 }
108 111
109 static void ParseListStatus(std::string status, Anime::Anime& anime) { 112 static void ParseListStatus(std::string status, Anime::Anime &anime)
113 {
110 static const std::unordered_map<std::string, Anime::ListStatus> map = { 114 static const std::unordered_map<std::string, Anime::ListStatus> map = {
111 {"CURRENT", Anime::ListStatus::Current }, 115 {"CURRENT", Anime::ListStatus::Current },
112 {"PLANNING", Anime::ListStatus::Planning }, 116 {"PLANNING", Anime::ListStatus::Planning },
113 {"COMPLETED", Anime::ListStatus::Completed}, 117 {"COMPLETED", Anime::ListStatus::Completed},
114 {"DROPPED", Anime::ListStatus::Dropped }, 118 {"DROPPED", Anime::ListStatus::Dropped },
115 {"PAUSED", Anime::ListStatus::Paused } 119 {"PAUSED", Anime::ListStatus::Paused }
116 }; 120 };
117 121
118 if (status == "REPEATING") { 122 if (status == "REPEATING") {
119 anime.SetUserIsRewatching(true); 123 anime.SetUserIsRewatching(true);
120 anime.SetUserStatus(Anime::ListStatus::Current); 124 anime.SetUserStatus(Anime::ListStatus::Current);
121 return; 125 return;
127 } 131 }
128 132
129 anime.SetUserStatus(map.at(status)); 133 anime.SetUserStatus(map.at(status));
130 } 134 }
131 135
132 static std::string ListStatusToString(const Anime::Anime& anime) { 136 static std::string ListStatusToString(const Anime::Anime &anime)
137 {
133 if (anime.GetUserIsRewatching() && anime.GetUserStatus() == Anime::ListStatus::Current) 138 if (anime.GetUserIsRewatching() && anime.GetUserStatus() == Anime::ListStatus::Current)
134 return "REWATCHING"; 139 return "REWATCHING";
135 140
136 switch (anime.GetUserStatus()) { 141 switch (anime.GetUserStatus()) {
137 case Anime::ListStatus::Planning: return "PLANNING"; 142 case Anime::ListStatus::Planning: return "PLANNING";
141 default: break; 146 default: break;
142 } 147 }
143 return "CURRENT"; 148 return "CURRENT";
144 } 149 }
145 150
146 static void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { 151 static void ParseTitle(const nlohmann::json &json, Anime::Anime &anime)
152 {
147 static const std::unordered_map<Anime::TitleLanguage, nlohmann::json::json_pointer> map = { 153 static const std::unordered_map<Anime::TitleLanguage, nlohmann::json::json_pointer> map = {
148 {Anime::TitleLanguage::Native, "/native"_json_pointer}, 154 {Anime::TitleLanguage::Native, "/native"_json_pointer },
149 {Anime::TitleLanguage::English, "/english"_json_pointer}, 155 {Anime::TitleLanguage::English, "/english"_json_pointer},
150 {Anime::TitleLanguage::Romaji, "/romaji"_json_pointer}, 156 {Anime::TitleLanguage::Romaji, "/romaji"_json_pointer },
151 }; 157 };
152 158
153 for (const auto& [language, ptr] : map) 159 for (const auto &[language, ptr] : map)
154 if (json.contains(ptr) && json[ptr].is_string()) 160 if (json.contains(ptr) && json[ptr].is_string())
155 anime.SetTitle(language, json[ptr]); 161 anime.SetTitle(language, json[ptr]);
156 } 162 }
157 163
158 static int ParseMediaJson(const nlohmann::json& json) { 164 static int ParseMediaJson(const nlohmann::json &json)
165 {
159 if (!json.contains("/id"_json_pointer) || !json["/id"_json_pointer].is_number()) { 166 if (!json.contains("/id"_json_pointer) || !json["/id"_json_pointer].is_number()) {
160 session.SetStatusBar(Strings::Translate("AniList: Failed to parse anime object!")); 167 session.SetStatusBar(Strings::Translate("AniList: Failed to parse anime object!"));
161 return 0; 168 return 0;
162 } 169 }
163 170
167 if (!id) { 174 if (!id) {
168 session.SetStatusBar(Strings::Translate("AniList: Failed to parse anime object!")); 175 session.SetStatusBar(Strings::Translate("AniList: Failed to parse anime object!"));
169 return 0; 176 return 0;
170 } 177 }
171 178
172 Anime::Anime& anime = Anime::db.items[id]; 179 Anime::Anime &anime = Anime::db.items[id];
173 anime.SetId(id); 180 anime.SetId(id);
174 anime.SetServiceId(Anime::Service::AniList, service_id); 181 anime.SetServiceId(Anime::Service::AniList, service_id);
175 182
176 if (json.contains("/idMal"_json_pointer) && json["/idMal"_json_pointer].is_number()) 183 if (json.contains("/idMal"_json_pointer) && json["/idMal"_json_pointer].is_number())
177 anime.SetServiceId(Anime::Service::MyAnimeList, Strings::ToUtf8String(json["/idMal"_json_pointer].get<int>())); 184 anime.SetServiceId(Anime::Service::MyAnimeList, Strings::ToUtf8String(json["/idMal"_json_pointer].get<int>()));
180 187
181 anime.SetEpisodes(JSON::GetNumber(json, "/episodes"_json_pointer, 0)); 188 anime.SetEpisodes(JSON::GetNumber(json, "/episodes"_json_pointer, 0));
182 anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString<std::string>(json, "/format"_json_pointer, ""))); 189 anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString<std::string>(json, "/format"_json_pointer, "")));
183 190
184 anime.SetAiringStatus( 191 anime.SetAiringStatus(
185 Translate::AniList::ToSeriesStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""))); 192 Translate::AniList::ToSeriesStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, "")));
186 193
187 if (json.contains("/startDate"_json_pointer) && json["/startDate"_json_pointer].is_object()) 194 if (json.contains("/startDate"_json_pointer) && json["/startDate"_json_pointer].is_object())
188 anime.SetStartedDate(Date(json["/startDate"_json_pointer])); 195 anime.SetStartedDate(Date(json["/startDate"_json_pointer]));
189 196
190 anime.SetCompletedDate(json.contains("/endDate"_json_pointer) && json["/endDate"_json_pointer].is_object() 197 anime.SetCompletedDate(json.contains("/endDate"_json_pointer) && json["/endDate"_json_pointer].is_object()
191 ? Date(json["/endDate"_json_pointer]) 198 ? Date(json["/endDate"_json_pointer])
192 : anime.GetStartedDate()); 199 : anime.GetStartedDate());
193 200
194 anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, "")); 201 anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, ""));
195 202
196 anime.SetAudienceScore(JSON::GetNumber(json, "/averageScore"_json_pointer, 0)); 203 anime.SetAudienceScore(JSON::GetNumber(json, "/averageScore"_json_pointer, 0));
197 // anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString<std::string>(json, "/season"_json_pointer, ""))); 204 // anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString<std::string>(json, "/season"_json_pointer,
205 // "")));
198 anime.SetDuration(JSON::GetNumber(json, "/duration"_json_pointer, 0)); 206 anime.SetDuration(JSON::GetNumber(json, "/duration"_json_pointer, 0));
199 207
200 std::string synopsis = JSON::GetString<std::string>(json, "/description"_json_pointer, ""); 208 std::string synopsis = JSON::GetString<std::string>(json, "/description"_json_pointer, "");
201 Strings::TextifySynopsis(synopsis); 209 Strings::TextifySynopsis(synopsis);
202 anime.SetSynopsis(synopsis); 210 anime.SetSynopsis(synopsis);
206 214
207 { 215 {
208 std::vector<std::string> producers; 216 std::vector<std::string> producers;
209 217
210 if (json.contains("/studios/edges"_json_pointer) && json["/studios/edges"_json_pointer].is_array()) 218 if (json.contains("/studios/edges"_json_pointer) && json["/studios/edges"_json_pointer].is_array())
211 for (const auto& edge : json["/studios/edges"_json_pointer]) 219 for (const auto &edge : json["/studios/edges"_json_pointer])
212 if (edge.contains("/node/name"_json_pointer) && edge["/node/name"_json_pointer].is_string()) 220 if (edge.contains("/node/name"_json_pointer) && edge["/node/name"_json_pointer].is_string())
213 producers.push_back(edge["/node/name"_json_pointer].get<std::string>()); 221 producers.push_back(edge["/node/name"_json_pointer].get<std::string>());
214 222
215 anime.SetProducers(producers); 223 anime.SetProducers(producers);
216 } 224 }
217 225
218 return id; 226 return id;
219 } 227 }
220 228
221 static int ParseListItem(const nlohmann::json& json) { 229 static int ParseListItem(const nlohmann::json &json)
230 {
222 int id = ParseMediaJson(json["/media"_json_pointer]); 231 int id = ParseMediaJson(json["/media"_json_pointer]);
223 if (!id) 232 if (!id)
224 return 0; 233 return 0;
225 234
226 Anime::Anime& anime = Anime::db.items[id]; 235 Anime::Anime &anime = Anime::db.items[id];
227 236
228 anime.AddToUserList(); 237 anime.AddToUserList();
229 238
230 anime.SetUserScore(JSON::GetNumber(json, "/score"_json_pointer, 0)); 239 anime.SetUserScore(JSON::GetNumber(json, "/score"_json_pointer, 0));
231 anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0)); 240 anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0));
238 anime.SetUserTimeUpdated(JSON::GetNumber(json, "/updatedAt"_json_pointer, 0)); 247 anime.SetUserTimeUpdated(JSON::GetNumber(json, "/updatedAt"_json_pointer, 0));
239 248
240 return id; 249 return id;
241 } 250 }
242 251
243 static bool ParseList(const nlohmann::json& json) { 252 static bool ParseList(const nlohmann::json &json)
253 {
244 bool success = true; 254 bool success = true;
245 255
246 for (const auto& entry : json["entries"].items()) 256 for (const auto &entry : json["entries"].items())
247 if (!ParseListItem(entry.value())) 257 if (!ParseListItem(entry.value()))
248 success = false; 258 success = false;
249 259
250 return success; 260 return success;
251 } 261 }
252 262
253 int GetAnimeList() { 263 int GetAnimeList()
254 auto& auth = session.config.auth.anilist; 264 {
255 265 auto &auth = session.config.auth.anilist;
256 static constexpr std::string_view query = 266
257 "query ($id: Int) {\n" 267 static constexpr std::string_view query = "query ($id: Int) {\n"
258 " MediaListCollection (userId: $id, type: ANIME) {\n" 268 " MediaListCollection (userId: $id, type: ANIME) {\n"
259 " lists {\n" 269 " lists {\n"
260 " name\n" 270 " name\n"
261 " entries {\n" 271 " entries {\n"
262 " score\n" 272 " score\n"
263 " notes\n" 273 " notes\n"
264 " status\n" 274 " status\n"
265 " progress\n" 275 " progress\n"
266 " startedAt {\n" 276 " startedAt {\n"
267 " year\n" 277 " year\n"
268 " month\n" 278 " month\n"
269 " day\n" 279 " day\n"
270 " }\n" 280 " }\n"
271 " completedAt {\n" 281 " completedAt {\n"
272 " year\n" 282 " year\n"
273 " month\n" 283 " month\n"
274 " day\n" 284 " day\n"
275 " }\n" 285 " }\n"
276 " updatedAt\n" 286 " updatedAt\n"
277 " media {\n" 287 " media {\n" MEDIA_FIELDS " }\n"
278 MEDIA_FIELDS 288 " }\n"
279 " }\n" 289 " }\n"
280 " }\n" 290 " }\n"
281 " }\n" 291 "}\n";
282 " }\n"
283 "}\n";
284 292
285 // clang-format off 293 // clang-format off
286 nlohmann::json request = { 294 nlohmann::json request = {
287 {"query", query}, 295 {"query", query},
288 {"variables", { 296 {"variables", {
297 if (!response) 305 if (!response)
298 return 0; 306 return 0;
299 307
300 Anime::db.RemoveAllUserData(); 308 Anime::db.RemoveAllUserData();
301 309
302 const nlohmann::json& json = response.value(); 310 const nlohmann::json &json = response.value();
303 311
304 bool success = true; 312 bool success = true;
305 313
306 for (const auto& list : json["data"]["MediaListCollection"]["lists"].items()) 314 for (const auto &list : json["data"]["MediaListCollection"]["lists"].items())
307 if (!ParseList(list.value())) 315 if (!ParseList(list.value()))
308 success = false; 316 success = false;
309 317
310 if (success) 318 if (success)
311 session.SetStatusBar(Strings::Translate("AniList: Retrieved anime list successfully!")); 319 session.SetStatusBar(Strings::Translate("AniList: Retrieved anime list successfully!"));
312 320
313 return 1; 321 return 1;
314 } 322 }
315 323
316 /* return is a vector of anime ids */ 324 /* return is a vector of anime ids */
317 std::vector<int> Search(const std::string& search) { 325 std::vector<int> Search(const std::string &search)
318 static constexpr std::string_view query = 326 {
319 "query ($search: String) {\n" 327 static constexpr std::string_view query = "query ($search: String) {\n"
320 " Page (page: 1, perPage: 50) {\n" 328 " Page (page: 1, perPage: 50) {\n"
321 " media (search: $search, type: ANIME) {\n" 329 " media (search: $search, type: ANIME) {\n" MEDIA_FIELDS " }\n"
322 MEDIA_FIELDS 330 " }\n"
323 " }\n" 331 "}\n";
324 " }\n"
325 "}\n";
326 332
327 // clang-format off 333 // clang-format off
328 nlohmann::json json = { 334 nlohmann::json json = {
329 {"query", query}, 335 {"query", query},
330 {"variables", { 336 {"variables", {
335 341
336 const std::optional<nlohmann::json> response = SendJSONRequest(json); 342 const std::optional<nlohmann::json> response = SendJSONRequest(json);
337 if (!response) 343 if (!response)
338 return {}; 344 return {};
339 345
340 const nlohmann::json& result = response.value(); 346 const nlohmann::json &result = response.value();
341 347
342 /* FIXME: error handling here */ 348 /* FIXME: error handling here */
343 std::vector<int> ret; 349 std::vector<int> ret;
344 ret.reserve(result["/data/Page/media"_json_pointer].size()); 350 ret.reserve(result["/data/Page/media"_json_pointer].size());
345 351
346 for (const auto& media : result["/data/Page/media"_json_pointer].items()) 352 for (const auto &media : result["/data/Page/media"_json_pointer].items())
347 ret.push_back(ParseMediaJson(media.value())); 353 ret.push_back(ParseMediaJson(media.value()));
348 354
349 return ret; 355 return ret;
350 } 356 }
351 357
352 bool GetSeason(Anime::Season season) { 358 bool GetSeason(Anime::Season season)
359 {
353 static constexpr std::string_view query = 360 static constexpr std::string_view query =
354 "query ($season: MediaSeason!, $season_year: Int!, $page: Int) {\n" 361 "query ($season: MediaSeason!, $season_year: Int!, $page: Int) {\n"
355 " Page(page: $page) {\n" 362 " Page(page: $page) {\n"
356 " media(season: $season, seasonYear: $season_year, type: ANIME, sort: START_DATE) {\n" 363 " media(season: $season, seasonYear: $season_year, type: ANIME, sort: START_DATE) {\n" MEDIA_FIELDS " }\n"
357 MEDIA_FIELDS 364 " pageInfo {\n"
358 " }\n" 365 " total\n"
359 " pageInfo {\n" 366 " perPage\n"
360 " total\n" 367 " currentPage\n"
361 " perPage\n" 368 " lastPage\n"
362 " currentPage\n" 369 " hasNextPage\n"
363 " lastPage\n" 370 " }\n"
364 " hasNextPage\n" 371 " }\n"
365 " }\n" 372 "}\n";
366 " }\n"
367 "}\n";
368 373
369 int page = 0; 374 int page = 0;
370 bool has_next_page = true; 375 bool has_next_page = true;
371 376
372 while (has_next_page) { 377 while (has_next_page) {
373 nlohmann::json json = { 378 nlohmann::json json = {
374 {"query", query}, 379 {"query", query},
375 {"variables", { 380 {"variables",
376 {"season", Translate::AniList::ToString(season.season)}, 381 {
377 {"season_year", Strings::ToUtf8String(season.year)}, 382 {"season", Translate::AniList::ToString(season.season)},
378 {"page", page}, 383 {"season_year", Strings::ToUtf8String(season.year)},
379 }}, 384 {"page", page},
385 } },
380 }; 386 };
381 387
382 const std::optional<nlohmann::json> res = SendJSONRequest(json); 388 const std::optional<nlohmann::json> res = SendJSONRequest(json);
383 if (!res) 389 if (!res)
384 return false; 390 return false;
385 391
386 const nlohmann::json& result = res.value(); 392 const nlohmann::json &result = res.value();
387 393
388 for (const auto& media : result["/data/Page/media"_json_pointer].items()) 394 for (const auto &media : result["/data/Page/media"_json_pointer].items())
389 ParseMediaJson(media.value()); 395 ParseMediaJson(media.value());
390 396
391 has_next_page = JSON::GetBoolean(result, "/data/Page/pageInfo/hasNextPage"_json_pointer, false); 397 has_next_page = JSON::GetBoolean(result, "/data/Page/pageInfo/hasNextPage"_json_pointer, false);
392 if (has_next_page) 398 if (has_next_page)
393 page++; 399 page++;
394 } 400 }
395 401
396 return true; 402 return true;
397 } 403 }
398 404
399 int UpdateAnimeEntry(int id) { 405 int UpdateAnimeEntry(int id)
400 Anime::Anime& anime = Anime::db.items[id]; 406 {
407 Anime::Anime &anime = Anime::db.items[id];
401 if (!anime.IsInUserList()) 408 if (!anime.IsInUserList())
402 return 0; 409 return 0;
403 410
404 std::optional<std::string> service_id = anime.GetServiceId(Anime::Service::AniList); 411 std::optional<std::string> service_id = anime.GetServiceId(Anime::Service::AniList);
405 if (!service_id) 412 if (!service_id)
406 return 0; 413 return 0;
407 414
408 static constexpr std::string_view query = 415 static constexpr std::string_view query =
409 "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String," 416 "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String,"
410 " $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n" 417 " $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n"
411 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score," 418 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score,"
412 " notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n" 419 " notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n"
413 " id\n" 420 " id\n"
414 " }\n" 421 " }\n"
415 "}\n"; 422 "}\n";
416 // clang-format off 423 // clang-format off
417 nlohmann::json json = { 424 nlohmann::json json = {
418 {"query", query}, 425 {"query", query},
419 {"variables", { 426 {"variables", {
420 {"media_id", Strings::ToInt<int64_t>(service_id.value())}, 427 {"media_id", Strings::ToInt<int64_t>(service_id.value())},
431 438
432 const std::optional<nlohmann::json> res = SendJSONRequest(json); 439 const std::optional<nlohmann::json> res = SendJSONRequest(json);
433 if (!res) 440 if (!res)
434 return 0; 441 return 0;
435 442
436 const nlohmann::json& result = res.value(); 443 const nlohmann::json &result = res.value();
437 444
438 session.SetStatusBar(Strings::Translate("AniList: Anime entry updated successfully!")); 445 session.SetStatusBar(Strings::Translate("AniList: Anime entry updated successfully!"));
439 446
440 return JSON::GetNumber(result, "/data/SaveMediaListEntry/id"_json_pointer, 0); 447 return JSON::GetNumber(result, "/data/SaveMediaListEntry/id"_json_pointer, 0);
441 } 448 }
442 449
443 static int ParseUser(const nlohmann::json& json) { 450 static int ParseUser(const nlohmann::json &json)
444 auto& auth = session.config.auth.anilist; 451 {
452 auto &auth = session.config.auth.anilist;
445 453
446 return auth.user_id = JSON::GetNumber(json, "/id"_json_pointer, 0); 454 return auth.user_id = JSON::GetNumber(json, "/id"_json_pointer, 0);
447 } 455 }
448 456
449 bool AuthorizeUser() { 457 bool AuthorizeUser()
450 auto& auth = session.config.auth.anilist; 458 {
459 auto &auth = session.config.auth.anilist;
451 460
452 /* Prompt for PIN */ 461 /* Prompt for PIN */
453 QDesktopServices::openUrl(QUrl(Strings::ToQString("https://anilist.co/api/v2/oauth/authorize?client_id=" + 462 QDesktopServices::openUrl(QUrl(Strings::ToQString(
454 std::string(CLIENT_ID) + "&response_type=token"))); 463 "https://anilist.co/api/v2/oauth/authorize?client_id=" + std::string(CLIENT_ID) + "&response_type=token")));
455 464
456 bool ok; 465 bool ok;
457 QString token = QInputDialog::getText( 466 QString token = QInputDialog::getText(
458 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, 467 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal,
459 "", &ok); 468 "", &ok);
460 469
461 if (!ok || token.isEmpty()) 470 if (!ok || token.isEmpty())
462 return false; 471 return false;
463 472
464 auth.auth_token = Strings::ToUtf8String(token); 473 auth.auth_token = Strings::ToUtf8String(token);
465 474
466 session.SetStatusBar(Strings::Translate("AniList: Requesting user ID...")); 475 session.SetStatusBar(Strings::Translate("AniList: Requesting user ID..."));
467 476
468 static constexpr std::string_view query = 477 static constexpr std::string_view query = "query {\n"
469 "query {\n" 478 " Viewer {\n"
470 " Viewer {\n" 479 " id\n"
471 " id\n" 480 " }\n"
472 " }\n" 481 "}\n";
473 "}\n";
474 482
475 nlohmann::json json = { 483 nlohmann::json json = {
476 {"query", query} 484 {"query", query}
477 }; 485 };
478 486
479 /* SendJSONRequest handles status errors */ 487 /* SendJSONRequest handles status errors */
480 const std::optional<nlohmann::json> ret = SendJSONRequest(json); 488 const std::optional<nlohmann::json> ret = SendJSONRequest(json);
481 if (!ret) 489 if (!ret)
482 return 0; 490 return 0;
483 491
484 const nlohmann::json& result = ret.value(); 492 const nlohmann::json &result = ret.value();
485 493
486 if (ParseUser(result["data"]["Viewer"])) 494 if (ParseUser(result["data"]["Viewer"]))
487 session.SetStatusBar(Strings::Translate("AniList: Successfully retrieved user data!")); 495 session.SetStatusBar(Strings::Translate("AniList: Successfully retrieved user data!"));
488 else 496 else
489 session.SetStatusBar(Strings::Translate("AniList: Failed to retrieve user ID!")); 497 session.SetStatusBar(Strings::Translate("AniList: Failed to retrieve user ID!"));
490 498
491 return true; 499 return true;
492 } 500 }
493 501
494 } // namespace AniList 502 } // namespace AniList
495 } // namespace Services 503 } // namespace Services