154 lines
5.0 KiB
Rust
154 lines
5.0 KiB
Rust
use client::{MangaClient, MangaClientBuilder, SearchFilter};
|
|
use response_deserializer::{Chapter, Id, Language, Manga};
|
|
use select::Entry;
|
|
use std::future::Future;
|
|
use std::pin::Pin;
|
|
use util::SelectionType;
|
|
|
|
mod client;
|
|
mod error;
|
|
mod response_deserializer;
|
|
mod select;
|
|
mod test;
|
|
mod util;
|
|
|
|
const BASE: &str = "https://api.mangadex.dev";
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let config = util::args();
|
|
let manga_client =
|
|
MangaClientBuilder::new()
|
|
.bonus(config.bonus.unwrap_or_else(util::ask_bonus))
|
|
.cover(config.cover.unwrap_or_else(util::ask_cover))
|
|
.result_limit(config.result_limit.unwrap_or(5))
|
|
.cover_size(util::CoverSize::W512)
|
|
.selection_type(
|
|
config
|
|
.selection_type
|
|
.unwrap_or_else(util::ask_selection_type),
|
|
)
|
|
.selection_range(
|
|
config
|
|
.selection_range
|
|
.unwrap_or_else(util::ask_selection_range),
|
|
)
|
|
.search(config.search.unwrap_or_else(|| {
|
|
util::ConfigSearch::Query(util::get_input("Enter search query: "))
|
|
}))
|
|
.search_filter(SearchFilter::default())
|
|
.language(Language::default())
|
|
.build();
|
|
let search_result = manga_client.get_manga().await;
|
|
|
|
let mut choice = 0;
|
|
if search_result.len() > 1 {
|
|
choice = select_manga_from_search(&manga_client, &search_result).await;
|
|
}
|
|
let manga = &search_result[choice as usize];
|
|
let mut chapters = match manga_client.get_chapters(&manga.id).await {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
eprintln!("ERROR: {e:#?}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
println!("Downloading {} chapters", chapters.len());
|
|
chapters.sort_by(|a, b| {
|
|
a.attributes
|
|
.chapter
|
|
.unwrap_or(-1.)
|
|
.partial_cmp(&b.attributes.chapter.unwrap_or(-1.))
|
|
.unwrap()
|
|
});
|
|
let create_volumes = matches!(
|
|
manga_client.selection.selection_type,
|
|
SelectionType::Volume(_)
|
|
);
|
|
let selected_chapters = util::get_chapters_from_selection(manga_client.selection, &chapters);
|
|
|
|
let title = &manga.attributes.title.en;
|
|
|
|
util::download_the_stuff(
|
|
&manga_client.client,
|
|
&selected_chapters,
|
|
title,
|
|
create_volumes,
|
|
)
|
|
.await;
|
|
|
|
util::create_volumes_or_chapters(&selected_chapters, create_volumes, title);
|
|
}
|
|
|
|
async fn select_manga_from_search(client: &MangaClient, results: &[Manga]) -> u16 {
|
|
let cover_ex = match client.cover_size {
|
|
util::CoverSize::Full => "",
|
|
util::CoverSize::W256 => ".256.jpg",
|
|
util::CoverSize::W512 => ".512.jpg",
|
|
};
|
|
|
|
let mut entry_futures: Vec<Pin<Box<dyn Future<Output = Entry>>>> = Vec::new();
|
|
assert!(!results.is_empty());
|
|
for result in results.iter() {
|
|
let mut entry = Entry::new(result.attributes.title.en.clone());
|
|
if let Some(year) = result.attributes.year {
|
|
entry.add_info("year", year);
|
|
}
|
|
let id = result.id.to_string();
|
|
entry.add_info("id", &id);
|
|
entry.add_info("status", result.attributes.status.to_string());
|
|
entry.add_info(
|
|
"content rating",
|
|
result.attributes.content_rating.to_string(),
|
|
);
|
|
if let Some(chapters) = result.attributes.last_chapter {
|
|
entry.add_info("chapters", chapters);
|
|
}
|
|
if let Some(volumes) = result.attributes.last_volume {
|
|
entry.add_info("volumes", volumes);
|
|
}
|
|
if let Some(cover_data) = &result.relationships[2].attributes {
|
|
// The lib used for converting to sixel is abysmally slow for larger images, this
|
|
// should be in a future to allow for multithreaded work
|
|
// Cover data should only be present if used
|
|
assert!(client.cover);
|
|
let future = async move {
|
|
let image_url = format!(
|
|
"https://uploads.mangadex.org/covers/{id}/{}{cover_ex}",
|
|
&cover_data.file_name
|
|
);
|
|
let data = client
|
|
.client
|
|
.get(&image_url)
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.bytes()
|
|
.await
|
|
.unwrap();
|
|
let result = util::convert_to_sixel(&data);
|
|
|
|
entry.set_image(result);
|
|
entry
|
|
};
|
|
entry_futures.push(Box::pin(future));
|
|
} else {
|
|
entry_futures.push(Box::pin(async move { entry }));
|
|
}
|
|
}
|
|
let entries: Vec<Entry> = futures::future::join_all(entry_futures).await;
|
|
assert!(!entries.is_empty());
|
|
if entries.len() == 1 {
|
|
return 0;
|
|
}
|
|
|
|
let choice = match select::select(&entries) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
eprintln!("ERROR: Failed to select: {e:?}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
choice
|
|
}
|