Mercurial > minori
annotate 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 |
| rev | line source |
|---|---|
| 7 | 1 #include <QMessageBox> |
| 2 #include <QDesktopServices> | |
| 3 #include <QInputDialog> | |
| 4 #include <QLineEdit> | |
| 2 | 5 #include <curl/curl.h> |
| 6 #include <chrono> | |
| 7 #include <exception> | |
| 8 #include <format> | |
| 7 | 9 #include "json.h" |
| 2 | 10 #include "anilist.h" |
| 11 #include "anime.h" | |
| 12 #include "config.h" | |
| 13 #include "string_utils.h" | |
| 7 | 14 #include "session.h" |
| 2 | 15 #define CLIENT_ID "13706" |
| 16 | |
| 7 | 17 CURL* AniList::curl = NULL; |
| 18 CURLcode AniList::res = (CURLcode)0; | |
| 19 | |
| 2 | 20 size_t AniList::CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userdata) { |
| 21 ((std::string*)userdata)->append((char*)contents, size * nmemb); | |
| 22 return size * nmemb; | |
| 23 } | |
| 24 | |
| 25 std::string AniList::SendRequest(std::string data) { | |
| 26 struct curl_slist *list = NULL; | |
| 27 std::string userdata; | |
| 28 curl = curl_easy_init(); | |
| 29 if (curl) { | |
| 30 list = curl_slist_append(list, "Accept: application/json"); | |
| 31 list = curl_slist_append(list, "Content-Type: application/json"); | |
| 32 std::string bearer = "Authorization: Bearer " + session.config.anilist.auth_token; | |
| 33 list = curl_slist_append(list, bearer.c_str()); | |
| 34 curl_easy_setopt(curl, CURLOPT_URL, "https://graphql.anilist.co"); | |
| 35 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); | |
| 36 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); | |
| 37 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); | |
| 38 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback); | |
| 39 /* FIXME: This sucks. When using HTTPS, we should ALWAYS make sure that our peer | |
| 40 is actually valid. I assume the best way to go about this would be to bundle a | |
| 41 certificate file, and if it's not found we should *prompt the user* and ask them | |
| 42 if it's okay to contact AniList WITHOUT verification. If so, we're golden, and this | |
| 43 flag will be set. If not, we should abort mission. | |
| 44 | |
| 45 For this program, it's probably fine to just contact AniList without | |
| 46 HTTPS verification. However it should still be in the list of things to do... */ | |
|
4
5af270662505
Set override functions as override
Paper <mrpapersonic@gmail.com>
parents:
3
diff
changeset
|
47 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); |
| 2 | 48 res = curl_easy_perform(curl); |
| 49 curl_slist_free_all(list); | |
| 50 if (res != CURLE_OK) { | |
| 51 QMessageBox box(QMessageBox::Icon::Critical, "", QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res))); | |
| 52 box.exec(); | |
| 53 curl_easy_cleanup(curl); | |
| 54 return ""; | |
| 55 } | |
| 56 curl_easy_cleanup(curl); | |
| 57 return userdata; | |
| 58 } | |
| 59 return ""; | |
| 60 } | |
| 61 | |
| 62 int AniList::GetUserId(std::string name) { | |
| 63 #define QUERY "query ($name: String) {\n" \ | |
| 64 " User (name: $name) {\n" \ | |
| 65 " id\n" \ | |
| 66 " }\n" \ | |
| 67 "}\n" | |
| 68 nlohmann::json json = { | |
| 69 {"query", QUERY}, | |
| 70 {"variables", { | |
| 71 {"name", name} | |
| 72 }} | |
| 73 }; | |
| 74 auto ret = nlohmann::json::parse(SendRequest(json.dump())); | |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
75 return JSON::GetInt(ret, "/data/User/id"_json_pointer); |
| 2 | 76 #undef QUERY |
| 77 } | |
| 78 | |
| 79 /* Maps to convert string forms to our internal enums */ | |
| 80 | |
| 81 std::map<std::string, enum AnimeWatchingStatus> StringToAnimeWatchingMap = { | |
| 82 {"CURRENT", CURRENT}, | |
| 83 {"PLANNING", PLANNING}, | |
| 84 {"COMPLETED", COMPLETED}, | |
| 85 {"DROPPED", DROPPED}, | |
| 86 {"PAUSED", PAUSED}, | |
| 87 {"REPEATING", REPEATING} | |
| 88 }; | |
| 89 | |
| 90 std::map<std::string, enum AnimeAiringStatus> StringToAnimeAiringMap = { | |
| 91 {"FINISHED", FINISHED}, | |
| 92 {"RELEASING", RELEASING}, | |
| 93 {"NOT_YET_RELEASED", NOT_YET_RELEASED}, | |
| 94 {"CANCELLED", CANCELLED}, | |
| 95 {"HIATUS", HIATUS} | |
| 96 }; | |
| 97 | |
| 98 std::map<std::string, enum AnimeSeason> StringToAnimeSeasonMap = { | |
| 99 {"WINTER", WINTER}, | |
| 100 {"SPRING", SPRING}, | |
| 101 {"SUMMER", SUMMER}, | |
| 102 {"FALL", FALL} | |
| 103 }; | |
| 104 | |
| 105 std::map<std::string, enum AnimeFormat> StringToAnimeFormatMap = { | |
| 106 {"TV", TV}, | |
| 107 {"TV_SHORT", TV_SHORT}, | |
| 108 {"MOVIE", MOVIE}, | |
| 109 {"SPECIAL", SPECIAL}, | |
| 110 {"OVA", OVA}, | |
| 111 {"ONA", ONA}, | |
| 112 {"MUSIC", MUSIC}, | |
| 113 {"MANGA", MANGA}, | |
| 114 {"NOVEL", NOVEL}, | |
| 115 {"ONE_SHOT", ONE_SHOT} | |
| 116 }; | |
| 117 | |
| 118 int AniList::UpdateAnimeList(std::vector<AnimeList>* anime_lists, int id) { | |
| 119 /* NOTE: these should be in the qrc file */ | |
| 120 #define QUERY "query ($id: Int) {\n" \ | |
| 121 " MediaListCollection (userId: $id, type: ANIME) {\n" \ | |
| 122 " lists {\n" \ | |
| 123 " name\n" \ | |
| 124 " entries {\n" \ | |
| 125 " score\n" \ | |
| 126 " notes\n" \ | |
| 127 " progress\n" \ | |
| 128 " startedAt {\n" \ | |
| 129 " year\n" \ | |
| 130 " month\n" \ | |
| 131 " day\n" \ | |
| 132 " }\n" \ | |
| 133 " completedAt {\n" \ | |
| 134 " year\n" \ | |
| 135 " month\n" \ | |
| 136 " day\n" \ | |
| 137 " }\n" \ | |
| 138 " updatedAt\n" \ | |
| 139 " media {\n" \ | |
| 140 " id\n" \ | |
| 141 " title {\n" \ | |
| 142 " romaji\n" \ | |
| 143 " english\n" \ | |
| 144 " native\n" \ | |
| 145 " }\n" \ | |
| 146 " format\n" \ | |
| 147 " status\n" \ | |
| 148 " averageScore\n" \ | |
| 149 " season\n" \ | |
| 150 " startDate {\n" \ | |
| 151 " year\n" \ | |
| 152 " month\n" \ | |
| 153 " day\n" \ | |
| 154 " }\n" \ | |
| 155 " genres\n" \ | |
| 156 " episodes\n" \ | |
| 157 " duration\n" \ | |
| 158 " synonyms\n" \ | |
| 159 " description(asHtml: false)\n" \ | |
| 160 " }\n" \ | |
| 161 " }\n" \ | |
| 162 " }\n" \ | |
| 163 " }\n" \ | |
| 164 "}\n" | |
| 165 nlohmann::json json = { | |
| 166 {"query", QUERY}, | |
| 167 {"variables", { | |
| 168 {"id", id} | |
| 169 }} | |
| 170 }; | |
| 171 /* TODO: do a try catch here, catch any json errors and then call | |
| 172 Authorize() if needed */ | |
| 173 auto res = nlohmann::json::parse(SendRequest(json.dump())); | |
| 174 /* TODO: make sure that we actually need the wstring converter and see | |
| 175 if we can just get wide strings back from nlohmann::json */ | |
| 176 for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { | |
| 177 /* why are the .key() values strings?? */ | |
| 178 AnimeList anime_list; | |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
179 anime_list.name = JSON::GetString(list.value(), "/name"_json_pointer); |
| 2 | 180 for (const auto& entry : list.value()["entries"].items()) { |
| 181 Anime anime; | |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
182 anime.score = JSON::GetInt(entry.value(), "/score"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
183 anime.progress = JSON::GetInt(entry.value(), "/progress"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
184 anime.status = StringToAnimeWatchingMap[JSON::GetString(entry.value(), "/status"_json_pointer)]; |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
185 anime.notes = JSON::GetString(entry.value(), "/notes"_json_pointer); |
| 2 | 186 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
187 anime.started.SetYear(JSON::GetInt(entry.value(), "/startedAt/year"_json_pointer)); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
188 anime.started.SetMonth(JSON::GetInt(entry.value(), "/startedAt/month"_json_pointer)); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
189 anime.started.SetDay(JSON::GetInt(entry.value(), "/startedAt/day"_json_pointer)); |
| 2 | 190 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
191 anime.completed.SetYear(JSON::GetInt(entry.value(), "/completedAt/year"_json_pointer)); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
192 anime.completed.SetMonth(JSON::GetInt(entry.value(), "/completedAt/month"_json_pointer)); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
193 anime.completed.SetDay(JSON::GetInt(entry.value(), "/completedAt/day"_json_pointer)); |
| 2 | 194 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
195 anime.updated = JSON::GetInt(entry.value(), "/updatedAt"_json_pointer); |
| 2 | 196 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
197 anime.title.native = JSON::GetString(entry.value(), "/media/title/native"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
198 anime.title.english = JSON::GetString(entry.value(), "/media/title/english"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
199 anime.title.romaji = JSON::GetString(entry.value(), "/media/title/romaji"_json_pointer); |
| 2 | 200 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
201 anime.id = JSON::GetInt(entry.value(), "/media/id"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
202 anime.episodes = JSON::GetInt(entry.value(), "/media/episodes"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
203 anime.type = StringToAnimeFormatMap[JSON::GetString(entry.value()["media"], "/media/format"_json_pointer)]; |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
204 |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
205 anime.airing = StringToAnimeAiringMap[JSON::GetString(entry.value()["media"], "/media/status"_json_pointer)]; |
| 2 | 206 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
207 anime.air_date.SetYear(JSON::GetInt(entry.value(), "/media/startDate/year"_json_pointer)); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
208 anime.air_date.SetMonth(JSON::GetInt(entry.value(), "/media/startDate/month"_json_pointer)); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
209 anime.air_date.SetDay(JSON::GetInt(entry.value(), "/media/startDate/day"_json_pointer)); |
| 2 | 210 |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
211 anime.audience_score = JSON::GetInt(entry.value(), "/media/averageScore"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
212 anime.season = StringToAnimeSeasonMap[JSON::GetString(entry.value(), "/media/season"_json_pointer)]; |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
213 anime.duration = JSON::GetInt(entry.value(), "/media/duration"_json_pointer); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
214 anime.synopsis = StringUtils::TextifySynopsis(JSON::GetString(entry.value(), "/media/description"_json_pointer)); |
| 2 | 215 |
| 7 | 216 if (entry.value().contains("/media/genres"_json_pointer) && entry.value()["/media/genres"_json_pointer].is_array()) |
| 217 anime.genres = entry.value()["/media/genres"_json_pointer].get<std::vector<std::string>>(); | |
| 218 if (entry.value().contains("/media/synonyms"_json_pointer) && entry.value()["/media/synonyms"_json_pointer].is_array()) | |
| 219 anime.synonyms = entry.value()["/media/synonyms"_json_pointer].get<std::vector<std::string>>(); | |
| 2 | 220 anime_list.Add(anime); |
| 221 } | |
| 222 anime_lists->push_back(anime_list); | |
| 223 } | |
| 224 return 1; | |
| 225 #undef QUERY | |
| 226 } | |
| 227 | |
| 228 int AniList::Authorize() { | |
|
6
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
229 /* Prompt for PIN */ |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
230 QDesktopServices::openUrl(QUrl("https://anilist.co/api/v2/oauth/authorize?client_id=" CLIENT_ID "&response_type=token")); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
231 bool ok; |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
232 QString token = QInputDialog::getText(0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, "", &ok); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
233 if (ok && !token.isEmpty()) { |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
234 session.config.anilist.auth_token = token.toStdString(); |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
235 } else { // fail |
|
1d82f6e04d7d
Update: add first parts to the settings dialog
Paper <mrpapersonic@gmail.com>
parents:
4
diff
changeset
|
236 return 0; |
| 2 | 237 } |
| 238 return 1; | |
| 239 } |
