Can now import anime from Anilist by ID in Custom list

This commit is contained in:
Vyn 2024-11-21 11:59:39 +01:00
parent 605e4c763a
commit b0e32b6717
8 changed files with 156 additions and 37 deletions

View file

37
src/AddAnimeWindow.slint Normal file
View file

@ -0,0 +1,37 @@
import { State } from "./state.slint";
import { VText, VTextInput , VButton, ToggleButton, VActionButton, Svg, Palette } from "@selenite";
import { ComboBox } from "std-widgets.slint";
export component AddAnimeWindow inherits Window {
title: "Lali - Add List";
background: Palette.background;
default-font-size: 16px;
padding: 0px;
width: 200px;
private property <bool> show-add-list-form: false;
in-out property <string> anilist-anime-id;
callback import-anilist-anime(string);
VerticalLayout {
alignment: start;
padding: 16px;
spacing: 16px;
source := ComboBox {
model: ["Anilist", "Custom"];
}
if source.current-value == "Anilist" : VTextInput {
label: "Anilist anime ID";
text <=> anilist-anime-id;
}
VButton {
text: "Import";
clicked => { import-anilist-anime(anilist-anime-id) }
}
}
}

View file

@ -62,6 +62,46 @@ public:
return std::get<rei::json::JsonObject>(rei::json::parse(res->body));
}
static std::optional<rei::json::JsonObject> fetchAnimeById(int id) {
httplib::Client cli("https://graphql.anilist.co");
cli.set_follow_location(true);
rei::json::JsonObject body{};
rei::json::JsonObject variables{};
const std::string graphqlQuery = "query Query($mediaId: Int) {\\n Media(id: $mediaId) {\\n id\\n title {\\n romaji\\n }\\n description\\n episodes\\n genres\\n status\\n startDate {\\n year\\n month\\n day\\n }\\n endDate {\\n year\\n month\\n day\\n }\\n coverImage {\\n extraLarge\\n }\\n idMal\\n isAdult\\n siteUrl\\n bannerImage\\n }\\n}";
variables.set("mediaId", id);
body.set("query", graphqlQuery).addObject("variables", variables);
auto res = cli.Post("/", rei::json::toString(body), "application/json");
if (res.error() != httplib::Error::Success) {
std::cerr << res.error() << std::endl;
return std::nullopt;
}
if (!res->has_header("Content-Type")) {
std::cerr << "Missing 'Content-Type'" << std::endl;
return std::nullopt;
}
const std::string contentType = res->get_header_value("Content-Type");
if (!contentType.starts_with("application/json")) {
std::cerr << "Wrong content type" << " (" << contentType << ")" << std::endl;
return std::nullopt;
}
if (res.error() != httplib::Error::Success) {
std::cerr << res.error() << std::endl;
return std::nullopt;
}
if (res->status != 200) {
std::println("Status: {}\nBody: {}", res->status, res->body);
return std::nullopt;
}
return std::get<rei::json::JsonObject>(rei::json::parse(res->body));
}
static std::optional<std::string> fetchImage(const std::string& path) {
httplib::Client cli("https://s4.anilist.co");
cli.set_follow_location(true);

View file

@ -3,6 +3,8 @@
#include <mutex>
#include <vector>
#include <optional>
#include "rei-json/Field.h"
#include "rei-json/Object.h"
#include "ui.h"
#include "rei-json/Array.h"
#include "rei-json/json.h"
@ -106,6 +108,7 @@ public:
animesSlint = std::make_shared<slint::VectorModel<ui::Anime>>();
ui->global<ui::State>().set_current_list({
.index = 0,
.name = "ok",
.animes = animesSlint
});
@ -137,31 +140,25 @@ public:
addLocalList(std::string(params.name));
});
ui->global<ui::State>().on_sync_list([&](slint::SharedString name) {
//std::println("{}",res->body);
try {
//addAnilistList("defaut", name, "Completed");
ui->global<ui::State>().on_open_add_anime_window([&]() {
addAnimeWindow->show();
});
/*std::println("0000");*/
/*selectList(saveJson.getArray("lists").getObject(0).getArray("animes"), animesSlint);*/
/*std::println("1111");*/
/*imgsToLoad.clear();*/
/*std::println("2222");*/
/*saveJson.getArray("lists").getObject(0).getArray("animes").forEach([&] (rei::json::FieldValue& element, int index) {*/
/*std::println("IN");*/
/*imgsToLoad.push_back(element.asObject().getObject("coverImage").getString("extraLarge"));*/
/*std::println("OUT");*/
/*});*/
/*std::println("3333");*/
/*readLinks.unlock();*/
} catch (std::exception& e) {
std::println(std::cerr, "Error: {}", e.what());
} catch (char const* e) {
std::println(std::cerr, "Error str: {}", e);
}
addAnimeWindow->on_import_anilist_anime([&] (slint::SharedString animeIdStr) {
int animeId = stoi(std::string(animeIdStr));
int currentListIndex = ui->global<ui::State>().get_current_list().index;
addAnimeWindow->hide();
addAnimeWindow->set_anilist_anime_id("");
addAnilistAnime(currentListIndex, animeId);
});
ui->global<ui::State>().on_sync_list([&]() {
int currentListIndex = ui->global<ui::State>().get_current_list().index;
auto& listJson = json.getArray("lists").getObject(currentListIndex);
const std::string listName = listJson.getString("name");
const std::string anilistUserName = listJson.getString("anilistUserName");
const std::string anilistListName = listJson.getString("anilistListName");
addAnilistList(listName, anilistUserName, anilistListName);
});
}
@ -201,6 +198,8 @@ public:
}
ui->global<ui::State>().set_current_list({
.index = listIndex,
.source = list.getString("source") == "anilist" ? ui::ListSource::Anilist : ui::ListSource::Local,
.name = slint::SharedString(list.getString("name")),
.animes = animesSlint
});
@ -215,9 +214,6 @@ public:
}
auto anilistJson = anilistJsonResult.value();
rei::json::JsonObject saveJson{};
saveJson.set("version", 1);
rei::json::JsonObject listJson{};
anilistJson.getObject("data").getObject("MediaListCollection").getArray("lists")
.forEach([&](rei::json::FieldValue& field, int index) {
@ -238,19 +234,27 @@ public:
listJson.addArray("animes", animesJson);
});
json.getArray("lists").pushObject(listJson);
listsSlint->push_back(ui::List{
.name = slint::SharedString(listName),
.selected = false
std::optional<int> existingListIndex;
json.getArray("lists").forEach([&](rei::json::FieldValue& value, int index) {
if (value.asObject().getString("name") == listName) {
existingListIndex = index;
}
});
if (existingListIndex.has_value()) {
json.getArray("lists").getObject(existingListIndex.value()) = listJson;
selectList(existingListIndex.value());
} else {
json.getArray("lists").pushObject(listJson);
listsSlint->push_back(ui::List{
.name = slint::SharedString(listName),
.selected = false
});
}
save();
}
void addLocalList(const std::string& listName) {
rei::json::JsonObject saveJson{};
saveJson.set("version", 1);
rei::json::JsonObject listJson{};
listJson.set("name", listName);
listJson.set("source", "local");
@ -265,6 +269,20 @@ public:
save();
}
void addAnilistAnime(int currentListIndex, int animeId) {
auto res = Anilist::fetchAnimeById(animeId);
if (!res.has_value()) {
std::println(std::cerr, "Cannot fetch anime id {}", animeId);
return;
}
std::println("{}", rei::json::toString(res.value()));
rei::json::JsonArray& listJson = json.getArray("lists").getObject(currentListIndex).getArray("animes");
listJson.pushObject(res.value().getObject("data").getObject("Media"));
selectList(currentListIndex);
save();
}
slint::Image getAnimeImage(int animeId, std::string imageUrl) {
//std::println("Get image for anime {}", animeId);
std::string imagePath = "./imgs/" + std::to_string(animeId);
@ -322,6 +340,7 @@ private:
rei::json::JsonObject json;
slint::ComponentHandle<ui::AppWindow> ui;
slint::ComponentHandle<ui::AddAnimeWindow> addAnimeWindow = ui::AddAnimeWindow::create();
std::shared_ptr<slint::VectorModel<ui::Anime>> animesSlint;
std::shared_ptr<slint::VectorModel<ui::List>> listsSlint;
std::mutex animeListLock;

View file

@ -1,4 +1,4 @@
import { State } from "./state.slint";
import { State, ListSource } from "./state.slint";
import { VText, VTextInput , VButton, ToggleButton, VActionButton, Svg, Palette } from "@selenite";
import { ComboBox } from "std-widgets.slint";
@ -117,6 +117,19 @@ export component AppWindow inherits Window {
text: "\{State.current-list.animes.length} animes";
font-size: 1rem;
}
HorizontalLayout {
alignment: start;
if State.current-list.source == ListSource.Local : VButton {
icon-svg: Svg.plus;
text: "Add";
clicked => { State.open-add-anime-window() }
}
if State.current-list.source == ListSource.Anilist : VButton {
text: "Sync";
clicked => { State.sync-list() }
}
}
}
animes-grid-background := Rectangle {

View file

@ -16,7 +16,7 @@
#include <memory>
#include <ostream>
#include <string>
#include <print>
#include <print>
struct Config {
std::string timerEndingScript = "";

View file

@ -1,3 +1,8 @@
export enum ListSource {
Local,
Anilist
}
export struct Anime {
id: int,
title: string,
@ -12,6 +17,8 @@ export struct List {
}
export struct CurrentList {
index: int,
source: ListSource,
name: string,
animes: [Anime]
}
@ -32,9 +39,11 @@ export global State {
in-out property <CurrentList> current-list;
callback select-list(int);
callback sync-list(string);
callback sync-list();
callback add-anilist-list(AddAnilistListParams);
callback add-local-list(AddLocalListParams);
callback open-add-anime-window();
callback config-changed();
}

View file

@ -1,4 +1,5 @@
import { AppWindow } from "./AppWindow.slint";
import { AddAnimeWindow } from "./AddAnimeWindow.slint";
import { State } from "./state.slint";
export { State, AppWindow }
export { State, AppWindow, AddAnimeWindow }