Mercurial > minori
view src/anilist.cpp @ 8:b1f73678ef61
update
text paragraphs are now their own objects, as they should be
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sat, 26 Aug 2023 03:39:34 -0400 |
parents | 07a9095eaeed |
children |
line wrap: on
line source
#include <QMessageBox> #include <QDesktopServices> #include <QInputDialog> #include <QLineEdit> #include <curl/curl.h> #include <chrono> #include <exception> #include <format> #include "json.h" #include "anilist.h" #include "anime.h" #include "config.h" #include "string_utils.h" #include "session.h" #define CLIENT_ID "13706" CURL* AniList::curl = NULL; CURLcode AniList::res = (CURLcode)0; size_t AniList::CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userdata) { ((std::string*)userdata)->append((char*)contents, size * nmemb); return size * nmemb; } std::string AniList::SendRequest(std::string data) { struct curl_slist *list = NULL; std::string userdata; curl = curl_easy_init(); if (curl) { list = curl_slist_append(list, "Accept: application/json"); list = curl_slist_append(list, "Content-Type: application/json"); std::string bearer = "Authorization: Bearer " + session.config.anilist.auth_token; list = curl_slist_append(list, bearer.c_str()); curl_easy_setopt(curl, CURLOPT_URL, "https://graphql.anilist.co"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback); /* FIXME: This sucks. When using HTTPS, we should ALWAYS make sure that our peer is actually valid. I assume the best way to go about this would be to bundle a certificate file, and if it's not found we should *prompt the user* and ask them if it's okay to contact AniList WITHOUT verification. If so, we're golden, and this flag will be set. If not, we should abort mission. For this program, it's probably fine to just contact AniList without HTTPS verification. However it should still be in the list of things to do... */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); res = curl_easy_perform(curl); curl_slist_free_all(list); if (res != CURLE_OK) { QMessageBox box(QMessageBox::Icon::Critical, "", QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res))); box.exec(); curl_easy_cleanup(curl); return ""; } curl_easy_cleanup(curl); return userdata; } return ""; } int AniList::GetUserId(std::string name) { #define QUERY "query ($name: String) {\n" \ " User (name: $name) {\n" \ " id\n" \ " }\n" \ "}\n" nlohmann::json json = { {"query", QUERY}, {"variables", { {"name", name} }} }; auto ret = nlohmann::json::parse(SendRequest(json.dump())); return JSON::GetInt(ret, "/data/User/id"_json_pointer); #undef QUERY } /* Maps to convert string forms to our internal enums */ std::map<std::string, enum AnimeWatchingStatus> StringToAnimeWatchingMap = { {"CURRENT", CURRENT}, {"PLANNING", PLANNING}, {"COMPLETED", COMPLETED}, {"DROPPED", DROPPED}, {"PAUSED", PAUSED}, {"REPEATING", REPEATING} }; std::map<std::string, enum AnimeAiringStatus> StringToAnimeAiringMap = { {"FINISHED", FINISHED}, {"RELEASING", RELEASING}, {"NOT_YET_RELEASED", NOT_YET_RELEASED}, {"CANCELLED", CANCELLED}, {"HIATUS", HIATUS} }; std::map<std::string, enum AnimeSeason> StringToAnimeSeasonMap = { {"WINTER", WINTER}, {"SPRING", SPRING}, {"SUMMER", SUMMER}, {"FALL", FALL} }; std::map<std::string, enum AnimeFormat> StringToAnimeFormatMap = { {"TV", TV}, {"TV_SHORT", TV_SHORT}, {"MOVIE", MOVIE}, {"SPECIAL", SPECIAL}, {"OVA", OVA}, {"ONA", ONA}, {"MUSIC", MUSIC}, {"MANGA", MANGA}, {"NOVEL", NOVEL}, {"ONE_SHOT", ONE_SHOT} }; int AniList::UpdateAnimeList(std::vector<AnimeList>* anime_lists, int id) { /* NOTE: these should be in the qrc file */ #define QUERY "query ($id: Int) {\n" \ " MediaListCollection (userId: $id, type: ANIME) {\n" \ " lists {\n" \ " name\n" \ " entries {\n" \ " score\n" \ " notes\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" \ " 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" nlohmann::json json = { {"query", QUERY}, {"variables", { {"id", id} }} }; /* 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 */ for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { /* why are the .key() values strings?? */ AnimeList anime_list; anime_list.name = JSON::GetString(list.value(), "/name"_json_pointer); for (const auto& entry : list.value()["entries"].items()) { Anime anime; anime.score = JSON::GetInt(entry.value(), "/score"_json_pointer); anime.progress = JSON::GetInt(entry.value(), "/progress"_json_pointer); anime.status = StringToAnimeWatchingMap[JSON::GetString(entry.value(), "/status"_json_pointer)]; anime.notes = JSON::GetString(entry.value(), "/notes"_json_pointer); anime.started.SetYear(JSON::GetInt(entry.value(), "/startedAt/year"_json_pointer)); anime.started.SetMonth(JSON::GetInt(entry.value(), "/startedAt/month"_json_pointer)); anime.started.SetDay(JSON::GetInt(entry.value(), "/startedAt/day"_json_pointer)); anime.completed.SetYear(JSON::GetInt(entry.value(), "/completedAt/year"_json_pointer)); anime.completed.SetMonth(JSON::GetInt(entry.value(), "/completedAt/month"_json_pointer)); anime.completed.SetDay(JSON::GetInt(entry.value(), "/completedAt/day"_json_pointer)); anime.updated = JSON::GetInt(entry.value(), "/updatedAt"_json_pointer); anime.title.native = JSON::GetString(entry.value(), "/media/title/native"_json_pointer); anime.title.english = JSON::GetString(entry.value(), "/media/title/english"_json_pointer); anime.title.romaji = JSON::GetString(entry.value(), "/media/title/romaji"_json_pointer); anime.id = JSON::GetInt(entry.value(), "/media/id"_json_pointer); anime.episodes = JSON::GetInt(entry.value(), "/media/episodes"_json_pointer); anime.type = StringToAnimeFormatMap[JSON::GetString(entry.value()["media"], "/media/format"_json_pointer)]; anime.airing = StringToAnimeAiringMap[JSON::GetString(entry.value()["media"], "/media/status"_json_pointer)]; anime.air_date.SetYear(JSON::GetInt(entry.value(), "/media/startDate/year"_json_pointer)); anime.air_date.SetMonth(JSON::GetInt(entry.value(), "/media/startDate/month"_json_pointer)); anime.air_date.SetDay(JSON::GetInt(entry.value(), "/media/startDate/day"_json_pointer)); anime.audience_score = JSON::GetInt(entry.value(), "/media/averageScore"_json_pointer); anime.season = StringToAnimeSeasonMap[JSON::GetString(entry.value(), "/media/season"_json_pointer)]; anime.duration = JSON::GetInt(entry.value(), "/media/duration"_json_pointer); anime.synopsis = StringUtils::TextifySynopsis(JSON::GetString(entry.value(), "/media/description"_json_pointer)); if (entry.value().contains("/media/genres"_json_pointer) && entry.value()["/media/genres"_json_pointer].is_array()) anime.genres = entry.value()["/media/genres"_json_pointer].get<std::vector<std::string>>(); if (entry.value().contains("/media/synonyms"_json_pointer) && entry.value()["/media/synonyms"_json_pointer].is_array()) anime.synonyms = entry.value()["/media/synonyms"_json_pointer].get<std::vector<std::string>>(); anime_list.Add(anime); } anime_lists->push_back(anime_list); } return 1; #undef QUERY } int AniList::Authorize() { /* Prompt for PIN */ QDesktopServices::openUrl(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()) { session.config.anilist.auth_token = token.toStdString(); } else { // fail return 0; } return 1; }