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