Mercurial > minori
comparison src/services/anilist.cpp @ 10:4b198a111713
Update
things actually compile now btw
qttest wants to fuck over the model but that might be my fault so /shrug
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sat, 16 Sep 2023 02:06:01 -0400 |
parents | 5c0397762b53 |
children | fc1bf97c528b |
comparison
equal
deleted
inserted
replaced
9:5c0397762b53 | 10:4b198a111713 |
---|---|
1 #include "services/anilist.h" | 1 #include "services/anilist.h" |
2 #include "core/anime.h" | 2 #include "core/anime.h" |
3 #include "core/anime_db.h" | |
3 #include "core/config.h" | 4 #include "core/config.h" |
4 #include "core/json.h" | 5 #include "core/json.h" |
5 #include "core/session.h" | 6 #include "core/session.h" |
6 #include "core/strings.h" | 7 #include "core/strings.h" |
7 #include <QDesktopServices> | 8 #include <QDesktopServices> |
8 #include <QInputDialog> | 9 #include <QInputDialog> |
9 #include <QLineEdit> | 10 #include <QLineEdit> |
10 #include <QMessageBox> | 11 #include <QMessageBox> |
12 #include <QUrl> | |
11 #include <chrono> | 13 #include <chrono> |
12 #include <curl/curl.h> | 14 #include <curl/curl.h> |
13 #include <exception> | 15 #include <exception> |
14 #include <format> | 16 #include <format> |
15 #define CLIENT_ID "13706" | 17 #define CLIENT_ID "13706" |
16 | 18 |
17 namespace Services::AniList { | 19 namespace Services::AniList { |
18 | 20 |
19 class Account { | 21 class Account { |
20 public: | 22 public: |
21 std::string Username() const { return session.anilist.username; } | 23 std::string Username() const { return session.config.anilist.username; } |
22 void SetUsername(std::string const& username) { session.anilist.username = username; } | 24 void SetUsername(std::string const& username) { session.config.anilist.username = username; } |
23 | 25 |
24 int UserId() const { return session.anilist.user_id; } | 26 int UserId() const { return session.config.anilist.user_id; } |
25 void SetUserId(const int id) { session.anilist.user_id = id; } | 27 void SetUserId(const int id) { session.config.anilist.user_id = id; } |
26 | 28 |
27 std::string AuthToken() const { return session.anilist.auth_token; } | 29 std::string AuthToken() const { return session.config.anilist.auth_token; } |
28 void SetAuthToken(std::string const& auth_token) { session.anilist.auth_token = auth_token; } | 30 void SetAuthToken(std::string const& auth_token) { session.config.anilist.auth_token = auth_token; } |
29 | 31 |
30 bool Authenticated() const { return !AuthToken().empty(); } | 32 bool Authenticated() const { return !AuthToken().empty(); } |
31 } | 33 }; |
32 | 34 |
33 static Account account; | 35 static Account account; |
34 | 36 |
35 static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) { | 37 static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) { |
36 ((std::string*)userdata)->append((char*)contents, size * nmemb); | 38 ((std::string*)userdata)->append((char*)contents, size * nmemb); |
66 return userdata; | 68 return userdata; |
67 } | 69 } |
68 return ""; | 70 return ""; |
69 } | 71 } |
70 | 72 |
71 /* Maps to convert string forms to our internal enums */ | 73 /* TODO: Move to Translate */ |
72 | 74 |
73 std::map<std::string, enum AnimeWatchingStatus> StringToAnimeWatchingMap = { | 75 std::map<std::string, Anime::ListStatus> AniListStringToAnimeWatchingMap = { |
74 {"CURRENT", CURRENT }, | 76 {"CURRENT", Anime::ListStatus::CURRENT }, |
75 {"PLANNING", PLANNING }, | 77 {"PLANNING", Anime::ListStatus::PLANNING }, |
76 {"COMPLETED", COMPLETED}, | 78 {"COMPLETED", Anime::ListStatus::COMPLETED}, |
77 {"DROPPED", DROPPED }, | 79 {"DROPPED", Anime::ListStatus::DROPPED }, |
78 {"PAUSED", PAUSED }, | 80 {"PAUSED", Anime::ListStatus::PAUSED }, |
79 {"REPEATING", REPEATING} | 81 {"REPEATING", Anime::ListStatus::CURRENT} |
80 }; | 82 }; |
81 | 83 |
82 std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { | 84 std::map<Anime::ListStatus, std::string> AniListAnimeWatchingToStringMap = { |
83 {CURRENT, "CURRENT" }, | 85 {Anime::ListStatus::CURRENT, "CURRENT" }, |
84 {PLANNING, "PLANNING" }, | 86 {Anime::ListStatus::PLANNING, "PLANNING" }, |
85 {COMPLETED, "COMPLETED"}, | 87 {Anime::ListStatus::COMPLETED, "COMPLETED"}, |
86 {DROPPED, "DROPPED" }, | 88 {Anime::ListStatus::DROPPED, "DROPPED" }, |
87 {PAUSED, "PAUSED" }, | 89 {Anime::ListStatus::PAUSED, "PAUSED" } |
88 {REPEATING, "REPEATING"} | 90 }; |
89 }; | 91 |
90 | 92 std::map<std::string, Anime::SeriesStatus> AniListStringToAnimeAiringMap = { |
91 std::map<std::string, enum AnimeAiringStatus> StringToAnimeAiringMap = { | 93 {"FINISHED", Anime::SeriesStatus::FINISHED }, |
92 {"FINISHED", FINISHED }, | 94 {"RELEASING", Anime::SeriesStatus::RELEASING }, |
93 {"RELEASING", RELEASING }, | 95 {"NOT_YET_RELEASED", Anime::SeriesStatus::NOT_YET_RELEASED}, |
94 {"NOT_YET_RELEASED", NOT_YET_RELEASED}, | 96 {"CANCELLED", Anime::SeriesStatus::CANCELLED }, |
95 {"CANCELLED", CANCELLED }, | 97 {"HIATUS", Anime::SeriesStatus::HIATUS } |
96 {"HIATUS", HIATUS } | 98 }; |
97 }; | 99 |
98 | 100 std::map<std::string, Anime::SeriesSeason> AniListStringToAnimeSeasonMap = { |
99 std::map<std::string, enum AnimeSeason> StringToAnimeSeasonMap = { | 101 {"WINTER", Anime::SeriesSeason::WINTER}, |
100 {"WINTER", WINTER}, | 102 {"SPRING", Anime::SeriesSeason::SPRING}, |
101 {"SPRING", SPRING}, | 103 {"SUMMER", Anime::SeriesSeason::SUMMER}, |
102 {"SUMMER", SUMMER}, | 104 {"FALL", Anime::SeriesSeason::FALL } |
103 {"FALL", FALL } | 105 }; |
104 }; | 106 |
105 | 107 std::map<std::string, enum Anime::SeriesFormat> AniListStringToAnimeFormatMap = { |
106 std::map<std::string, enum AnimeFormat> StringToAnimeFormatMap = { | 108 {"TV", Anime::SeriesFormat::TV }, |
107 {"TV", TV }, | 109 {"TV_SHORT", Anime::SeriesFormat::TV_SHORT}, |
108 {"TV_SHORT", TV_SHORT}, | 110 {"MOVIE", Anime::SeriesFormat::MOVIE }, |
109 {"MOVIE", MOVIE }, | 111 {"SPECIAL", Anime::SeriesFormat::SPECIAL }, |
110 {"SPECIAL", SPECIAL }, | 112 {"OVA", Anime::SeriesFormat::OVA }, |
111 {"OVA", OVA }, | 113 {"ONA", Anime::SeriesFormat::ONA }, |
112 {"ONA", ONA }, | 114 {"MUSIC", Anime::SeriesFormat::MUSIC }, |
113 {"MUSIC", MUSIC }, | 115 {"MANGA", Anime::SeriesFormat::MANGA }, |
114 {"MANGA", MANGA }, | 116 {"NOVEL", Anime::SeriesFormat::NOVEL }, |
115 {"NOVEL", NOVEL }, | 117 {"ONE_SHOT", Anime::SeriesFormat::ONE_SHOT} |
116 {"ONE_SHOT", ONE_SHOT} | 118 }; |
117 }; | 119 |
118 | 120 Date ParseDate(const nlohmann::json& json) { |
119 void ParseDate(const nlohmann::json& json, Date& date) { | 121 Date date; |
120 if (json.contains("/year"_json_pointer) && json["/year"_json_pointer].is_number()) | 122 if (json.contains("/year"_json_pointer) && json["/year"_json_pointer].is_number()) |
121 date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); | 123 date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); |
122 else | 124 else |
123 date.VoidYear(); | 125 date.VoidYear(); |
124 | 126 |
129 | 131 |
130 if (json.contains("/day"_json_pointer) && json["/day"_json_pointer].is_number()) | 132 if (json.contains("/day"_json_pointer) && json["/day"_json_pointer].is_number()) |
131 date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); | 133 date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); |
132 else | 134 else |
133 date.VoidDay(); | 135 date.VoidDay(); |
136 return date; | |
134 } | 137 } |
135 | 138 |
136 void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { | 139 void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { |
137 anime.SetNativeTitle(JSON::GetString(json, "/native"_json_pointer)); | 140 anime.SetNativeTitle(JSON::GetString(json, "/native"_json_pointer)); |
138 anime.SetEnglishTitle(JSON::GetString(json, "/english"_json_pointer)); | 141 anime.SetEnglishTitle(JSON::GetString(json, "/english"_json_pointer)); |
149 ParseTitle(json["/title"_json_pointer], anime); | 152 ParseTitle(json["/title"_json_pointer], anime); |
150 | 153 |
151 anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); | 154 anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); |
152 anime.SetFormat(AniListStringToAnimeFormatMap[JSON::GetString(json, "/format"_json_pointer)]); | 155 anime.SetFormat(AniListStringToAnimeFormatMap[JSON::GetString(json, "/format"_json_pointer)]); |
153 | 156 |
154 anime.SetListStatus(AniListStringToAnimeAiringMap[JSON::GetString(json, "/status"_json_pointer)]); | 157 anime.SetAiringStatus(AniListStringToAnimeAiringMap[JSON::GetString(json, "/status"_json_pointer)]); |
155 | 158 |
156 ParseDate(json["/startDate"_json_pointer], anime.air_date); | 159 anime.SetAirDate(ParseDate(json["/startDate"_json_pointer])); |
157 | 160 |
158 anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); | 161 anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); |
159 anime.SetSeason(AniListStringToAnimeSeasonMap[JSON::GetString(json, "/season"_json_pointer)]); | 162 anime.SetSeason(AniListStringToAnimeSeasonMap[JSON::GetString(json, "/season"_json_pointer)]); |
160 anime.SetDuration(JSON::GetInt(json, "/duration"_json_pointer)); | 163 anime.SetDuration(JSON::GetInt(json, "/duration"_json_pointer)); |
161 anime.SetSynopsis(StringUtils::TextifySynopsis(JSON::GetString(json, "/description"_json_pointer))); | 164 anime.SetSynopsis(Strings::TextifySynopsis(JSON::GetString(json, "/description"_json_pointer))); |
162 | 165 |
163 if (json.contains("/genres"_json_pointer) && json["/genres"_json_pointer].is_array()) | 166 if (json.contains("/genres"_json_pointer) && json["/genres"_json_pointer].is_array()) |
164 anime.SetGenres(json["/genres"_json_pointer].get<std::vector<std::string>>()); | 167 anime.SetGenres(json["/genres"_json_pointer].get<std::vector<std::string>>()); |
165 if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) | 168 if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) |
166 anime.SetSynonyms(json["/synonyms"_json_pointer].get<std::vector<std::string>>()); | 169 anime.SetTitleSynonyms(json["/synonyms"_json_pointer].get<std::vector<std::string>>()); |
167 return 1; | 170 return id; |
168 } | 171 } |
169 | 172 |
170 int ParseListItem(const nlohmann::json& json, Anime::Anime& anime) { | 173 int ParseListItem(const nlohmann::json& json) { |
171 anime.SetScore(JSON::GetInt(entry.value(), "/score"_json_pointer)); | 174 int id = ParseMediaJson(json["media"]); |
172 anime.SetProgress(JSON::GetInt(entry.value(), "/progress"_json_pointer)); | 175 |
173 anime.SetStatus(AniListStringToAnimeWatchingMap[JSON::GetString(entry.value(), "/status"_json_pointer)]); | 176 Anime::Anime& anime = Anime::db.items[id]; |
174 anime.SetNotes(JSON::GetString(entry.value(), "/notes"_json_pointer)); | 177 |
175 | 178 anime.AddToUserList(); |
176 ParseDate(json["/startedAt"_json_pointer], anime.started); | 179 |
177 ParseDate(json["/completedAt"_json_pointer], anime.completed); | 180 anime.SetUserScore(JSON::GetInt(json, "/score"_json_pointer)); |
178 | 181 anime.SetUserProgress(JSON::GetInt(json, "/progress"_json_pointer)); |
179 anime.SetUpdated(JSON::GetInt(entry.value(), "/updatedAt"_json_pointer)); | 182 anime.SetUserStatus(AniListStringToAnimeWatchingMap[JSON::GetString(json, "/status"_json_pointer)]); |
180 | 183 anime.SetUserNotes(JSON::GetString(json, "/notes"_json_pointer)); |
181 return ParseMediaJson(json["media"], anime); | 184 |
185 anime.SetUserDateStarted(ParseDate(json["/startedAt"_json_pointer])); | |
186 anime.SetUserDateCompleted(ParseDate(json["/completedAt"_json_pointer])); | |
187 | |
188 anime.SetUserTimeUpdated(JSON::GetInt(json, "/updatedAt"_json_pointer)); | |
189 | |
190 return id; | |
182 } | 191 } |
183 | 192 |
184 int ParseList(const nlohmann::json& json) { | 193 int ParseList(const nlohmann::json& json) { |
185 for (const auto& entry : json["entries"].items()) { | 194 for (const auto& entry : json["entries"].items()) { |
186 ParseListItem(entry.value()); | 195 ParseListItem(entry.value()); |
187 } | 196 } |
188 } | 197 return 1; |
189 | 198 } |
190 int GetAnimeList(int id) { | 199 |
200 int GetAnimeList() { | |
191 /* NOTE: these should be in the qrc file */ | 201 /* NOTE: these should be in the qrc file */ |
192 const std::string query = "query ($id: Int) {\n" | 202 const std::string query = "query ($id: Int) {\n" |
193 " MediaListCollection (userId: $id, type: ANIME) {\n" | 203 " MediaListCollection (userId: $id, type: ANIME) {\n" |
194 " lists {\n" | 204 " lists {\n" |
195 " name\n" | 205 " name\n" |
196 " entries {\n" | 206 " entries {\n" |
197 " score\n" | 207 " score\n" |
198 " notes\n" | 208 " notes\n" |
209 " status\n" | |
199 " progress\n" | 210 " progress\n" |
200 " startedAt {\n" | 211 " startedAt {\n" |
201 " year\n" | 212 " year\n" |
202 " month\n" | 213 " month\n" |
203 " day\n" | 214 " day\n" |
236 "}\n"; | 247 "}\n"; |
237 // clang-format off | 248 // clang-format off |
238 nlohmann::json json = { | 249 nlohmann::json json = { |
239 {"query", query}, | 250 {"query", query}, |
240 {"variables", { | 251 {"variables", { |
241 {"id", id} | 252 {"id", account.UserId()} |
242 }} | 253 }} |
243 }; | 254 }; |
244 // clang-format on | 255 // clang-format on |
245 /* TODO: do a try catch here, catch any json errors and then call | 256 /* TODO: do a try catch here, catch any json errors and then call |
246 Authorize() if needed */ | 257 Authorize() if needed */ |
247 auto res = nlohmann::json::parse(SendRequest(json.dump())); | 258 auto res = nlohmann::json::parse(SendRequest(json.dump())); |
248 /* TODO: make sure that we actually need the wstring converter and see | 259 /* TODO: make sure that we actually need the wstring converter and see |
249 if we can just get wide strings back from nlohmann::json */ | 260 if we can just get wide strings back from nlohmann::json */ |
250 for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { | 261 for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { |
251 | 262 ParseList(list.value()); |
252 ParseList(list.entry()); | |
253 } | 263 } |
254 return 1; | 264 return 1; |
255 } | 265 } |
256 | 266 |
257 int UpdateAnimeEntry(const Anime& anime) { | 267 int UpdateAnimeEntry(const Anime::Anime& anime) { |
258 /** | 268 /** |
259 * possible values: | 269 * possible values: |
260 * | 270 * |
261 * int mediaId, | 271 * int mediaId, |
262 * MediaListStatus status, | 272 * MediaListStatus status, |
283 "}\n"; | 293 "}\n"; |
284 // clang-format off | 294 // clang-format off |
285 nlohmann::json json = { | 295 nlohmann::json json = { |
286 {"query", query}, | 296 {"query", query}, |
287 {"variables", { | 297 {"variables", { |
288 {"media_id", anime.id}, | 298 {"media_id", anime.GetId()}, |
289 {"progress", anime.progress}, | 299 {"progress", anime.GetUserProgress()}, |
290 {"status", AnimeWatchingToStringMap[anime.status]}, | 300 {"status", AniListAnimeWatchingToStringMap[anime.GetUserStatus()]}, |
291 {"score", anime.score}, | 301 {"score", anime.GetUserScore()}, |
292 {"notes", anime.notes} | 302 {"notes", anime.GetUserNotes()} |
293 }} | 303 }} |
294 }; | 304 }; |
295 // clang-format on | 305 // clang-format on |
296 SendRequest(json.dump()); | 306 SendRequest(json.dump()); |
297 return 1; | 307 return 1; |
298 } | 308 } |
299 | 309 |
300 int ParseUser(const nlohmann::json& json) { | 310 int ParseUser(const nlohmann::json& json) { |
301 account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); | 311 account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); |
302 account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); | 312 account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); |
303 account.SetAuthenticated(true); | 313 return account.UserId(); |
304 } | 314 } |
305 | 315 |
306 int AuthorizeUser() { | 316 int AuthorizeUser() { |
307 /* Prompt for PIN */ | 317 /* Prompt for PIN */ |
308 QDesktopServices::openUrl( | 318 QDesktopServices::openUrl( |
312 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, | 322 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, |
313 "", &ok); | 323 "", &ok); |
314 if (ok && !token.isEmpty()) | 324 if (ok && !token.isEmpty()) |
315 account.SetAuthToken(token.toStdString()); | 325 account.SetAuthToken(token.toStdString()); |
316 else { // fail | 326 else { // fail |
317 account.SetAuthenticated(false); | |
318 return 0; | 327 return 0; |
319 } | 328 } |
320 const std::string query = "query {\n" | 329 const std::string query = "query {\n" |
321 " Viewer {\n" | 330 " Viewer {\n" |
322 " id\n" | 331 " id\n" |
328 "}\n"; | 337 "}\n"; |
329 nlohmann::json json = { | 338 nlohmann::json json = { |
330 {"query", query} | 339 {"query", query} |
331 }; | 340 }; |
332 auto ret = nlohmann::json::parse(SendRequest(json.dump())); | 341 auto ret = nlohmann::json::parse(SendRequest(json.dump())); |
333 ParseUser(json["Viewer"]) account.SetAuthenticated(true); | 342 ParseUser(json["Viewer"]); |
334 return 1; | 343 return 1; |
335 } | 344 } |
336 | 345 |
337 } // namespace Services::AniList | 346 } // namespace Services::AniList |