Can now import anime from Anilist by ID in Custom list
This commit is contained in:
parent
605e4c763a
commit
b0e32b6717
8 changed files with 156 additions and 37 deletions
0
src/httplib.h → external/httplib.h
vendored
0
src/httplib.h → external/httplib.h
vendored
37
src/AddAnimeWindow.slint
Normal file
37
src/AddAnimeWindow.slint
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,46 @@ public:
|
||||||
return std::get<rei::json::JsonObject>(rei::json::parse(res->body));
|
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) {
|
static std::optional<std::string> fetchImage(const std::string& path) {
|
||||||
httplib::Client cli("https://s4.anilist.co");
|
httplib::Client cli("https://s4.anilist.co");
|
||||||
cli.set_follow_location(true);
|
cli.set_follow_location(true);
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include "rei-json/Field.h"
|
||||||
|
#include "rei-json/Object.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "rei-json/Array.h"
|
#include "rei-json/Array.h"
|
||||||
#include "rei-json/json.h"
|
#include "rei-json/json.h"
|
||||||
|
@ -106,6 +108,7 @@ public:
|
||||||
|
|
||||||
animesSlint = std::make_shared<slint::VectorModel<ui::Anime>>();
|
animesSlint = std::make_shared<slint::VectorModel<ui::Anime>>();
|
||||||
ui->global<ui::State>().set_current_list({
|
ui->global<ui::State>().set_current_list({
|
||||||
|
.index = 0,
|
||||||
.name = "ok",
|
.name = "ok",
|
||||||
.animes = animesSlint
|
.animes = animesSlint
|
||||||
});
|
});
|
||||||
|
@ -137,31 +140,25 @@ public:
|
||||||
addLocalList(std::string(params.name));
|
addLocalList(std::string(params.name));
|
||||||
});
|
});
|
||||||
|
|
||||||
ui->global<ui::State>().on_sync_list([&](slint::SharedString name) {
|
ui->global<ui::State>().on_open_add_anime_window([&]() {
|
||||||
//std::println("{}",res->body);
|
addAnimeWindow->show();
|
||||||
try {
|
});
|
||||||
//addAnilistList("defaut", name, "Completed");
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
/*std::println("0000");*/
|
ui->global<ui::State>().on_sync_list([&]() {
|
||||||
/*selectList(saveJson.getArray("lists").getObject(0).getArray("animes"), animesSlint);*/
|
int currentListIndex = ui->global<ui::State>().get_current_list().index;
|
||||||
/*std::println("1111");*/
|
auto& listJson = json.getArray("lists").getObject(currentListIndex);
|
||||||
/*imgsToLoad.clear();*/
|
const std::string listName = listJson.getString("name");
|
||||||
/*std::println("2222");*/
|
const std::string anilistUserName = listJson.getString("anilistUserName");
|
||||||
/*saveJson.getArray("lists").getObject(0).getArray("animes").forEach([&] (rei::json::FieldValue& element, int index) {*/
|
const std::string anilistListName = listJson.getString("anilistListName");
|
||||||
/*std::println("IN");*/
|
addAnilistList(listName, anilistUserName, anilistListName);
|
||||||
/*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);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +198,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->global<ui::State>().set_current_list({
|
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")),
|
.name = slint::SharedString(list.getString("name")),
|
||||||
.animes = animesSlint
|
.animes = animesSlint
|
||||||
});
|
});
|
||||||
|
@ -215,9 +214,6 @@ public:
|
||||||
}
|
}
|
||||||
auto anilistJson = anilistJsonResult.value();
|
auto anilistJson = anilistJsonResult.value();
|
||||||
|
|
||||||
rei::json::JsonObject saveJson{};
|
|
||||||
saveJson.set("version", 1);
|
|
||||||
|
|
||||||
rei::json::JsonObject listJson{};
|
rei::json::JsonObject listJson{};
|
||||||
anilistJson.getObject("data").getObject("MediaListCollection").getArray("lists")
|
anilistJson.getObject("data").getObject("MediaListCollection").getArray("lists")
|
||||||
.forEach([&](rei::json::FieldValue& field, int index) {
|
.forEach([&](rei::json::FieldValue& field, int index) {
|
||||||
|
@ -238,19 +234,27 @@ public:
|
||||||
|
|
||||||
listJson.addArray("animes", animesJson);
|
listJson.addArray("animes", animesJson);
|
||||||
});
|
});
|
||||||
|
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);
|
json.getArray("lists").pushObject(listJson);
|
||||||
listsSlint->push_back(ui::List{
|
listsSlint->push_back(ui::List{
|
||||||
.name = slint::SharedString(listName),
|
.name = slint::SharedString(listName),
|
||||||
.selected = false
|
.selected = false
|
||||||
});
|
});
|
||||||
|
}
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addLocalList(const std::string& listName) {
|
void addLocalList(const std::string& listName) {
|
||||||
|
|
||||||
rei::json::JsonObject saveJson{};
|
|
||||||
saveJson.set("version", 1);
|
|
||||||
|
|
||||||
rei::json::JsonObject listJson{};
|
rei::json::JsonObject listJson{};
|
||||||
listJson.set("name", listName);
|
listJson.set("name", listName);
|
||||||
listJson.set("source", "local");
|
listJson.set("source", "local");
|
||||||
|
@ -265,6 +269,20 @@ public:
|
||||||
save();
|
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) {
|
slint::Image getAnimeImage(int animeId, std::string imageUrl) {
|
||||||
//std::println("Get image for anime {}", animeId);
|
//std::println("Get image for anime {}", animeId);
|
||||||
std::string imagePath = "./imgs/" + std::to_string(animeId);
|
std::string imagePath = "./imgs/" + std::to_string(animeId);
|
||||||
|
@ -322,6 +340,7 @@ private:
|
||||||
|
|
||||||
rei::json::JsonObject json;
|
rei::json::JsonObject json;
|
||||||
slint::ComponentHandle<ui::AppWindow> ui;
|
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::Anime>> animesSlint;
|
||||||
std::shared_ptr<slint::VectorModel<ui::List>> listsSlint;
|
std::shared_ptr<slint::VectorModel<ui::List>> listsSlint;
|
||||||
std::mutex animeListLock;
|
std::mutex animeListLock;
|
||||||
|
|
|
@ -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 { VText, VTextInput , VButton, ToggleButton, VActionButton, Svg, Palette } from "@selenite";
|
||||||
import { ComboBox } from "std-widgets.slint";
|
import { ComboBox } from "std-widgets.slint";
|
||||||
|
|
||||||
|
@ -117,6 +117,19 @@ export component AppWindow inherits Window {
|
||||||
text: "\{State.current-list.animes.length} animes";
|
text: "\{State.current-list.animes.length} animes";
|
||||||
font-size: 1rem;
|
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 {
|
animes-grid-background := Rectangle {
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
export enum ListSource {
|
||||||
|
Local,
|
||||||
|
Anilist
|
||||||
|
}
|
||||||
|
|
||||||
export struct Anime {
|
export struct Anime {
|
||||||
id: int,
|
id: int,
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -12,6 +17,8 @@ export struct List {
|
||||||
}
|
}
|
||||||
|
|
||||||
export struct CurrentList {
|
export struct CurrentList {
|
||||||
|
index: int,
|
||||||
|
source: ListSource,
|
||||||
name: string,
|
name: string,
|
||||||
animes: [Anime]
|
animes: [Anime]
|
||||||
}
|
}
|
||||||
|
@ -32,9 +39,11 @@ export global State {
|
||||||
in-out property <CurrentList> current-list;
|
in-out property <CurrentList> current-list;
|
||||||
|
|
||||||
callback select-list(int);
|
callback select-list(int);
|
||||||
callback sync-list(string);
|
callback sync-list();
|
||||||
callback add-anilist-list(AddAnilistListParams);
|
callback add-anilist-list(AddAnilistListParams);
|
||||||
callback add-local-list(AddLocalListParams);
|
callback add-local-list(AddLocalListParams);
|
||||||
|
|
||||||
|
callback open-add-anime-window();
|
||||||
|
|
||||||
callback config-changed();
|
callback config-changed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { AppWindow } from "./AppWindow.slint";
|
import { AppWindow } from "./AppWindow.slint";
|
||||||
|
import { AddAnimeWindow } from "./AddAnimeWindow.slint";
|
||||||
import { State } from "./state.slint";
|
import { State } from "./state.slint";
|
||||||
|
|
||||||
export { State, AppWindow }
|
export { State, AppWindow, AddAnimeWindow }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue