comparison src/services/anilist.cc @ 291:9a88e1725fd2

*: refactor lots of stuff I forgot to put this into different commits, oops! anyway, it doesn't really matter *that* much since this is an unfinished hobby project anyway. once it starts getting stable commit history will be more important, but for now it's not that big of a deal
author Paper <paper@paper.us.eu.org>
date Sun, 12 May 2024 16:31:07 -0400
parents 53e3c015a973
children 2115488eb302
comparison
equal deleted inserted replaced
290:9347e2eaf6e5 291:9a88e1725fd2
16 #include <QMessageBox> 16 #include <QMessageBox>
17 #include <QUrl> 17 #include <QUrl>
18 18
19 #include <chrono> 19 #include <chrono>
20 #include <exception> 20 #include <exception>
21 #include <string_view>
21 22
22 #include <iostream> 23 #include <iostream>
23 24
24 using namespace nlohmann::literals::json_literals; 25 using namespace nlohmann::literals::json_literals;
25 26
26 namespace Services { 27 namespace Services {
27 namespace AniList { 28 namespace AniList {
28 29
29 static constexpr int CLIENT_ID = 13706; 30 static constexpr std::string_view CLIENT_ID = "13706";
30 31
31 class Account { 32 class Account {
32 public: 33 public:
33 int UserId() const { return session.config.auth.anilist.user_id; } 34 int UserId() const { return session.config.auth.anilist.user_id; }
34 void SetUserId(const int id) { session.config.auth.anilist.user_id = id; } 35 void SetUserId(const int id) { session.config.auth.anilist.user_id = id; }
40 bool IsValid() const { return UserId() && Authenticated(); } 41 bool IsValid() const { return UserId() && Authenticated(); }
41 }; 42 };
42 43
43 static Account account; 44 static Account account;
44 45
45 std::string SendRequest(std::string data) { 46 static std::string SendRequest(const std::string& data) {
46 std::vector<std::string> headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json", 47 std::vector<std::string> headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json",
47 "Content-Type: application/json"}; 48 "Content-Type: application/json"};
48 return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers)); 49 return Strings::ToUtf8String(HTTP::Request("https://graphql.anilist.co", headers, data, HTTP::Type::Post));
49 } 50 }
50 51
51 nlohmann::json SendJSONRequest(nlohmann::json data) { 52 static nlohmann::json SendJSONRequest(const nlohmann::json& data) {
52 std::string request = SendRequest(data.dump()); 53 std::string request = SendRequest(data.dump());
53 if (request.empty()) { 54 if (request.empty()) {
54 std::cerr << "[AniList] JSON Request returned an empty result!" << std::endl; 55 std::cerr << "[AniList] JSON Request returned an empty result!" << std::endl;
55 return {}; 56 return {};
56 } 57 }
70 } 71 }
71 72
72 return ret; 73 return ret;
73 } 74 }
74 75
75 void ParseListStatus(std::string status, Anime::Anime& anime) { 76 static void ParseListStatus(std::string status, Anime::Anime& anime) {
76 static const std::unordered_map<std::string, Anime::ListStatus> map = { 77 static const std::unordered_map<std::string, Anime::ListStatus> map = {
77 {"CURRENT", Anime::ListStatus::Current }, 78 {"CURRENT", Anime::ListStatus::Current },
78 {"PLANNING", Anime::ListStatus::Planning }, 79 {"PLANNING", Anime::ListStatus::Planning },
79 {"COMPLETED", Anime::ListStatus::Completed}, 80 {"COMPLETED", Anime::ListStatus::Completed},
80 {"DROPPED", Anime::ListStatus::Dropped }, 81 {"DROPPED", Anime::ListStatus::Dropped },
93 } 94 }
94 95
95 anime.SetUserStatus(map.at(status)); 96 anime.SetUserStatus(map.at(status));
96 } 97 }
97 98
98 std::string ListStatusToString(const Anime::Anime& anime) { 99 static std::string ListStatusToString(const Anime::Anime& anime) {
99 if (anime.GetUserIsRewatching() && anime.GetUserStatus() == Anime::ListStatus::Current) 100 if (anime.GetUserIsRewatching() && anime.GetUserStatus() == Anime::ListStatus::Current)
100 return "REWATCHING"; 101 return "REWATCHING";
101 102
102 switch (anime.GetUserStatus()) { 103 switch (anime.GetUserStatus()) {
103 case Anime::ListStatus::Planning: return "PLANNING"; 104 case Anime::ListStatus::Planning: return "PLANNING";
107 default: break; 108 default: break;
108 } 109 }
109 return "CURRENT"; 110 return "CURRENT";
110 } 111 }
111 112
112 void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { 113 static void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) {
113 nlohmann::json::json_pointer g = "/native"_json_pointer; 114 nlohmann::json::json_pointer g = "/native"_json_pointer;
114 if (json.contains(g) && json[g].is_string()) 115 if (json.contains(g) && json[g].is_string())
115 anime.SetTitle(Anime::TitleLanguage::Native, json[g]); 116 anime.SetTitle(Anime::TitleLanguage::Native, json[g]);
116 117
117 g = "/english"_json_pointer; 118 g = "/english"_json_pointer;
121 g = "/romaji"_json_pointer; 122 g = "/romaji"_json_pointer;
122 if (json.contains(g) && json[g].is_string()) 123 if (json.contains(g) && json[g].is_string())
123 anime.SetTitle(Anime::TitleLanguage::Romaji, json[g]); 124 anime.SetTitle(Anime::TitleLanguage::Romaji, json[g]);
124 } 125 }
125 126
126 int ParseMediaJson(const nlohmann::json& json) { 127 static int ParseMediaJson(const nlohmann::json& json) {
127 int id = JSON::GetNumber(json, "/id"_json_pointer); 128 int id = JSON::GetNumber(json, "/id"_json_pointer);
128 if (!id) 129 if (!id)
129 return 0; 130 return 0;
130 131
131 Anime::Anime& anime = Anime::db.items[id]; 132 Anime::Anime& anime = Anime::db.items[id];
157 anime.SetTitleSynonyms(JSON::GetArray<std::vector<std::string>>(json, "/synonyms"_json_pointer, {})); 158 anime.SetTitleSynonyms(JSON::GetArray<std::vector<std::string>>(json, "/synonyms"_json_pointer, {}));
158 159
159 return id; 160 return id;
160 } 161 }
161 162
162 int ParseListItem(const nlohmann::json& json) { 163 static int ParseListItem(const nlohmann::json& json) {
163 int id = ParseMediaJson(json["media"]); 164 int id = ParseMediaJson(json["media"]);
164 165
165 Anime::Anime& anime = Anime::db.items[id]; 166 Anime::Anime& anime = Anime::db.items[id];
166 167
167 anime.AddToUserList(); 168 anime.AddToUserList();
177 anime.SetUserTimeUpdated(JSON::GetNumber(json, "/updatedAt"_json_pointer, 0)); 178 anime.SetUserTimeUpdated(JSON::GetNumber(json, "/updatedAt"_json_pointer, 0));
178 179
179 return id; 180 return id;
180 } 181 }
181 182
182 int ParseList(const nlohmann::json& json) { 183 static int ParseList(const nlohmann::json& json) {
183 for (const auto& entry : json["entries"].items()) { 184 for (const auto& entry : json["entries"].items()) {
184 ParseListItem(entry.value()); 185 ParseListItem(entry.value());
185 } 186 }
186 return 1; 187 return 1;
187 } 188 }
334 **/ 335 **/
335 Anime::Anime& anime = Anime::db.items[id]; 336 Anime::Anime& anime = Anime::db.items[id];
336 if (!anime.IsInUserList()) 337 if (!anime.IsInUserList())
337 return 0; 338 return 0;
338 339
340 std::optional<std::string> service_id = anime.GetServiceId(Anime::Service::AniList);
341 if (!service_id)
342 return 0;
343
339 constexpr std::string_view query = 344 constexpr std::string_view query =
340 "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String, $start: " 345 "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String, $start: "
341 "FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n" 346 "FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n"
342 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score, notes: " 347 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score, notes: "
343 "$notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n" 348 "$notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n"
346 "}\n"; 351 "}\n";
347 // clang-format off 352 // clang-format off
348 nlohmann::json json = { 353 nlohmann::json json = {
349 {"query", query}, 354 {"query", query},
350 {"variables", { 355 {"variables", {
351 {"media_id", anime.GetId()}, 356 {"media_id", Strings::ToInt<int64_t>(service_id.value())},
352 {"progress", anime.GetUserProgress()}, 357 {"progress", anime.GetUserProgress()},
353 {"status", ListStatusToString(anime)}, 358 {"status", ListStatusToString(anime)},
354 {"score", anime.GetUserScore()}, 359 {"score", anime.GetUserScore()},
355 {"notes", anime.GetUserNotes()}, 360 {"notes", anime.GetUserNotes()},
356 {"start", anime.GetUserDateStarted().GetAsAniListJson()}, 361 {"start", anime.GetUserDateStarted().GetAsAniListJson()},
363 auto ret = SendJSONRequest(json); 368 auto ret = SendJSONRequest(json);
364 369
365 return JSON::GetNumber(ret, "/data/SaveMediaListEntry/id"_json_pointer, 0); 370 return JSON::GetNumber(ret, "/data/SaveMediaListEntry/id"_json_pointer, 0);
366 } 371 }
367 372
368 int ParseUser(const nlohmann::json& json) { 373 static int ParseUser(const nlohmann::json& json) {
369 account.SetUserId(JSON::GetNumber(json, "/id"_json_pointer, 0)); 374 account.SetUserId(JSON::GetNumber(json, "/id"_json_pointer, 0));
370 return account.UserId(); 375 return account.UserId();
371 } 376 }
372 377
373 bool AuthorizeUser() { 378 bool AuthorizeUser() {
374 /* Prompt for PIN */ 379 /* Prompt for PIN */
375 QDesktopServices::openUrl(QUrl(Strings::ToQString("https://anilist.co/api/v2/oauth/authorize?client_id=" + 380 QDesktopServices::openUrl(QUrl(Strings::ToQString("https://anilist.co/api/v2/oauth/authorize?client_id=" +
376 Strings::ToUtf8String(CLIENT_ID) + "&response_type=token"))); 381 std::string(CLIENT_ID) + "&response_type=token")));
377 382
378 bool ok; 383 bool ok;
379 QString token = QInputDialog::getText( 384 QString token = QInputDialog::getText(
380 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, 385 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal,
381 "", &ok); 386 "", &ok);