comparison src/services/kitsu.cc @ 389:1e5d922fe82b default tip

kitsu: implement UpdateAnimeEntry yay... i guess
author Paper <paper@tflc.us>
date Thu, 06 Nov 2025 12:21:35 -0500
parents 83aa0ddd1a46
children
comparison
equal deleted inserted replaced
388:83aa0ddd1a46 389:1e5d922fe82b
137 } 137 }
138 138
139 /* ----------------------------------------------------------------------------- */ 139 /* ----------------------------------------------------------------------------- */
140 140
141 static std::optional<nlohmann::json> SendJSONAPIRequest(const std::string &path, 141 static std::optional<nlohmann::json> SendJSONAPIRequest(const std::string &path,
142 const std::map<std::string, std::string> &params = {}) 142 const std::map<std::string, std::string> &params = {},
143 const std::string &data = "",
144 HTTP::Type type = HTTP::Type::Get)
143 { 145 {
144 std::optional<std::string> token = AccountAccessToken(); 146 std::optional<std::string> token = AccountAccessToken();
145 if (!token) 147 if (!token)
146 return std::nullopt; 148 return std::nullopt;
147 149
149 "Authorization: Bearer " + token.value(), 151 "Authorization: Bearer " + token.value(),
150 "Content-Type: application/vnd.api+json"}; 152 "Content-Type: application/vnd.api+json"};
151 153
152 const std::string url = HTTP::EncodeParamsList(std::string(BASE_API_PATH) + path, params); 154 const std::string url = HTTP::EncodeParamsList(std::string(BASE_API_PATH) + path, params);
153 155
154 const std::string response = Strings::ToUtf8String(HTTP::Request(url, headers, "", HTTP::Type::Get)); 156 const std::string response = Strings::ToUtf8String(HTTP::Request(url, headers, data, type));
155 if (response.empty()) 157 if (response.empty())
156 return std::nullopt; 158 return std::nullopt;
157 159
158 nlohmann::json json; 160 nlohmann::json json;
159 try { 161 try {
163 fmt::format(Strings::Translate("Kitsu: Failed to parse response with error \"{}\""), ex.what())); 165 fmt::format(Strings::Translate("Kitsu: Failed to parse response with error \"{}\""), ex.what()));
164 return std::nullopt; 166 return std::nullopt;
165 } 167 }
166 168
167 if (json.contains("/errors"_json_pointer)) { 169 if (json.contains("/errors"_json_pointer)) {
170 std::cout << json["/errors"_json_pointer] << '\n';
171 #if 0
168 for (const auto &item : json["/errors"]) 172 for (const auto &item : json["/errors"])
169 std::cerr << "Kitsu: API returned error \"" << json["/errors/title"_json_pointer] << "\" with detail \"" 173 std::cerr << "Kitsu: API returned error \"" << json["/errors/title"_json_pointer] << "\" with detail \""
170 << json["/errors/detail"] << std::endl; 174 << json["/errors/detail"] << std::endl;
175 #endif
171 176
172 session.SetStatusBar(Strings::Translate("Kitsu: Request failed with errors!")); 177 session.SetStatusBar(Strings::Translate("Kitsu: Request failed with errors!"));
173 return std::nullopt; 178 return std::nullopt;
174 } 179 }
175 180
606 ParseAnimeJson(item); 611 ParseAnimeJson(item);
607 612
608 return true; 613 return true;
609 } 614 }
610 615
616 static std::string UserStatusToString(Anime::ListStatus status)
617 {
618 switch (status) {
619 case Anime::ListStatus::Planning: return "planned";
620 case Anime::ListStatus::Completed: return "completed";
621 case Anime::ListStatus::Dropped: return "dropped";
622 case Anime::ListStatus::Paused: return "on_hold";
623 default: break;
624 }
625
626 return "current";
627 }
628
611 int UpdateAnimeEntry(int id) 629 int UpdateAnimeEntry(int id)
612 { 630 {
613 return 0; 631 const Anime::Anime &anime = Anime::db.items[id];
632 int score;
633
634 if (!anime.IsInUserList())
635 return 0; /* WTF */
636
637 nlohmann::json json = {
638 {"data", {
639 {"type", "libraryEntries"},
640 {"attributes", {
641 {"status", UserStatusToString(anime.GetUserStatus())},
642 {"progress", anime.GetUserProgress()},
643 {"reconsuming", anime.GetUserIsRewatching()},
644 {"reconsumeCount", anime.GetUserRewatchedTimes()},
645 {"notes", anime.GetUserNotes()},
646 {"private", anime.GetUserIsPrivate()},
647 // WTF is reactionSkipped?
648 {"startedAt", anime.GetUserDateStarted().GetAsISO8601()},
649 {"finishedAt", anime.GetUserDateCompleted().GetAsISO8601()},
650 }},
651 {"relationships", {
652 {"anime", {
653 {"data", {
654 {"type", "anime"},
655 {"id", anime.GetServiceId(Anime::Service::Kitsu)},
656 }}
657 }},
658 {"user", {
659 {"data", {
660 {"type", "users"},
661 {"id", session.config.auth.kitsu.user_id},
662 }}
663 }}
664 }}
665 }}
666 };
667
668 nlohmann::json &attributes = json["data"]["attributes"];
669
670 score = anime.GetUserScore() / 5;
671 if (score > 0) {
672 attributes["ratingTwenty"] = score;
673 } else {
674 attributes["ratingTwenty"] = nullptr;
675 }
676
677 /* I really don't like this */
678 std::string uid = anime.GetUserId();
679
680 std::string path = "/library-entries";
681 HTTP::Type type;
682
683 if (!uid.empty()) {
684 json["data"]["id"] = uid;
685 path = path + "/" + uid;
686 type = HTTP::Type::Patch;
687 } else {
688 type = HTTP::Type::Post;
689 }
690
691 std::optional<nlohmann::json> res = SendJSONAPIRequest(path, {}, json.dump(), type);
692 if (!res)
693 return 0;
694
695 /* TODO parse result; can reduces races */
696
697 session.SetStatusBar(Strings::Translate("Kitsu: Anime entry updated successfully!"));
698
699 /* I guess? */
700 return Strings::ToInt<int>(anime.GetUserId());
614 } 701 }
615 702
616 bool AuthorizeUser(const std::string &email, const std::string &password) 703 bool AuthorizeUser(const std::string &email, const std::string &password)
617 { 704 {
618 const nlohmann::json body = { 705 const nlohmann::json body = {