Mercurial > minori
annotate src/services/anilist.cc @ 187:9613d72b097e
*: multiple performance improvements
like marking `static const` when it makes sense...
date: change old stupid heap-based method to a structure which should
make copying the thing actually make a copy.
also many performance-based changes, like removing the std::tie
dependency and forward-declaring nlohmann json
*: replace every instance of QString::fromUtf8 to Strings::ToQString.
the main difference is that our function will always convert exactly
what is in the string, while some other times it would only convert
up to the nearest NUL byte
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Wed, 06 Dec 2023 13:43:54 -0500 |
parents | 62e336597bb7 |
children | 7cf53145de11 |
rev | line source |
---|---|
9 | 1 #include "services/anilist.h" |
2 #include "core/anime.h" | |
10 | 3 #include "core/anime_db.h" |
9 | 4 #include "core/config.h" |
76 | 5 #include "core/http.h" |
9 | 6 #include "core/json.h" |
7 #include "core/session.h" | |
8 #include "core/strings.h" | |
15 | 9 #include "gui/translate/anilist.h" |
187
9613d72b097e
*: multiple performance improvements
Paper <mrpapersonic@gmail.com>
parents:
185
diff
changeset
|
10 |
9613d72b097e
*: multiple performance improvements
Paper <mrpapersonic@gmail.com>
parents:
185
diff
changeset
|
11 #include <QDate> |
77 | 12 #include <QByteArray> |
9 | 13 #include <QDesktopServices> |
14 #include <QInputDialog> | |
15 #include <QLineEdit> | |
16 #include <QMessageBox> | |
10 | 17 #include <QUrl> |
187
9613d72b097e
*: multiple performance improvements
Paper <mrpapersonic@gmail.com>
parents:
185
diff
changeset
|
18 |
9 | 19 #include <chrono> |
20 #include <exception> | |
175 | 21 |
22 #include <iostream> | |
23 | |
64 | 24 using namespace nlohmann::literals::json_literals; |
11 | 25 |
63 | 26 namespace Services { |
27 namespace AniList { | |
9 | 28 |
185
62e336597bb7
anime list: add support for different score formats
Paper <mrpapersonic@gmail.com>
parents:
184
diff
changeset
|
29 constexpr int CLIENT_ID = 13706; |
62e336597bb7
anime list: add support for different score formats
Paper <mrpapersonic@gmail.com>
parents:
184
diff
changeset
|
30 |
9 | 31 class Account { |
32 public: | |
120 | 33 std::string Username() const { return session.config.auth.anilist.username; } |
34 void SetUsername(std::string const& username) { session.config.auth.anilist.username = username; } | |
9 | 35 |
120 | 36 int UserId() const { return session.config.auth.anilist.user_id; } |
37 void SetUserId(const int id) { session.config.auth.anilist.user_id = id; } | |
9 | 38 |
120 | 39 std::string AuthToken() const { return session.config.auth.anilist.auth_token; } |
40 void SetAuthToken(std::string const& auth_token) { session.config.auth.anilist.auth_token = auth_token; } | |
9 | 41 |
42 bool Authenticated() const { return !AuthToken().empty(); } | |
175 | 43 bool IsValid() const { return UserId() && Authenticated(); } |
10 | 44 }; |
9 | 45 |
46 static Account account; | |
47 | |
48 std::string SendRequest(std::string data) { | |
76 | 49 std::vector<std::string> headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json", |
175 | 50 "Content-Type: application/json"}; |
77 | 51 return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers)); |
9 | 52 } |
53 | |
175 | 54 nlohmann::json SendJSONRequest(nlohmann::json data) { |
55 std::string request = SendRequest(data.dump()); | |
56 if (request.empty()) { | |
57 std::cerr << "[AniList] JSON Request returned an empty result!" << std::endl; | |
58 return {}; | |
59 } | |
60 | |
61 auto ret = nlohmann::json::parse(request, nullptr, false); | |
62 if (ret.is_discarded()) { | |
63 std::cerr << "[AniList] Failed to parse request JSON!" << std::endl; | |
64 return {}; | |
65 } | |
66 | |
67 if (ret.contains("/errors"_json_pointer) && ret.at("/errors"_json_pointer).is_array()) { | |
68 for (const auto& error : ret.at("/errors"_json_pointer)) | |
69 std::cerr << "[AniList] Received an error in response: " << JSON::GetString<std::string>(error, "/message"_json_pointer, "") << std::endl; | |
70 | |
71 return {}; | |
72 } | |
73 | |
74 return ret; | |
75 } | |
76 | |
15 | 77 void ParseListStatus(std::string status, Anime::Anime& anime) { |
187
9613d72b097e
*: multiple performance improvements
Paper <mrpapersonic@gmail.com>
parents:
185
diff
changeset
|
78 static const std::unordered_map<std::string, Anime::ListStatus> map = { |
175 | 79 {"CURRENT", Anime::ListStatus::CURRENT }, |
80 {"PLANNING", Anime::ListStatus::PLANNING }, | |
81 {"COMPLETED", Anime::ListStatus::COMPLETED}, | |
82 {"DROPPED", Anime::ListStatus::DROPPED }, | |
83 {"PAUSED", Anime::ListStatus::PAUSED } | |
84 }; | |
9 | 85 |
15 | 86 if (status == "REPEATING") { |
87 anime.SetUserIsRewatching(true); | |
88 anime.SetUserStatus(Anime::ListStatus::CURRENT); | |
89 return; | |
90 } | |
9 | 91 |
47
d8eb763e6661
information.cpp: add widgets to the list tab, and add an
Paper <mrpapersonic@gmail.com>
parents:
44
diff
changeset
|
92 if (map.find(status) == map.end()) { |
15 | 93 anime.SetUserStatus(Anime::ListStatus::NOT_IN_LIST); |
94 return; | |
95 } | |
9 | 96 |
187
9613d72b097e
*: multiple performance improvements
Paper <mrpapersonic@gmail.com>
parents:
185
diff
changeset
|
97 anime.SetUserStatus(map.at(status)); |
15 | 98 } |
9 | 99 |
15 | 100 std::string ListStatusToString(const Anime::Anime& anime) { |
101 if (anime.GetUserIsRewatching()) | |
102 return "REWATCHING"; | |
103 | |
70
64e5f427c6a2
services/anilist: remove unordered_map usage for enum classes
Paper <mrpapersonic@gmail.com>
parents:
66
diff
changeset
|
104 switch (anime.GetUserStatus()) { |
76 | 105 case Anime::ListStatus::PLANNING: return "PLANNING"; |
106 case Anime::ListStatus::COMPLETED: return "COMPLETED"; | |
107 case Anime::ListStatus::DROPPED: return "DROPPED"; | |
108 case Anime::ListStatus::PAUSED: return "PAUSED"; | |
109 default: break; | |
70
64e5f427c6a2
services/anilist: remove unordered_map usage for enum classes
Paper <mrpapersonic@gmail.com>
parents:
66
diff
changeset
|
110 } |
64e5f427c6a2
services/anilist: remove unordered_map usage for enum classes
Paper <mrpapersonic@gmail.com>
parents:
66
diff
changeset
|
111 return "CURRENT"; |
15 | 112 } |
9 | 113 |
114 void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { | |
175 | 115 anime.SetNativeTitle(JSON::GetString<std::string>(json, "/native"_json_pointer, "")); |
116 anime.SetEnglishTitle(JSON::GetString<std::string>(json, "/english"_json_pointer, "")); | |
117 anime.SetRomajiTitle(JSON::GetString<std::string>(json, "/romaji"_json_pointer, "")); | |
9 | 118 } |
119 | |
120 int ParseMediaJson(const nlohmann::json& json) { | |
175 | 121 int id = JSON::GetNumber(json, "/id"_json_pointer); |
9 | 122 if (!id) |
123 return 0; | |
175 | 124 |
9 | 125 Anime::Anime& anime = Anime::db.items[id]; |
126 anime.SetId(id); | |
127 | |
11 | 128 ParseTitle(json.at("/title"_json_pointer), anime); |
9 | 129 |
175 | 130 anime.SetEpisodes(JSON::GetNumber(json, "/episodes"_json_pointer, 0)); |
131 anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString<std::string>(json, "/format"_json_pointer, ""))); | |
9 | 132 |
175 | 133 anime.SetAiringStatus(Translate::AniList::ToSeriesStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""))); |
9 | 134 |
175 | 135 anime.SetAirDate(Date(json["/startDate"_json_pointer])); |
9 | 136 |
175 | 137 anime.SetPosterUrl(JSON::GetString<std::string>(json, "/coverImage/large"_json_pointer, "")); |
66
6481c5aed3e1
posters: add poster widget...
Paper <mrpapersonic@gmail.com>
parents:
65
diff
changeset
|
138 |
175 | 139 anime.SetAudienceScore(JSON::GetNumber(json, "/averageScore"_json_pointer, 0)); |
140 anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString<std::string>(json, "/season"_json_pointer, ""))); | |
141 anime.SetDuration(JSON::GetNumber(json, "/duration"_json_pointer, 0)); | |
142 anime.SetSynopsis(Strings::TextifySynopsis(JSON::GetString<std::string>(json, "/description"_json_pointer, ""))); | |
9 | 143 |
175 | 144 anime.SetGenres(JSON::GetArray<std::vector<std::string>>(json, "/genres"_json_pointer, {})); |
145 anime.SetTitleSynonyms(JSON::GetArray<std::vector<std::string>>(json, "/synonyms"_json_pointer, {})); | |
146 | |
10 | 147 return id; |
9 | 148 } |
149 | |
10 | 150 int ParseListItem(const nlohmann::json& json) { |
151 int id = ParseMediaJson(json["media"]); | |
152 | |
153 Anime::Anime& anime = Anime::db.items[id]; | |
154 | |
155 anime.AddToUserList(); | |
9 | 156 |
175 | 157 anime.SetUserScore(JSON::GetNumber(json, "/score"_json_pointer, 0)); |
158 anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0)); | |
159 ParseListStatus(JSON::GetString<std::string>(json, "/status"_json_pointer, ""), anime); | |
160 anime.SetUserNotes(JSON::GetString<std::string>(json, "/notes"_json_pointer, "")); | |
9 | 161 |
175 | 162 anime.SetUserDateStarted(Date(json["/startedAt"_json_pointer])); |
163 anime.SetUserDateCompleted(Date(json["/completedAt"_json_pointer])); | |
9 | 164 |
175 | 165 anime.SetUserTimeUpdated(JSON::GetNumber(json, "/updatedAt"_json_pointer, 0)); |
10 | 166 |
167 return id; | |
9 | 168 } |
169 | |
170 int ParseList(const nlohmann::json& json) { | |
171 for (const auto& entry : json["entries"].items()) { | |
172 ParseListItem(entry.value()); | |
173 } | |
10 | 174 return 1; |
9 | 175 } |
176 | |
10 | 177 int GetAnimeList() { |
175 | 178 if (!account.IsValid()) { |
179 std::cerr << "AniList: Account isn't valid!" << std::endl; | |
180 return 0; | |
181 } | |
182 | |
183
01d259b9c89f
pages/torrents.cc: parse feed descriptions separately
Paper <mrpapersonic@gmail.com>
parents:
175
diff
changeset
|
183 /* NOTE: these really ought to be in the qrc file */ |
01d259b9c89f
pages/torrents.cc: parse feed descriptions separately
Paper <mrpapersonic@gmail.com>
parents:
175
diff
changeset
|
184 constexpr std::string_view query = "query ($id: Int) {\n" |
175 | 185 " MediaListCollection (userId: $id, type: ANIME) {\n" |
186 " lists {\n" | |
187 " name\n" | |
188 " entries {\n" | |
189 " score\n" | |
190 " notes\n" | |
191 " status\n" | |
192 " progress\n" | |
193 " startedAt {\n" | |
194 " year\n" | |
195 " month\n" | |
196 " day\n" | |
197 " }\n" | |
198 " completedAt {\n" | |
199 " year\n" | |
200 " month\n" | |
201 " day\n" | |
202 " }\n" | |
203 " updatedAt\n" | |
204 " media {\n" | |
205 " coverImage {\n" | |
206 " large\n" | |
207 " }\n" | |
208 " id\n" | |
209 " title {\n" | |
210 " romaji\n" | |
211 " english\n" | |
212 " native\n" | |
213 " }\n" | |
214 " format\n" | |
215 " status\n" | |
216 " averageScore\n" | |
217 " season\n" | |
218 " startDate {\n" | |
219 " year\n" | |
220 " month\n" | |
221 " day\n" | |
222 " }\n" | |
223 " genres\n" | |
224 " episodes\n" | |
225 " duration\n" | |
226 " synonyms\n" | |
227 " description(asHtml: false)\n" | |
228 " }\n" | |
229 " }\n" | |
230 " }\n" | |
231 " }\n" | |
232 "}\n"; | |
9 | 233 // clang-format off |
234 nlohmann::json json = { | |
235 {"query", query}, | |
236 {"variables", { | |
10 | 237 {"id", account.UserId()} |
9 | 238 }} |
239 }; | |
240 // clang-format on | |
175 | 241 |
242 auto res = SendJSONRequest(json); | |
243 | |
9 | 244 for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { |
10 | 245 ParseList(list.value()); |
9 | 246 } |
247 return 1; | |
248 } | |
249 | |
52
0c4138de2ea7
anime list: we are finally read-write
Paper <mrpapersonic@gmail.com>
parents:
48
diff
changeset
|
250 int UpdateAnimeEntry(int id) { |
9 | 251 /** |
252 * possible values: | |
15 | 253 * |
9 | 254 * int mediaId, |
255 * MediaListStatus status, | |
256 * float score, | |
257 * int scoreRaw, | |
258 * int progress, | |
184
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
259 * int progressVolumes, // manga-specific. |
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
260 * int repeat, // rewatch |
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
261 * int priority, |
9 | 262 * bool private, |
263 * string notes, | |
264 * bool hiddenFromStatusLists, | |
265 * string[] customLists, | |
266 * float[] advancedScores, | |
267 * Date startedAt, | |
268 * Date completedAt | |
15 | 269 **/ |
52
0c4138de2ea7
anime list: we are finally read-write
Paper <mrpapersonic@gmail.com>
parents:
48
diff
changeset
|
270 Anime::Anime& anime = Anime::db.items[id]; |
184
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
271 if (!anime.IsInUserList()) |
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
272 return 0; |
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
273 |
183
01d259b9c89f
pages/torrents.cc: parse feed descriptions separately
Paper <mrpapersonic@gmail.com>
parents:
175
diff
changeset
|
274 constexpr std::string_view query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, " |
184
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
275 "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n" |
175 | 276 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " |
184
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
277 "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n" |
175 | 278 " id\n" |
279 " }\n" | |
280 "}\n"; | |
9 | 281 // clang-format off |
282 nlohmann::json json = { | |
283 {"query", query}, | |
284 {"variables", { | |
10 | 285 {"media_id", anime.GetId()}, |
286 {"progress", anime.GetUserProgress()}, | |
15 | 287 {"status", ListStatusToString(anime)}, |
10 | 288 {"score", anime.GetUserScore()}, |
77 | 289 {"notes", anime.GetUserNotes()}, |
290 {"start", anime.GetUserDateStarted().GetAsAniListJson()}, | |
184
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
291 {"comp", anime.GetUserDateCompleted().GetAsAniListJson()}, |
09492158bcc5
anime: etc. comments and changes
Paper <mrpapersonic@gmail.com>
parents:
183
diff
changeset
|
292 {"repeat", anime.GetUserRewatchedTimes()} |
9 | 293 }} |
294 }; | |
295 // clang-format on | |
175 | 296 |
297 auto ret = SendJSONRequest(json); | |
298 | |
299 return JSON::GetNumber(ret, "/data/SaveMediaListEntry/id"_json_pointer, 0); | |
9 | 300 } |
301 | |
302 int ParseUser(const nlohmann::json& json) { | |
175 | 303 account.SetUsername(JSON::GetString<std::string>(json, "/name"_json_pointer, "")); |
304 account.SetUserId(JSON::GetNumber(json, "/id"_json_pointer, 0)); | |
10 | 305 return account.UserId(); |
9 | 306 } |
307 | |
44
619cbd6e69f9
filesystem: fix CreateDirectories function
Paper <mrpapersonic@gmail.com>
parents:
36
diff
changeset
|
308 bool AuthorizeUser() { |
9 | 309 /* Prompt for PIN */ |
36 | 310 QDesktopServices::openUrl( |
185
62e336597bb7
anime list: add support for different score formats
Paper <mrpapersonic@gmail.com>
parents:
184
diff
changeset
|
311 QUrl(Strings::ToQString("https://anilist.co/api/v2/oauth/authorize?client_id=" + std::to_string(CLIENT_ID) + "&response_type=token"))); |
175 | 312 |
9 | 313 bool ok; |
314 QString token = QInputDialog::getText( | |
175 | 315 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, |
316 "", &ok); | |
317 | |
318 if (!ok || token.isEmpty()) | |
44
619cbd6e69f9
filesystem: fix CreateDirectories function
Paper <mrpapersonic@gmail.com>
parents:
36
diff
changeset
|
319 return false; |
175 | 320 |
321 account.SetAuthToken(Strings::ToUtf8String(token)); | |
322 | |
183
01d259b9c89f
pages/torrents.cc: parse feed descriptions separately
Paper <mrpapersonic@gmail.com>
parents:
175
diff
changeset
|
323 constexpr std::string_view query = "query {\n" |
175 | 324 " Viewer {\n" |
325 " id\n" | |
326 " name\n" | |
327 " mediaListOptions {\n" | |
328 " scoreFormat\n" // this will be used... eventually | |
329 " }\n" | |
330 " }\n" | |
331 "}\n"; | |
9 | 332 nlohmann::json json = { |
175 | 333 {"query", query} |
334 }; | |
335 | |
336 auto ret = SendJSONRequest(json); | |
337 | |
74 | 338 ParseUser(ret["data"]["Viewer"]); |
44
619cbd6e69f9
filesystem: fix CreateDirectories function
Paper <mrpapersonic@gmail.com>
parents:
36
diff
changeset
|
339 return true; |
9 | 340 } |
341 | |
63 | 342 } // namespace AniList |
343 } // namespace Services |