# HG changeset patch # User Paper # Date 1762449695 18000 # Node ID 1e5d922fe82bf0e7914e9e160aba35646174c3d2 # Parent 83aa0ddd1a46f839acb269f54d9b0fcfe0676e75 kitsu: implement UpdateAnimeEntry yay... i guess diff -r 83aa0ddd1a46 -r 1e5d922fe82b include/core/date.h --- a/include/core/date.h Thu Nov 06 09:53:06 2025 -0500 +++ b/include/core/date.h Thu Nov 06 12:21:35 2025 -0500 @@ -56,6 +56,7 @@ std::optional GetDay() const; QDate GetAsQDate() const; nlohmann::json GetAsAniListJson() const; + std::string GetAsISO8601() const; private: std::optional year; diff -r 83aa0ddd1a46 -r 1e5d922fe82b include/core/http.h --- a/include/core/http.h Thu Nov 06 09:53:06 2025 -0500 +++ b/include/core/http.h Thu Nov 06 12:21:35 2025 -0500 @@ -18,7 +18,8 @@ enum class Type { Get, - Post + Post, + Patch }; QByteArray Request(const std::string &url, const std::vector &headers = {}, const std::string &data = "", diff -r 83aa0ddd1a46 -r 1e5d922fe82b include/gui/widgets/anime_info.h --- a/include/gui/widgets/anime_info.h Thu Nov 06 09:53:06 2025 -0500 +++ b/include/gui/widgets/anime_info.h Thu Nov 06 12:21:35 2025 -0500 @@ -37,6 +37,7 @@ public: AnimeInfoWidget(QWidget* parent = nullptr); AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent = nullptr); + ~AnimeInfoWidget(); void SetAnime(const Anime::Anime& anime); protected: diff -r 83aa0ddd1a46 -r 1e5d922fe82b src/core/anime_db.cc --- a/src/core/anime_db.cc Thu Nov 06 09:53:06 2025 -0500 +++ b/src/core/anime_db.cc Thu Nov 06 12:21:35 2025 -0500 @@ -146,6 +146,7 @@ // clang-format off json = { + {"id", anime.GetUserId()}, {"status", Translate::ToString(anime.GetUserStatus())}, {"progress", anime.GetUserProgress()}, {"score", anime.GetUserScore()}, @@ -236,6 +237,7 @@ if (!anime.IsInUserList()) anime.AddToUserList(); + anime.SetUserId(JSON::GetString(json, "/id"_json_pointer, "")); anime.SetUserStatus(Translate::ToListStatus(JSON::GetString(json, "/status"_json_pointer, ""))); anime.SetUserProgress(JSON::GetNumber(json, "/progress"_json_pointer, 0)); anime.SetUserScore(JSON::GetNumber(json, "/score"_json_pointer, 0)); diff -r 83aa0ddd1a46 -r 1e5d922fe82b src/core/date.cc --- a/src/core/date.cc Thu Nov 06 09:53:06 2025 -0500 +++ b/src/core/date.cc Thu Nov 06 12:21:35 2025 -0500 @@ -154,3 +154,19 @@ return json; } + +std::string Date::GetAsISO8601() const +{ + std::stringstream res; + + res << year.value_or(2000); + res << '-'; + res << (static_cast(month.value_or(Date::Month::Jan)) + 1); + res << '-'; + res << day.value_or(1); + + /* fake the rest... */ + res << "T00:00:00.000Z"; + + return res.str(); +} diff -r 83aa0ddd1a46 -r 1e5d922fe82b src/core/http.cc --- a/src/core/http.cc Thu Nov 06 09:53:06 2025 -0500 +++ b/src/core/http.cc Thu Nov 06 12:21:35 2025 -0500 @@ -82,8 +82,10 @@ list = curl_slist_append(list, h.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - if (type == Type::Post) + if (type == Type::Post || type == Type::Patch) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + if (type == Type::Patch) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); @@ -165,7 +167,7 @@ if (curl) { curl_easy_setopt(curl, CURLOPT_URL, url_.c_str()); - if (type_ == Type::Post) + if (type_ == Type::Post || type_ == Type::Patch) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data_.c_str()); for (const std::string &h : headers_) diff -r 83aa0ddd1a46 -r 1e5d922fe82b src/gui/widgets/anime_info.cc --- a/src/gui/widgets/anime_info.cc Thu Nov 06 09:53:06 2025 -0500 +++ b/src/gui/widgets/anime_info.cc Thu Nov 06 12:21:35 2025 -0500 @@ -84,6 +84,12 @@ SetAnime(anime); } +AnimeInfoWidget::~AnimeInfoWidget() +{ + disconnect(&get_metadata_thread, nullptr, this, nullptr); + get_metadata_thread.wait(); +} + void AnimeInfoWidget::SetAnime(const Anime::Anime &anime) { setUpdatesEnabled(false); diff -r 83aa0ddd1a46 -r 1e5d922fe82b src/services/kitsu.cc --- a/src/services/kitsu.cc Thu Nov 06 09:53:06 2025 -0500 +++ b/src/services/kitsu.cc Thu Nov 06 12:21:35 2025 -0500 @@ -139,7 +139,9 @@ /* ----------------------------------------------------------------------------- */ static std::optional SendJSONAPIRequest(const std::string &path, - const std::map ¶ms = {}) + const std::map ¶ms = {}, + const std::string &data = "", + HTTP::Type type = HTTP::Type::Get) { std::optional token = AccountAccessToken(); if (!token) @@ -151,7 +153,7 @@ const std::string url = HTTP::EncodeParamsList(std::string(BASE_API_PATH) + path, params); - const std::string response = Strings::ToUtf8String(HTTP::Request(url, headers, "", HTTP::Type::Get)); + const std::string response = Strings::ToUtf8String(HTTP::Request(url, headers, data, type)); if (response.empty()) return std::nullopt; @@ -165,9 +167,12 @@ } if (json.contains("/errors"_json_pointer)) { + std::cout << json["/errors"_json_pointer] << '\n'; +#if 0 for (const auto &item : json["/errors"]) std::cerr << "Kitsu: API returned error \"" << json["/errors/title"_json_pointer] << "\" with detail \"" << json["/errors/detail"] << std::endl; +#endif session.SetStatusBar(Strings::Translate("Kitsu: Request failed with errors!")); return std::nullopt; @@ -608,9 +613,91 @@ return true; } +static std::string UserStatusToString(Anime::ListStatus status) +{ + switch (status) { + case Anime::ListStatus::Planning: return "planned"; + case Anime::ListStatus::Completed: return "completed"; + case Anime::ListStatus::Dropped: return "dropped"; + case Anime::ListStatus::Paused: return "on_hold"; + default: break; + } + + return "current"; +} + int UpdateAnimeEntry(int id) { - return 0; + const Anime::Anime &anime = Anime::db.items[id]; + int score; + + if (!anime.IsInUserList()) + return 0; /* WTF */ + + nlohmann::json json = { + {"data", { + {"type", "libraryEntries"}, + {"attributes", { + {"status", UserStatusToString(anime.GetUserStatus())}, + {"progress", anime.GetUserProgress()}, + {"reconsuming", anime.GetUserIsRewatching()}, + {"reconsumeCount", anime.GetUserRewatchedTimes()}, + {"notes", anime.GetUserNotes()}, + {"private", anime.GetUserIsPrivate()}, + // WTF is reactionSkipped? + {"startedAt", anime.GetUserDateStarted().GetAsISO8601()}, + {"finishedAt", anime.GetUserDateCompleted().GetAsISO8601()}, + }}, + {"relationships", { + {"anime", { + {"data", { + {"type", "anime"}, + {"id", anime.GetServiceId(Anime::Service::Kitsu)}, + }} + }}, + {"user", { + {"data", { + {"type", "users"}, + {"id", session.config.auth.kitsu.user_id}, + }} + }} + }} + }} + }; + + nlohmann::json &attributes = json["data"]["attributes"]; + + score = anime.GetUserScore() / 5; + if (score > 0) { + attributes["ratingTwenty"] = score; + } else { + attributes["ratingTwenty"] = nullptr; + } + + /* I really don't like this */ + std::string uid = anime.GetUserId(); + + std::string path = "/library-entries"; + HTTP::Type type; + + if (!uid.empty()) { + json["data"]["id"] = uid; + path = path + "/" + uid; + type = HTTP::Type::Patch; + } else { + type = HTTP::Type::Post; + } + + std::optional res = SendJSONAPIRequest(path, {}, json.dump(), type); + if (!res) + return 0; + + /* TODO parse result; can reduces races */ + + session.SetStatusBar(Strings::Translate("Kitsu: Anime entry updated successfully!")); + + /* I guess? */ + return Strings::ToInt(anime.GetUserId()); } bool AuthorizeUser(const std::string &email, const std::string &password)