changeset 286:53e3c015a973

anime: initial cross-service support currently the Kitsu and MAL services don't work when chosen in the GUI. This is because they haven't been implemented yet :)
author Paper <paper@paper.us.eu.org>
date Wed, 08 May 2024 16:44:27 -0400
parents 65df2813d0de
children 8535eb5fb836
files include/core/anime.h src/core/anime.cc src/core/anime_db.cc src/core/config.cc src/gui/translate/anime.cc src/gui/widgets/poster.cc src/services/anilist.cc
diffstat 7 files changed, 58 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/include/core/anime.h	Wed May 08 16:43:32 2024 -0400
+++ b/include/core/anime.h	Wed May 08 16:44:27 2024 -0400
@@ -86,10 +86,18 @@
 
 enum class Service {
 	None,
-	AniList
+	AniList,
+	MyAnimeList,
+	Kitsu
 };
 
-constexpr std::array<Service, 1> Services{Service::AniList};
+/* this doesn't include MAL and Kitsu because they aren't really
+ * "valid" services yet. */
+constexpr std::array<Service, 3> Services{
+	Service::AniList,
+	Service::MyAnimeList,
+	Service::Kitsu
+};
 
 enum class ScoreFormat {
 	Point100,       // 0-100
@@ -118,6 +126,7 @@
 
 struct SeriesInformation {
 	int id;
+	std::map<Service, std::string> ids;
 	std::map<TitleLanguage, std::string> titles;
 	std::vector<std::string> synonyms;
 	int episodes = 0;
@@ -160,6 +169,7 @@
 
 	/* Series data */
 	int GetId() const;
+	std::optional<std::string> GetServiceId(Service service) const;
 	std::optional<std::string> GetTitle(TitleLanguage language) const;
 	std::vector<std::string> GetTitleSynonyms() const;
 	int GetEpisodes() const;
@@ -173,9 +183,10 @@
 	std::string GetSynopsis() const;
 	int GetDuration() const;
 	std::string GetPosterUrl() const;
-	std::string GetServiceUrl() const;
+	std::optional<std::string> GetServiceUrl(Service service) const;
 
 	void SetId(int id);
+	void SetServiceId(Service service, const std::string& id);
 	void SetTitle(TitleLanguage language, const std::string& title);
 	void SetTitleSynonyms(std::vector<std::string> const& synonyms);
 	void AddTitleSynonym(std::string const& synonym);
--- a/src/core/anime.cc	Wed May 08 16:43:32 2024 -0400
+++ b/src/core/anime.cc	Wed May 08 16:44:27 2024 -0400
@@ -170,6 +170,13 @@
 	return info_.id;
 }
 
+std::optional<std::string> Anime::GetServiceId(Service service) const {
+	if (info_.ids.find(service) == info_.ids.end())
+		return std::nullopt;
+
+	return info_.ids.at(service);
+}
+
 /* note: this should use std::optional */
 std::optional<std::string> Anime::GetTitle(TitleLanguage language) const {
 	if (info_.titles.find(language) == info_.titles.end())
@@ -239,13 +246,21 @@
 }
 
 std::string Anime::GetPosterUrl() const {
+	/* this isn't really service-specific. this could use
+	 * kitsu, MAL, or anilist, and would achieve basically
+	 * the same effect. */
 	return info_.poster_url;
 }
 
-std::string Anime::GetServiceUrl() const {
+std::optional<std::string> Anime::GetServiceUrl(Service service) const {
 	/* todo: add support for other services... */
-	switch (session.config.service) {
-		case Service::AniList: return "https://anilist.co/anime/" + Strings::ToUtf8String(GetId());
+	std::optional<std::string> id = GetServiceId(service);
+	if (!id.has_value())
+		return std::nullopt;
+
+	switch (service) {
+		case Service::AniList:
+			return "https://anilist.co/anime/" + id.value();
 		default: return "";
 	}
 }
@@ -254,6 +269,10 @@
 	info_.id = id;
 }
 
+void Anime::SetServiceId(Service service, const std::string& id) {
+	info_.ids[service] = id;
+}
+
 void Anime::SetTitle(TitleLanguage language, const std::string& title) {
 	info_.titles[language] = title;
 }
--- a/src/core/anime_db.cc	Wed May 08 16:43:32 2024 -0400
+++ b/src/core/anime_db.cc	Wed May 08 16:44:27 2024 -0400
@@ -179,6 +179,12 @@
 			json["title"][Strings::ToLower(Translate::ToString(lang))] = title.value();
 	}
 
+	for (const auto& service : Services) {
+		std::optional<std::string> id = anime.GetServiceId(service);
+		if (id.has_value())
+			json["ids"][Strings::ToLower(Translate::ToString(service))] = id.value();
+	}
+
 	nlohmann::json user;
 	if (GetListDataAsJSON(anime, user))
 		json.push_back({"list_data", user});
--- a/src/core/config.cc	Wed May 08 16:43:32 2024 -0400
+++ b/src/core/config.cc	Wed May 08 16:44:27 2024 -0400
@@ -133,7 +133,7 @@
 	ini["Anime List"]["Display highlighted anime above others"] = Strings::ToUtf8String(anime_list.highlighted_anime_above_others);
 
 	ini["Authentication/AniList"]["Auth Token"] = auth.anilist.auth_token;
-	ini["Authentication/AniList"]["User ID"] = auth.anilist.user_id;
+	ini["Authentication/AniList"]["User ID"] = Strings::ToUtf8String(auth.anilist.user_id);
 
 	ini["Appearance"]["Theme"] = Translate::ToString(theme.GetTheme());
 
--- a/src/gui/translate/anime.cc	Wed May 08 16:43:32 2024 -0400
+++ b/src/gui/translate/anime.cc	Wed May 08 16:44:27 2024 -0400
@@ -60,6 +60,8 @@
 std::string ToString(const Anime::Service service) {
 	switch (service) {
 		case Anime::Service::AniList: return "AniList";
+		case Anime::Service::MyAnimeList: return "MyAnimeList";
+		case Anime::Service::Kitsu: return "Kitsu";
 		default:
 		case Anime::Service::None: return "None";
 	}
@@ -144,7 +146,9 @@
 
 Anime::Service ToService(const std::string& str) {
 	static const std::unordered_map<std::string, Anime::Service> map = {
-	    {"AniList", Anime::Service::AniList}
+	    {"AniList", Anime::Service::AniList},
+	    {"MyAnimeList", Anime::Service::MyAnimeList},
+	    {"Kitsu", Anime::Service::Kitsu}
     };
 
 	if (map.find(str) == map.end())
@@ -238,6 +242,8 @@
 std::string ToLocalString(const Anime::Service service) {
 	switch (service) {
 		case Anime::Service::AniList: return Strings::ToUtf8String(QCoreApplication::tr("AniList"));
+		case Anime::Service::MyAnimeList: return Strings::ToUtf8String(QCoreApplication::tr("MyAnimeList"));
+		case Anime::Service::Kitsu: return Strings::ToUtf8String(QCoreApplication::tr("Kitsu"));
 		default:
 		case Anime::Service::None: return Strings::ToUtf8String(QCoreApplication::tr("None"));
 	}
--- a/src/gui/widgets/poster.cc	Wed May 08 16:43:32 2024 -0400
+++ b/src/gui/widgets/poster.cc	Wed May 08 16:44:27 2024 -0400
@@ -44,7 +44,9 @@
 		thread->start();
 	}
 
-	service_url_ = Strings::ToQString(anime.GetServiceUrl());
+	std::optional<std::string> url = anime.GetServiceUrl(session.config.service);
+	if (url)
+		service_url_ = Strings::ToQString(url.value());
 
 	if (clickable_) {
 		label_.disconnect();
--- a/src/services/anilist.cc	Wed May 08 16:43:32 2024 -0400
+++ b/src/services/anilist.cc	Wed May 08 16:44:27 2024 -0400
@@ -26,7 +26,7 @@
 namespace Services {
 namespace AniList {
 
-constexpr int CLIENT_ID = 13706;
+static constexpr int CLIENT_ID = 13706;
 
 class Account {
 public:
@@ -34,7 +34,7 @@
 	void SetUserId(const int id) { session.config.auth.anilist.user_id = id; }
 
 	std::string AuthToken() const { return session.config.auth.anilist.auth_token; }
-	void SetAuthToken(std::string const& auth_token) { session.config.auth.anilist.auth_token = auth_token; }
+	void SetAuthToken(const std::string& auth_token) { session.config.auth.anilist.auth_token = auth_token; }
 
 	bool Authenticated() const { return !AuthToken().empty(); }
 	bool IsValid() const { return UserId() && Authenticated(); }
@@ -130,6 +130,8 @@
 
 	Anime::Anime& anime = Anime::db.items[id];
 	anime.SetId(id);
+	anime.SetServiceId(Anime::Service::AniList, Strings::ToUtf8String(id));
+	anime.SetServiceId(Anime::Service::MyAnimeList, Strings::ToUtf8String(JSON::GetNumber(json, "/id_mal"_json_pointer)));
 
 	ParseTitle(json.at("/title"_json_pointer), anime);
 
@@ -266,6 +268,7 @@
 	                                   "        large\n"
 	                                   "      }\n"
 	                                   "      id\n"
+	                                   "      id_mal\n"
 	                                   "      title {\n"
 	                                   "        romaji\n"
 	                                   "        english\n"