Mercurial > minori
annotate src/services/anilist.cc @ 137:69db40272acd
dep/animia: [WIP] huge refactor
this WILL NOT compile, because lots of code has been changed
and every API in the original codebase has been removed.
note that this api setup is not exactly permanent...
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Fri, 10 Nov 2023 13:52:47 -0500 |
parents | 275da698697d |
children | 9b10175be389 |
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" |
77 | 10 #include <QByteArray> |
9 | 11 #include <QDesktopServices> |
12 #include <QInputDialog> | |
13 #include <QLineEdit> | |
14 #include <QMessageBox> | |
10 | 15 #include <QUrl> |
9 | 16 #include <chrono> |
17 #include <exception> | |
18 #define CLIENT_ID "13706" | |
19 | |
64 | 20 using namespace nlohmann::literals::json_literals; |
11 | 21 |
63 | 22 namespace Services { |
23 namespace AniList { | |
9 | 24 |
25 class Account { | |
26 public: | |
120 | 27 std::string Username() const { return session.config.auth.anilist.username; } |
28 void SetUsername(std::string const& username) { session.config.auth.anilist.username = username; } | |
9 | 29 |
120 | 30 int UserId() const { return session.config.auth.anilist.user_id; } |
31 void SetUserId(const int id) { session.config.auth.anilist.user_id = id; } | |
9 | 32 |
120 | 33 std::string AuthToken() const { return session.config.auth.anilist.auth_token; } |
34 void SetAuthToken(std::string const& auth_token) { session.config.auth.anilist.auth_token = auth_token; } | |
9 | 35 |
36 bool Authenticated() const { return !AuthToken().empty(); } | |
10 | 37 }; |
9 | 38 |
39 static Account account; | |
40 | |
41 std::string SendRequest(std::string data) { | |
76 | 42 std::vector<std::string> headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json", |
43 "Content-Type: application/json"}; | |
77 | 44 return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers)); |
9 | 45 } |
46 | |
15 | 47 void ParseListStatus(std::string status, Anime::Anime& anime) { |
48 std::unordered_map<std::string, Anime::ListStatus> map = { | |
49 {"CURRENT", Anime::ListStatus::CURRENT }, | |
50 {"PLANNING", Anime::ListStatus::PLANNING }, | |
51 {"COMPLETED", Anime::ListStatus::COMPLETED}, | |
52 {"DROPPED", Anime::ListStatus::DROPPED }, | |
53 {"PAUSED", Anime::ListStatus::PAUSED } | |
54 }; | |
9 | 55 |
15 | 56 if (status == "REPEATING") { |
57 anime.SetUserIsRewatching(true); | |
58 anime.SetUserStatus(Anime::ListStatus::CURRENT); | |
59 return; | |
60 } | |
9 | 61 |
47
d8eb763e6661
information.cpp: add widgets to the list tab, and add an
Paper <mrpapersonic@gmail.com>
parents:
44
diff
changeset
|
62 if (map.find(status) == map.end()) { |
15 | 63 anime.SetUserStatus(Anime::ListStatus::NOT_IN_LIST); |
64 return; | |
65 } | |
9 | 66 |
15 | 67 anime.SetUserStatus(map[status]); |
68 } | |
9 | 69 |
15 | 70 std::string ListStatusToString(const Anime::Anime& anime) { |
71 if (anime.GetUserIsRewatching()) | |
72 return "REWATCHING"; | |
73 | |
70
64e5f427c6a2
services/anilist: remove unordered_map usage for enum classes
Paper <mrpapersonic@gmail.com>
parents:
66
diff
changeset
|
74 switch (anime.GetUserStatus()) { |
76 | 75 case Anime::ListStatus::PLANNING: return "PLANNING"; |
76 case Anime::ListStatus::COMPLETED: return "COMPLETED"; | |
77 case Anime::ListStatus::DROPPED: return "DROPPED"; | |
78 case Anime::ListStatus::PAUSED: return "PAUSED"; | |
79 default: break; | |
70
64e5f427c6a2
services/anilist: remove unordered_map usage for enum classes
Paper <mrpapersonic@gmail.com>
parents:
66
diff
changeset
|
80 } |
64e5f427c6a2
services/anilist: remove unordered_map usage for enum classes
Paper <mrpapersonic@gmail.com>
parents:
66
diff
changeset
|
81 return "CURRENT"; |
15 | 82 } |
9 | 83 |
10 | 84 Date ParseDate(const nlohmann::json& json) { |
85 Date date; | |
64 | 86 /* JSON for Modern C++ warns here. I'm not too sure why, this code works when I set the |
87 standard to C++17 :/ */ | |
11 | 88 if (json.contains("/year"_json_pointer) && json.at("/year"_json_pointer).is_number()) |
9 | 89 date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); |
90 else | |
91 date.VoidYear(); | |
92 | |
11 | 93 if (json.contains("/month"_json_pointer) && json.at("/month"_json_pointer).is_number()) |
9 | 94 date.SetMonth(JSON::GetInt(json, "/month"_json_pointer)); |
95 else | |
96 date.VoidMonth(); | |
97 | |
11 | 98 if (json.contains("/day"_json_pointer) && json.at("/day"_json_pointer).is_number()) |
9 | 99 date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); |
100 else | |
101 date.VoidDay(); | |
10 | 102 return date; |
9 | 103 } |
104 | |
105 void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { | |
106 anime.SetNativeTitle(JSON::GetString(json, "/native"_json_pointer)); | |
107 anime.SetEnglishTitle(JSON::GetString(json, "/english"_json_pointer)); | |
108 anime.SetRomajiTitle(JSON::GetString(json, "/romaji"_json_pointer)); | |
109 } | |
110 | |
111 int ParseMediaJson(const nlohmann::json& json) { | |
112 int id = JSON::GetInt(json, "/id"_json_pointer); | |
113 if (!id) | |
114 return 0; | |
115 Anime::Anime& anime = Anime::db.items[id]; | |
116 anime.SetId(id); | |
117 | |
11 | 118 ParseTitle(json.at("/title"_json_pointer), anime); |
9 | 119 |
120 anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); | |
15 | 121 anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString(json, "/format"_json_pointer))); |
9 | 122 |
15 | 123 anime.SetAiringStatus(Translate::AniList::ToSeriesStatus(JSON::GetString(json, "/status"_json_pointer))); |
9 | 124 |
10 | 125 anime.SetAirDate(ParseDate(json["/startDate"_json_pointer])); |
9 | 126 |
66
6481c5aed3e1
posters: add poster widget...
Paper <mrpapersonic@gmail.com>
parents:
65
diff
changeset
|
127 anime.SetPosterUrl(JSON::GetString(json, "/coverImage/large"_json_pointer)); |
6481c5aed3e1
posters: add poster widget...
Paper <mrpapersonic@gmail.com>
parents:
65
diff
changeset
|
128 |
9 | 129 anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); |
15 | 130 anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString(json, "/season"_json_pointer))); |
9 | 131 anime.SetDuration(JSON::GetInt(json, "/duration"_json_pointer)); |
10 | 132 anime.SetSynopsis(Strings::TextifySynopsis(JSON::GetString(json, "/description"_json_pointer))); |
9 | 133 |
134 if (json.contains("/genres"_json_pointer) && json["/genres"_json_pointer].is_array()) | |
135 anime.SetGenres(json["/genres"_json_pointer].get<std::vector<std::string>>()); | |
136 if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) | |
10 | 137 anime.SetTitleSynonyms(json["/synonyms"_json_pointer].get<std::vector<std::string>>()); |
138 return id; | |
9 | 139 } |
140 | |
10 | 141 int ParseListItem(const nlohmann::json& json) { |
142 int id = ParseMediaJson(json["media"]); | |
143 | |
144 Anime::Anime& anime = Anime::db.items[id]; | |
145 | |
146 anime.AddToUserList(); | |
9 | 147 |
10 | 148 anime.SetUserScore(JSON::GetInt(json, "/score"_json_pointer)); |
149 anime.SetUserProgress(JSON::GetInt(json, "/progress"_json_pointer)); | |
15 | 150 ParseListStatus(JSON::GetString(json, "/status"_json_pointer), anime); |
10 | 151 anime.SetUserNotes(JSON::GetString(json, "/notes"_json_pointer)); |
9 | 152 |
10 | 153 anime.SetUserDateStarted(ParseDate(json["/startedAt"_json_pointer])); |
154 anime.SetUserDateCompleted(ParseDate(json["/completedAt"_json_pointer])); | |
9 | 155 |
10 | 156 anime.SetUserTimeUpdated(JSON::GetInt(json, "/updatedAt"_json_pointer)); |
157 | |
158 return id; | |
9 | 159 } |
160 | |
161 int ParseList(const nlohmann::json& json) { | |
162 for (const auto& entry : json["entries"].items()) { | |
163 ParseListItem(entry.value()); | |
164 } | |
10 | 165 return 1; |
9 | 166 } |
167 | |
10 | 168 int GetAnimeList() { |
9 | 169 /* NOTE: these should be in the qrc file */ |
170 const std::string query = "query ($id: Int) {\n" | |
15 | 171 " MediaListCollection (userId: $id, type: ANIME) {\n" |
172 " lists {\n" | |
173 " name\n" | |
174 " entries {\n" | |
175 " score\n" | |
176 " notes\n" | |
177 " status\n" | |
178 " progress\n" | |
179 " startedAt {\n" | |
180 " year\n" | |
181 " month\n" | |
182 " day\n" | |
183 " }\n" | |
184 " completedAt {\n" | |
185 " year\n" | |
186 " month\n" | |
187 " day\n" | |
188 " }\n" | |
189 " updatedAt\n" | |
190 " media {\n" | |
66
6481c5aed3e1
posters: add poster widget...
Paper <mrpapersonic@gmail.com>
parents:
65
diff
changeset
|
191 " coverImage {\n" |
6481c5aed3e1
posters: add poster widget...
Paper <mrpapersonic@gmail.com>
parents:
65
diff
changeset
|
192 " large\n" |
6481c5aed3e1
posters: add poster widget...
Paper <mrpapersonic@gmail.com>
parents:
65
diff
changeset
|
193 " }\n" |
15 | 194 " id\n" |
195 " title {\n" | |
196 " romaji\n" | |
197 " english\n" | |
198 " native\n" | |
199 " }\n" | |
200 " format\n" | |
201 " status\n" | |
202 " averageScore\n" | |
203 " season\n" | |
204 " startDate {\n" | |
205 " year\n" | |
206 " month\n" | |
207 " day\n" | |
208 " }\n" | |
209 " genres\n" | |
210 " episodes\n" | |
211 " duration\n" | |
212 " synonyms\n" | |
213 " description(asHtml: false)\n" | |
214 " }\n" | |
215 " }\n" | |
216 " }\n" | |
217 " }\n" | |
218 "}\n"; | |
9 | 219 // clang-format off |
220 nlohmann::json json = { | |
221 {"query", query}, | |
222 {"variables", { | |
10 | 223 {"id", account.UserId()} |
9 | 224 }} |
225 }; | |
226 // clang-format on | |
227 /* TODO: do a try catch here, catch any json errors and then call | |
228 Authorize() if needed */ | |
229 auto res = nlohmann::json::parse(SendRequest(json.dump())); | |
230 /* TODO: make sure that we actually need the wstring converter and see | |
231 if we can just get wide strings back from nlohmann::json */ | |
232 for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { | |
10 | 233 ParseList(list.value()); |
9 | 234 } |
235 return 1; | |
236 } | |
237 | |
52
0c4138de2ea7
anime list: we are finally read-write
Paper <mrpapersonic@gmail.com>
parents:
48
diff
changeset
|
238 int UpdateAnimeEntry(int id) { |
9 | 239 /** |
240 * possible values: | |
15 | 241 * |
9 | 242 * int mediaId, |
243 * MediaListStatus status, | |
244 * float score, | |
245 * int scoreRaw, | |
246 * int progress, | |
247 * int progressVolumes, | |
248 * int repeat, | |
249 * int priority, | |
250 * bool private, | |
251 * string notes, | |
252 * bool hiddenFromStatusLists, | |
253 * string[] customLists, | |
254 * float[] advancedScores, | |
255 * Date startedAt, | |
256 * Date completedAt | |
15 | 257 **/ |
52
0c4138de2ea7
anime list: we are finally read-write
Paper <mrpapersonic@gmail.com>
parents:
48
diff
changeset
|
258 Anime::Anime& anime = Anime::db.items[id]; |
77 | 259 const std::string query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, " |
260 "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput) {\n" | |
261 " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " | |
262 "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp) {\n" | |
263 " id\n" | |
264 " }\n" | |
265 "}\n"; | |
9 | 266 // clang-format off |
267 nlohmann::json json = { | |
268 {"query", query}, | |
269 {"variables", { | |
10 | 270 {"media_id", anime.GetId()}, |
271 {"progress", anime.GetUserProgress()}, | |
15 | 272 {"status", ListStatusToString(anime)}, |
10 | 273 {"score", anime.GetUserScore()}, |
77 | 274 {"notes", anime.GetUserNotes()}, |
275 {"start", anime.GetUserDateStarted().GetAsAniListJson()}, | |
276 {"comp", anime.GetUserDateCompleted().GetAsAniListJson()} | |
9 | 277 }} |
278 }; | |
279 // clang-format on | |
52
0c4138de2ea7
anime list: we are finally read-write
Paper <mrpapersonic@gmail.com>
parents:
48
diff
changeset
|
280 auto ret = nlohmann::json::parse(SendRequest(json.dump())); |
0c4138de2ea7
anime list: we are finally read-write
Paper <mrpapersonic@gmail.com>
parents:
48
diff
changeset
|
281 return JSON::GetInt(ret, "/data/SaveMediaListEntry/id"_json_pointer); |
9 | 282 } |
283 | |
284 int ParseUser(const nlohmann::json& json) { | |
285 account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); | |
286 account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); | |
10 | 287 return account.UserId(); |
9 | 288 } |
289 | |
44
619cbd6e69f9
filesystem: fix CreateDirectories function
Paper <mrpapersonic@gmail.com>
parents:
36
diff
changeset
|
290 bool AuthorizeUser() { |
9 | 291 /* Prompt for PIN */ |
36 | 292 QDesktopServices::openUrl( |
293 QUrl("https://anilist.co/api/v2/oauth/authorize?client_id=" CLIENT_ID "&response_type=token")); | |
9 | 294 bool ok; |
295 QString token = QInputDialog::getText( | |
36 | 296 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, |
297 "", &ok); | |
9 | 298 if (ok && !token.isEmpty()) |
65
26721c28bf22
*: avoid usage of (to|from)StdString
Paper <mrpapersonic@gmail.com>
parents:
64
diff
changeset
|
299 account.SetAuthToken(Strings::ToUtf8String(token)); |
15 | 300 else // fail |
44
619cbd6e69f9
filesystem: fix CreateDirectories function
Paper <mrpapersonic@gmail.com>
parents:
36
diff
changeset
|
301 return false; |
9 | 302 const std::string query = "query {\n" |
15 | 303 " Viewer {\n" |
304 " id\n" | |
305 " name\n" | |
306 " mediaListOptions {\n" | |
307 " scoreFormat\n" | |
308 " }\n" | |
309 " }\n" | |
310 "}\n"; | |
9 | 311 nlohmann::json json = { |
63 | 312 {"query", query} |
313 }; | |
9 | 314 auto ret = nlohmann::json::parse(SendRequest(json.dump())); |
74 | 315 ParseUser(ret["data"]["Viewer"]); |
44
619cbd6e69f9
filesystem: fix CreateDirectories function
Paper <mrpapersonic@gmail.com>
parents:
36
diff
changeset
|
316 return true; |
9 | 317 } |
318 | |
63 | 319 } // namespace AniList |
320 } // namespace Services |