Mercurial > minori
diff src/services/anilist.cc @ 175:9b10175be389
dep/json: update to v3.11.3
anime/db: save anime list to database, very much untested and likely won't work as intended
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Thu, 30 Nov 2023 13:52:26 -0500 |
parents | 275da698697d |
children | 01d259b9c89f |
line wrap: on
line diff
--- a/src/services/anilist.cc Wed Nov 29 13:53:56 2023 -0500 +++ b/src/services/anilist.cc Thu Nov 30 13:52:26 2023 -0500 @@ -15,6 +15,9 @@ #include <QUrl> #include <chrono> #include <exception> + +#include <iostream> + #define CLIENT_ID "13706" using namespace nlohmann::literals::json_literals; @@ -34,24 +37,48 @@ void SetAuthToken(std::string const& auth_token) { session.config.auth.anilist.auth_token = auth_token; } bool Authenticated() const { return !AuthToken().empty(); } + bool IsValid() const { return UserId() && Authenticated(); } }; static Account account; std::string SendRequest(std::string data) { std::vector<std::string> headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json", - "Content-Type: application/json"}; + "Content-Type: application/json"}; return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers)); } +nlohmann::json SendJSONRequest(nlohmann::json data) { + std::string request = SendRequest(data.dump()); + if (request.empty()) { + std::cerr << "[AniList] JSON Request returned an empty result!" << std::endl; + return {}; + } + + auto ret = nlohmann::json::parse(request, nullptr, false); + if (ret.is_discarded()) { + std::cerr << "[AniList] Failed to parse request JSON!" << std::endl; + return {}; + } + + if (ret.contains("/errors"_json_pointer) && ret.at("/errors"_json_pointer).is_array()) { + for (const auto& error : ret.at("/errors"_json_pointer)) + std::cerr << "[AniList] Received an error in response: " << JSON::GetString<std::string>(error, "/message"_json_pointer, "") << std::endl; + + return {}; + } + + return ret; +} + void ParseListStatus(std::string status, Anime::Anime& anime) { std::unordered_map<std::string, Anime::ListStatus> map = { - {"CURRENT", Anime::ListStatus::CURRENT }, - {"PLANNING", Anime::ListStatus::PLANNING }, - {"COMPLETED", Anime::ListStatus::COMPLETED}, - {"DROPPED", Anime::ListStatus::DROPPED }, - {"PAUSED", Anime::ListStatus::PAUSED } - }; + {"CURRENT", Anime::ListStatus::CURRENT }, + {"PLANNING", Anime::ListStatus::PLANNING }, + {"COMPLETED", Anime::ListStatus::COMPLETED}, + {"DROPPED", Anime::ListStatus::DROPPED }, + {"PAUSED", Anime::ListStatus::PAUSED } + }; if (status == "REPEATING") { anime.SetUserIsRewatching(true); @@ -81,60 +108,39 @@ return "CURRENT"; } -Date ParseDate(const nlohmann::json& json) { - Date date; - /* JSON for Modern C++ warns here. I'm not too sure why, this code works when I set the - standard to C++17 :/ */ - if (json.contains("/year"_json_pointer) && json.at("/year"_json_pointer).is_number()) - date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); - else - date.VoidYear(); - - if (json.contains("/month"_json_pointer) && json.at("/month"_json_pointer).is_number()) - date.SetMonth(JSON::GetInt(json, "/month"_json_pointer)); - else - date.VoidMonth(); - - if (json.contains("/day"_json_pointer) && json.at("/day"_json_pointer).is_number()) - date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); - else - date.VoidDay(); - return date; -} - void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { - anime.SetNativeTitle(JSON::GetString(json, "/native"_json_pointer)); - anime.SetEnglishTitle(JSON::GetString(json, "/english"_json_pointer)); - anime.SetRomajiTitle(JSON::GetString(json, "/romaji"_json_pointer)); + anime.SetNativeTitle(JSON::GetString<std::string>(json, "/native"_json_pointer, "")); + anime.SetEnglishTitle(JSON::GetString<std::string>(json, "/english"_json_pointer, "")); + anime.SetRomajiTitle(JSON::GetString<std::string>(json, "/romaji"_json_pointer, "")); } int ParseMediaJson(const nlohmann::json& json) { - int id = JSON::GetInt(json, "/id"_json_pointer); + int id = JSON::GetNumber(json, "/id"_json_pointer); if (!id) return 0; + Anime::Anime& anime = Anime::db.items[id]; anime.SetId(id); ParseTitle(json.at("/title"_json_pointer), anime); - anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); - anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString(json, "/format"_json_pointer))); + anime.SetEpisodes(JSON::GetNumber(json, "/episodes"_json_pointer, 0)); + anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString<std::string>(json, "/format"_json_pointer, ""))); - anime.SetAiringStatus(Translate::AniList::ToSeriesStatus(JSON::GetString(json, "/status"_json_pointer))); + anime.SetAiringStatus(Translate::AniList::ToSeriesStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""))); - anime.SetAirDate(ParseDate(json["/startDate"_json_pointer])); + anime.SetAirDate(Date(json["/startDate"_json_pointer])); - anime.SetPosterUrl(JSON::GetString(json, "/coverImage/large"_json_pointer)); + anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, "")); - anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); - anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString(json, "/season"_json_pointer))); - anime.SetDuration(JSON::GetInt(json, "/duration"_json_pointer)); - anime.SetSynopsis(Strings::TextifySynopsis(JSON::GetString(json, "/description"_json_pointer))); + anime.SetAudienceScore(JSON::GetNumber(json, "/averageScore"_json_pointer, 0)); + anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString<std::string>(json, "/season"_json_pointer, ""))); + anime.SetDuration(JSON::GetNumber(json, "/duration"_json_pointer, 0)); + anime.SetSynopsis(Strings::TextifySynopsis(JSON::GetString<std::string>(json, "/description"_json_pointer, ""))); - if (json.contains("/genres"_json_pointer) && json["/genres"_json_pointer].is_array()) - anime.SetGenres(json["/genres"_json_pointer].get<std::vector<std::string>>()); - if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) - anime.SetTitleSynonyms(json["/synonyms"_json_pointer].get<std::vector<std::string>>()); + anime.SetGenres(JSON::GetArray<std::vector<std::string>>(json, "/genres"_json_pointer, {})); + anime.SetTitleSynonyms(JSON::GetArray<std::vector<std::string>>(json, "/synonyms"_json_pointer, {})); + return id; } @@ -145,15 +151,15 @@ anime.AddToUserList(); - anime.SetUserScore(JSON::GetInt(json, "/score"_json_pointer)); - anime.SetUserProgress(JSON::GetInt(json, "/progress"_json_pointer)); - ParseListStatus(JSON::GetString(json, "/status"_json_pointer), anime); - anime.SetUserNotes(JSON::GetString(json, "/notes"_json_pointer)); + anime.SetUserScore(JSON::GetNumber(json, "/score"_json_pointer, 0)); + anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0)); + ParseListStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""), anime); + anime.SetUserNotes(JSON::GetString<std::string>(json, "/notes"_json_pointer, "")); - anime.SetUserDateStarted(ParseDate(json["/startedAt"_json_pointer])); - anime.SetUserDateCompleted(ParseDate(json["/completedAt"_json_pointer])); + anime.SetUserDateStarted(Date(json["/startedAt"_json_pointer])); + anime.SetUserDateCompleted(Date(json["/completedAt"_json_pointer])); - anime.SetUserTimeUpdated(JSON::GetInt(json, "/updatedAt"_json_pointer)); + anime.SetUserTimeUpdated(JSON::GetNumber(json, "/updatedAt"_json_pointer, 0)); return id; } @@ -166,56 +172,61 @@ } int GetAnimeList() { + if (!account.IsValid()) { + std::cerr << "AniList: Account isn't valid!" << std::endl; + return 0; + } + /* NOTE: these should be in the qrc file */ const std::string query = "query ($id: Int) {\n" - " MediaListCollection (userId: $id, type: ANIME) {\n" - " lists {\n" - " name\n" - " entries {\n" - " score\n" - " notes\n" - " status\n" - " progress\n" - " startedAt {\n" - " year\n" - " month\n" - " day\n" - " }\n" - " completedAt {\n" - " year\n" - " month\n" - " day\n" - " }\n" - " updatedAt\n" - " media {\n" - " coverImage {\n" - " large\n" - " }\n" - " id\n" - " title {\n" - " romaji\n" - " english\n" - " native\n" - " }\n" - " format\n" - " status\n" - " averageScore\n" - " season\n" - " startDate {\n" - " year\n" - " month\n" - " day\n" - " }\n" - " genres\n" - " episodes\n" - " duration\n" - " synonyms\n" - " description(asHtml: false)\n" - " }\n" - " }\n" - " }\n" - " }\n" - "}\n"; + " MediaListCollection (userId: $id, type: ANIME) {\n" + " lists {\n" + " name\n" + " entries {\n" + " score\n" + " notes\n" + " status\n" + " progress\n" + " startedAt {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " completedAt {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " updatedAt\n" + " media {\n" + " coverImage {\n" + " large\n" + " }\n" + " id\n" + " title {\n" + " romaji\n" + " english\n" + " native\n" + " }\n" + " format\n" + " status\n" + " averageScore\n" + " season\n" + " startDate {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " genres\n" + " episodes\n" + " duration\n" + " synonyms\n" + " description(asHtml: false)\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n"; // clang-format off nlohmann::json json = { {"query", query}, @@ -224,11 +235,9 @@ }} }; // clang-format on - /* TODO: do a try catch here, catch any json errors and then call - Authorize() if needed */ - auto res = nlohmann::json::parse(SendRequest(json.dump())); - /* TODO: make sure that we actually need the wstring converter and see - if we can just get wide strings back from nlohmann::json */ + + auto res = SendJSONRequest(json); + for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { ParseList(list.value()); } @@ -257,12 +266,12 @@ **/ Anime::Anime& anime = Anime::db.items[id]; const std::string query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, " - "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput) {\n" - " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " - "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp) {\n" - " id\n" - " }\n" - "}\n"; + "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput) {\n" + " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " + "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp) {\n" + " id\n" + " }\n" + "}\n"; // clang-format off nlohmann::json json = { {"query", query}, @@ -277,41 +286,48 @@ }} }; // clang-format on - auto ret = nlohmann::json::parse(SendRequest(json.dump())); - return JSON::GetInt(ret, "/data/SaveMediaListEntry/id"_json_pointer); + + auto ret = SendJSONRequest(json); + + return JSON::GetNumber(ret, "/data/SaveMediaListEntry/id"_json_pointer, 0); } int ParseUser(const nlohmann::json& json) { - account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); - account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); + account.SetUsername(JSON::GetString<std::string>(json, "/name"_json_pointer, "")); + account.SetUserId(JSON::GetNumber(json, "/id"_json_pointer, 0)); return account.UserId(); } bool AuthorizeUser() { /* Prompt for PIN */ QDesktopServices::openUrl( - QUrl("https://anilist.co/api/v2/oauth/authorize?client_id=" CLIENT_ID "&response_type=token")); + QUrl("https://anilist.co/api/v2/oauth/authorize?client_id=" CLIENT_ID "&response_type=token")); + bool ok; QString token = QInputDialog::getText( - 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, - "", &ok); - if (ok && !token.isEmpty()) - account.SetAuthToken(Strings::ToUtf8String(token)); - else // fail + 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, + "", &ok); + + if (!ok || token.isEmpty()) return false; + + account.SetAuthToken(Strings::ToUtf8String(token)); + const std::string query = "query {\n" - " Viewer {\n" - " id\n" - " name\n" - " mediaListOptions {\n" - " scoreFormat\n" - " }\n" - " }\n" - "}\n"; + " Viewer {\n" + " id\n" + " name\n" + " mediaListOptions {\n" + " scoreFormat\n" // this will be used... eventually + " }\n" + " }\n" + "}\n"; nlohmann::json json = { - {"query", query} - }; - auto ret = nlohmann::json::parse(SendRequest(json.dump())); + {"query", query} + }; + + auto ret = SendJSONRequest(json); + ParseUser(ret["data"]["Viewer"]); return true; }