comparison src/services/anilist.cc @ 413:192da585a0a8 default tip

*: fix formatting the clang-format file was broken andjust using LLVM defaults. now it's fixed and is more in line with what I actually prefer my code to look like -- esp. with regards to tabulators
author Paper <paper@tflc.us>
date Sat, 25 Jul 2026 14:54:32 -0400
parents 02a670a8e1c4
children
comparison
equal deleted inserted replaced
412:05aed03e0111 413:192da585a0a8
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 {
75 std::vector<std::string> headers = { 75 std::vector<std::string> headers = {
76 "Accept: application/json", 76 "Accept: application/json",
77 "Content-Type: application/json", 77 "Content-Type: application/json",
78 }; 78 };
79 79
80 if (!session.config.auth.anilist.auth_token.empty()) 80 if (!session.config.auth.anilist.auth_token.empty())
81 headers.push_back("Authorization: Bearer " + session.config.auth.anilist.auth_token); 81 headers.push_back("Authorization: Bearer " + session.config.auth.anilist.auth_token);
82 82
83 const std::string response = 83 const std::string response =
84 Strings::ToUtf8String(HTTP::Request("https://graphql.anilist.co", headers, data.dump(), HTTP::Type::Post)); 84 Strings::ToUtf8String(HTTP::Request("https://graphql.anilist.co", headers, data.dump(), HTTP::Type::Post));
85 if (response.empty()) { 85 if (response.empty()) {
86 session.SetStatusBar(Strings::Translate("AniList: JSON request returned an empty result!")); 86 session.SetStatusBar(Strings::Translate("AniList: JSON request returned an empty result!"));
87 return std::nullopt; 87 return std::nullopt;
88 } 88 }
89 89
91 91
92 try { 92 try {
93 out = nlohmann::json::parse(response); 93 out = nlohmann::json::parse(response);
94 } catch (const std::exception &ex) { 94 } catch (const std::exception &ex) {
95 session.SetStatusBar( 95 session.SetStatusBar(
96 fmt::format(Strings::Translate("AniList: Failed to parse request JSON with error \"{}\"!"), ex.what())); 96 fmt::format(Strings::Translate("AniList: Failed to parse request JSON with error \"{}\"!"), ex.what()));
97 return std::nullopt; 97 return std::nullopt;
98 } 98 }
99 99
100 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()) {
101 for (const auto &error : out.at("/errors"_json_pointer)) 101 for (const auto &error : out.at("/errors"_json_pointer))
102 std::cerr << "AniList: Received an error in response: " 102 std::cerr << "AniList: Received an error in response: "
103 << JSON::GetString<std::string>(error, "/message"_json_pointer, "") << std::endl; 103 << JSON::GetString<std::string>(error, "/message"_json_pointer, "") << std::endl;
104 104
105 session.SetStatusBar(Strings::Translate("AniList: Received an error in response!")); 105 session.SetStatusBar(Strings::Translate("AniList: Received an error in response!"));
106 return std::nullopt; 106 return std::nullopt;
107 } 107 }
108 108
110 } 110 }
111 111
112 static void ParseListStatus(std::string status, Anime::Anime &anime) 112 static void ParseListStatus(std::string status, Anime::Anime &anime)
113 { 113 {
114 static const std::unordered_map<std::string, Anime::ListStatus> map = { 114 static const std::unordered_map<std::string, Anime::ListStatus> map = {
115 {"CURRENT", Anime::ListStatus::Current }, 115 {"CURRENT", Anime::ListStatus::Current },
116 {"PLANNING", Anime::ListStatus::Planning }, 116 {"PLANNING", Anime::ListStatus::Planning },
117 {"COMPLETED", Anime::ListStatus::Completed}, 117 {"COMPLETED", Anime::ListStatus::Completed},
118 {"DROPPED", Anime::ListStatus::Dropped }, 118 {"DROPPED", Anime::ListStatus::Dropped },
119 {"PAUSED", Anime::ListStatus::Paused } 119 {"PAUSED", Anime::ListStatus::Paused }
120 }; 120 };
121 121
122 if (status == "REPEATING") { 122 if (status == "REPEATING") {
123 anime.SetUserIsRewatching(true); 123 anime.SetUserIsRewatching(true);
124 anime.SetUserStatus(Anime::ListStatus::Current); 124 anime.SetUserStatus(Anime::ListStatus::Current);
154 } 154 }
155 155
156 static void ParseTitle(const nlohmann::json &json, Anime::Anime &anime) 156 static void ParseTitle(const nlohmann::json &json, Anime::Anime &anime)
157 { 157 {
158 static const std::unordered_map<Anime::TitleLanguage, nlohmann::json::json_pointer> map = { 158 static const std::unordered_map<Anime::TitleLanguage, nlohmann::json::json_pointer> map = {
159 {Anime::TitleLanguage::Native, "/native"_json_pointer }, 159 {Anime::TitleLanguage::Native, "/native"_json_pointer },
160 {Anime::TitleLanguage::English, "/english"_json_pointer}, 160 {Anime::TitleLanguage::English, "/english"_json_pointer},
161 {Anime::TitleLanguage::Romaji, "/romaji"_json_pointer }, 161 {Anime::TitleLanguage::Romaji, "/romaji"_json_pointer },
162 }; 162 };
163 163
164 for (const auto &[language, ptr] : map) 164 for (const auto &[language, ptr] : map)
165 if (json.contains(ptr) && json[ptr].is_string()) 165 if (json.contains(ptr) && json[ptr].is_string())
166 anime.SetTitle(language, json[ptr]); 166 anime.SetTitle(language, json[ptr]);
192 192
193 anime.SetEpisodes(JSON::GetNumber(json, "/episodes"_json_pointer, 0)); 193 anime.SetEpisodes(JSON::GetNumber(json, "/episodes"_json_pointer, 0));
194 anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString<std::string>(json, "/format"_json_pointer, ""))); 194 anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString<std::string>(json, "/format"_json_pointer, "")));
195 195
196 anime.SetAiringStatus( 196 anime.SetAiringStatus(
197 Translate::AniList::ToSeriesStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""))); 197 Translate::AniList::ToSeriesStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, "")));
198 198
199 if (json.contains("/startDate"_json_pointer) && json["/startDate"_json_pointer].is_object()) 199 if (json.contains("/startDate"_json_pointer) && json["/startDate"_json_pointer].is_object())
200 anime.SetStartedDate(Date(json["/startDate"_json_pointer])); 200 anime.SetStartedDate(Date(json["/startDate"_json_pointer]));
201 201
202 anime.SetCompletedDate(json.contains("/endDate"_json_pointer) && json["/endDate"_json_pointer].is_object() 202 anime.SetCompletedDate(json.contains("/endDate"_json_pointer) && json["/endDate"_json_pointer].is_object()
203 ? Date(json["/endDate"_json_pointer]) 203 ? Date(json["/endDate"_json_pointer])
204 : anime.GetStartedDate()); 204 : anime.GetStartedDate());
205 205
206 anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, "")); 206 anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, ""));
207 207
208 anime.SetAudienceScore(JSON::GetNumber(json, "/averageScore"_json_pointer, 0)); 208 anime.SetAudienceScore(JSON::GetNumber(json, "/averageScore"_json_pointer, 0));
209 // anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString<std::string>(json, "/season"_json_pointer, 209 // anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString<std::string>(json, "/season"_json_pointer,
268 int GetAnimeList() 268 int GetAnimeList()
269 { 269 {
270 auto &auth = session.config.auth.anilist; 270 auto &auth = session.config.auth.anilist;
271 271
272 static constexpr std::string_view query = "query ($id: Int) {\n" 272 static constexpr std::string_view query = "query ($id: Int) {\n"
273 " MediaListCollection (userId: $id, type: ANIME) {\n" 273 " MediaListCollection (userId: $id, type: ANIME) {\n"
274 " lists {\n" 274 " lists {\n"
275 " name\n" 275 " name\n"
276 " entries {\n" 276 " entries {\n"
277 " score\n" 277 " score\n"
278 " notes\n" 278 " notes\n"
279 " status\n" 279 " status\n"
280 " progress\n" 280 " progress\n"
281 " startedAt {\n" 281 " startedAt {\n"
282 " year\n" 282 " year\n"
283 " month\n" 283 " month\n"
284 " day\n" 284 " day\n"
285 " }\n" 285 " }\n"
286 " completedAt {\n" 286 " completedAt {\n"
287 " year\n" 287 " year\n"
288 " month\n" 288 " month\n"
289 " day\n" 289 " day\n"
290 " }\n" 290 " }\n"
291 " updatedAt\n" 291 " updatedAt\n"
292 " media {\n" MEDIA_FIELDS " }\n" 292 " media {\n" MEDIA_FIELDS " }\n"
293 " }\n" 293 " }\n"
294 " }\n" 294 " }\n"
295 " }\n" 295 " }\n"
296 "}\n"; 296 "}\n";
297 297
298 // clang-format off 298 // clang-format off
299 nlohmann::json request = { 299 nlohmann::json request = {
300 {"query", query}, 300 {"query", query},
301 {"variables", { 301 {"variables", {
328 328
329 /* return is a vector of anime ids */ 329 /* return is a vector of anime ids */
330 std::vector<int> Search(const std::string &search) 330 std::vector<int> Search(const std::string &search)
331 { 331 {
332 static constexpr std::string_view query = "query ($search: String) {\n" 332 static constexpr std::string_view query = "query ($search: String) {\n"
333 " Page (page: 1, perPage: 50) {\n" 333 " Page (page: 1, perPage: 50) {\n"
334 " media (search: $search, type: ANIME) {\n" MEDIA_FIELDS " }\n" 334 " media (search: $search, type: ANIME) {\n" MEDIA_FIELDS " }\n"
335 " }\n" 335 " }\n"
336 "}\n"; 336 "}\n";
337 337
338 // clang-format off 338 // clang-format off
339 nlohmann::json json = { 339 nlohmann::json json = {
340 {"query", query}, 340 {"query", query},
341 {"variables", { 341 {"variables", {
361 } 361 }
362 362
363 bool GetSeason(Anime::Season season) 363 bool GetSeason(Anime::Season season)
364 { 364 {
365 static constexpr std::string_view query = 365 static constexpr std::string_view query =
366 "query ($season: MediaSeason!, $season_year: Int!, $page: Int) {\n" 366 "query ($season: MediaSeason!, $season_year: Int!, $page: Int) {\n"
367 " Page(page: $page) {\n" 367 " Page(page: $page) {\n"
368 " media(season: $season, seasonYear: $season_year, type: ANIME, sort: START_DATE) {\n" MEDIA_FIELDS " }\n" 368 " media(season: $season, seasonYear: $season_year, type: ANIME, sort: START_DATE) {\n" MEDIA_FIELDS " }\n"
369 " pageInfo {\n" 369 " pageInfo {\n"
370 " total\n" 370 " total\n"
371 " perPage\n" 371 " perPage\n"
372 " currentPage\n" 372 " currentPage\n"
373 " lastPage\n" 373 " lastPage\n"
374 " hasNextPage\n" 374 " hasNextPage\n"
375 " }\n" 375 " }\n"
376 " }\n" 376 " }\n"
377 "}\n"; 377 "}\n";
378 378
379 int page = 0; 379 int page = 0;
380 bool has_next_page = true; 380 bool has_next_page = true;
381 381
382 while (has_next_page) { 382 while (has_next_page) {
383 nlohmann::json json = { 383 nlohmann::json json = {
384 {"query", query}, 384 {"query", query},
385 {"variables", 385 {"variables",
386 { 386 {
387 {"season", Translate::AniList::ToString(season.season)}, 387 {"season", Translate::AniList::ToString(season.season)},
388 {"season_year", Strings::ToUtf8String(season.year)}, 388 {"season_year", Strings::ToUtf8String(season.year)},
389 {"page", page}, 389 {"page", page},
390 } }, 390 } },
391 }; 391 };
392 392
393 const std::optional<nlohmann::json> res = SendJSONRequest(json); 393 const std::optional<nlohmann::json> res = SendJSONRequest(json);
394 if (!res) 394 if (!res)
395 return false; 395 return false;
416 std::optional<std::string> service_id = anime.GetServiceId(Anime::Service::AniList); 416 std::optional<std::string> service_id = anime.GetServiceId(Anime::Service::AniList);
417 if (!service_id) 417 if (!service_id)
418 return 0; 418 return 0;
419 419
420 static constexpr std::string_view query = 420 static constexpr std::string_view query =
421 "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String," 421 "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String,"
422 " $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n" 422 " $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n"
423 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score," 423 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score,"
424 " notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n" 424 " notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n"
425 " id\n" 425 " id\n"
426 " }\n" 426 " }\n"
427 "}\n"; 427 "}\n";
428 // clang-format off 428 // clang-format off
429 nlohmann::json json = { 429 nlohmann::json json = {
430 {"query", query}, 430 {"query", query},
431 {"variables", { 431 {"variables", {
432 {"media_id", Strings::ToInt<int64_t>(service_id.value())}, 432 {"media_id", Strings::ToInt<int64_t>(service_id.value())},
463 { 463 {
464 auto &auth = session.config.auth.anilist; 464 auto &auth = session.config.auth.anilist;
465 465
466 /* Prompt for PIN */ 466 /* Prompt for PIN */
467 QDesktopServices::openUrl(QUrl(Strings::ToQString( 467 QDesktopServices::openUrl(QUrl(Strings::ToQString(
468 "https://anilist.co/api/v2/oauth/authorize?client_id=" + std::string(CLIENT_ID) + "&response_type=token"))); 468 "https://anilist.co/api/v2/oauth/authorize?client_id=" + std::string(CLIENT_ID) + "&response_type=token")));
469 469
470 bool ok; 470 bool ok;
471 QString token = QInputDialog::getText( 471 QString token = QInputDialog::getText(0, "Credentials needed!",
472 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, 472 "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, "", &ok);
473 "", &ok);
474 473
475 if (!ok || token.isEmpty()) 474 if (!ok || token.isEmpty())
476 return false; 475 return false;
477 476
478 auth.auth_token = Strings::ToUtf8String(token); 477 auth.auth_token = Strings::ToUtf8String(token);
479 478
480 session.SetStatusBar(Strings::Translate("AniList: Requesting user ID...")); 479 session.SetStatusBar(Strings::Translate("AniList: Requesting user ID..."));
481 480
482 static constexpr std::string_view query = "query {\n" 481 static constexpr std::string_view query = "query {\n"
483 " Viewer {\n" 482 " Viewer {\n"
484 " id\n" 483 " id\n"
485 " }\n" 484 " }\n"
486 "}\n"; 485 "}\n";
487 486
488 nlohmann::json json = { 487 nlohmann::json json = {
489 {"query", query} 488 {"query", query}
490 }; 489 };
491 490
492 /* SendJSONRequest handles status errors */ 491 /* SendJSONRequest handles status errors */
493 const std::optional<nlohmann::json> ret = SendJSONRequest(json); 492 const std::optional<nlohmann::json> ret = SendJSONRequest(json);
494 if (!ret) 493 if (!ret)