Mercurial > minori
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 |
