replace zip command with zip lib, do languages properly i suppose, and other stuff i do not remember
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ Cargo.lock
|
||||
/cba
|
||||
*.jpg
|
||||
*.png
|
||||
*.cbz
|
||||
|
||||
316
src/main.rs
316
src/main.rs
@@ -1,6 +1,11 @@
|
||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||
use response_deserializer::{ChapterImages, SearchResult};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use zip::write::{SimpleFileOptions, ZipWriter};
|
||||
use zip::CompressionMethod;
|
||||
|
||||
mod response_deserializer;
|
||||
mod select;
|
||||
@@ -15,7 +20,7 @@ type Client = ClientWithMiddleware;
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config = util::args();
|
||||
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
|
||||
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(10);
|
||||
let client = ClientBuilder::new(
|
||||
reqwest::Client::builder()
|
||||
.user_agent("manga-cli/version-0.1")
|
||||
@@ -32,73 +37,26 @@ async fn main() {
|
||||
];
|
||||
let limit = config.result_limit;
|
||||
|
||||
let results = if let Some(query) = config.search {
|
||||
let mut choice = 0;
|
||||
let results = if let Some(util::ConfigSearch::Id(set_id)) = config.search {
|
||||
let id_query_result: response_deserializer::IdQueryResult =
|
||||
id_query_get_info(client, &set_id).await;
|
||||
vec![id_query_result.data]
|
||||
} else {
|
||||
let results = if let Some(ref query) = config.search {
|
||||
match query {
|
||||
util::ConfigSearch::Query(query) => search(client, &query, &filters, limit).await,
|
||||
util::ConfigSearch::Id(_) => todo!(),
|
||||
util::ConfigSearch::Query(query) => search(client, query, &filters, limit).await,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
let input = util::get_input("Enter search query: ");
|
||||
search(client, &input, &filters, limit).await
|
||||
};
|
||||
let cover_ex = match config.cover_size {
|
||||
util::CoverSize::Full => "",
|
||||
util::CoverSize::W256 => ".256.jpg",
|
||||
util::CoverSize::W512 => ".512.jpg",
|
||||
choice = select_manga_from_search(client, &config, &results).await;
|
||||
results.data
|
||||
};
|
||||
let manga = &results[choice as usize];
|
||||
|
||||
let mut entry_futures = Vec::new();
|
||||
for result in results.data.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
|
||||
let future = async move {
|
||||
let image_url = format!(
|
||||
"https://uploads.mangadex.org/covers/{id}/{}{cover_ex}",
|
||||
&cover_data.file_name
|
||||
);
|
||||
let data = 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(future);
|
||||
}
|
||||
}
|
||||
let entries = futures::future::join_all(entry_futures).await;
|
||||
let choice = match select::select(&entries) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("ERROR: Failed to select: {:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let choice_id = &results.data[choice as usize].id;
|
||||
let bonus = if let Some(bonus) = config.bonus {
|
||||
bonus
|
||||
} else {
|
||||
@@ -111,6 +69,24 @@ async fn main() {
|
||||
}
|
||||
};
|
||||
let selection_type = if let Some(selection_type) = config.selection_type {
|
||||
if let Some(selection_range) = config.selection_range {
|
||||
match selection_type {
|
||||
util::ConfigSelectionType::Volume => {
|
||||
if let Some(selection) = util::choose_volumes(selection_range.as_str()) {
|
||||
util::SelectionType::Volume(selection)
|
||||
} else {
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
util::ConfigSelectionType::Chapter => {
|
||||
if let Some(selection) = util::choose_chapters(selection_range.as_str()) {
|
||||
util::SelectionType::Chapter(selection)
|
||||
} else {
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match selection_type {
|
||||
util::ConfigSelectionType::Volume => loop {
|
||||
let input = util::get_input("Choose volumes: ").replace(" ", "");
|
||||
@@ -125,28 +101,49 @@ async fn main() {
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
'outer: loop {
|
||||
match util::get_input("Select by volume or chapter? [v/c] : ").as_str() {
|
||||
"v" | "volume" => loop {
|
||||
"v" | "volume" => {
|
||||
if let Some(ref selection_range) = config.selection_range {
|
||||
if let Some(selection) = util::choose_volumes(selection_range.as_str()) {
|
||||
break util::SelectionType::Volume(selection);
|
||||
} else {
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
let input = util::get_input("Choose volumes: ").replace(" ", "");
|
||||
if let Some(selection) = util::choose_volumes(input.as_str()) {
|
||||
break 'outer util::SelectionType::Volume(selection);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
"c" | "chapter" => {
|
||||
if let Some(ref selection_range) = config.selection_range {
|
||||
if let Some(selection) = util::choose_chapters(selection_range.as_str()) {
|
||||
break util::SelectionType::Chapter(selection);
|
||||
} else {
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
let input = util::get_input("Choose chapters: ").replace(" ", "");
|
||||
if let Some(selection) = util::choose_chapters(input.as_str()) {
|
||||
break 'outer util::SelectionType::Chapter(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Invalid input");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut chapters = match get_chapters(client, choice_id).await {
|
||||
let mut chapters = match get_chapters(client, &manga.id).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("ERROR: {:#?}", e);
|
||||
@@ -166,11 +163,9 @@ async fn main() {
|
||||
let selected_chapters =
|
||||
util::get_chapters_from_selection(util::Selection::new(selection_type, bonus), &chapters);
|
||||
|
||||
let mut chapters_image_data = Vec::new();
|
||||
let mut i = 0;
|
||||
for chapter in &selected_chapters {
|
||||
// rate limits beware
|
||||
let r = loop {
|
||||
let title = &manga.attributes.title;
|
||||
for (i, chapter) in selected_chapters.iter().enumerate() {
|
||||
let chapter_image_data = loop {
|
||||
let json = client
|
||||
.get(format!("{BASE}/at-home/server/{}", chapter.id))
|
||||
.send()
|
||||
@@ -195,40 +190,40 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
};
|
||||
std::thread::sleep(std::time::Duration::from_millis(800));
|
||||
println!("downloaded chapter json image data: {i}");
|
||||
i += 1;
|
||||
chapters_image_data.push(r);
|
||||
}
|
||||
|
||||
let chapters = futures::future::join_all(
|
||||
chapters_image_data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, image_data)| {
|
||||
download_chapter_images(client, image_data, selected_chapters[i])
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
for (i, chapter) in chapters.iter().enumerate() {
|
||||
println!(
|
||||
"\x1b[1A\x1b[2Kdownloaded chapter json image data: [{i}/{}]",
|
||||
selected_chapters.len()
|
||||
);
|
||||
let chapter =
|
||||
download_chapter_images(client, &chapter_image_data, selected_chapters[i]).await;
|
||||
match chapter {
|
||||
Ok(chapter) => {
|
||||
for (j, image) in chapter.iter().enumerate() {
|
||||
let chapter_n = selected_chapters[i].attributes.chapter.unwrap();
|
||||
let chapter_n = selected_chapters[i].attributes.chapter;
|
||||
let chapter_text = if let Some(n) = chapter_n {
|
||||
n.to_string()
|
||||
} else {
|
||||
String::from("_none")
|
||||
};
|
||||
let chapter_path = format!(
|
||||
"images/{}/chapter_{:0>3}_image_{:0>3}.png",
|
||||
title.en, chapter_text, j
|
||||
);
|
||||
let path = if let Some(v) = selected_chapters[i].attributes.volume {
|
||||
if create_volumes {
|
||||
format!(
|
||||
"images/{}/volume_{:0>3}/chapter{:0>3}_image_{:0>3}.png",
|
||||
results.data[choice as usize].attributes.title.en, v, chapter_n, j
|
||||
"images/{}/volume_{:0>3}/chapter_{:0>3}_image_{:0>3}.png",
|
||||
title.en, v, chapter_text, j
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"images/{}/chapter{:0>3}_image_{:0>3}.png",
|
||||
results.data[choice as usize].attributes.title.en, chapter_n, j
|
||||
)
|
||||
chapter_path
|
||||
}
|
||||
} else {
|
||||
chapter_path
|
||||
};
|
||||
let path = std::path::Path::new(&path);
|
||||
if selected_chapters[i].attributes.volume.is_some()
|
||||
if create_volumes
|
||||
&& selected_chapters[i].attributes.volume.is_some()
|
||||
&& !&path.parent().unwrap().exists()
|
||||
{
|
||||
if !path.parent().unwrap().parent().unwrap().exists() {
|
||||
@@ -245,7 +240,6 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let title = &results.data[choice as usize].attributes.title;
|
||||
if create_volumes {
|
||||
let mut volumes = Vec::new();
|
||||
selected_chapters
|
||||
@@ -257,14 +251,55 @@ async fn main() {
|
||||
}
|
||||
});
|
||||
for volume in volumes {
|
||||
let path = format!("images/{}/volume_{:0>3}", title.en, volume);
|
||||
let file_name = format!("{} - Volume {:0>3}.cbz", title.en, volume);
|
||||
std::process::Command::new("/usr/bin/zip")
|
||||
.args(["-j", "-r", &file_name, &path])
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
let image_paths = format!("images/{}/volume_{:0>3}", title.en, volume);
|
||||
let image_paths = Path::new(&image_paths);
|
||||
|
||||
let zip_file_path = format!("{} - Volume {:0>3}.cbz", title.en, volume);
|
||||
let zip_file_path = Path::new(&zip_file_path);
|
||||
let zip_file = File::create(&zip_file_path).unwrap();
|
||||
|
||||
let mut zip = ZipWriter::new(zip_file);
|
||||
let options =
|
||||
SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
|
||||
|
||||
for entry in std::fs::read_dir(image_paths).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
zip.start_file(entry.file_name().to_str().unwrap(), options)
|
||||
.unwrap();
|
||||
|
||||
let buffer = std::fs::read(entry.path()).unwrap();
|
||||
zip.write_all(&buffer).unwrap();
|
||||
}
|
||||
|
||||
zip.finish().unwrap();
|
||||
}
|
||||
} else {
|
||||
for chapter in selected_chapters {
|
||||
let chapter = chapter.attributes.chapter.unwrap();
|
||||
let image_paths = format!("images/{}", title.en);
|
||||
let image_paths = Path::new(&image_paths);
|
||||
|
||||
let zip_file_path = format!("{} - Chapter {:0>3}.cbz", title.en, chapter);
|
||||
let zip_file_path = Path::new(&zip_file_path);
|
||||
let zip_file = File::create(&zip_file_path).unwrap();
|
||||
|
||||
let mut zip = ZipWriter::new(zip_file);
|
||||
let options =
|
||||
SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
|
||||
|
||||
for entry in std::fs::read_dir(image_paths).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
if entry.path().is_dir() {
|
||||
continue;
|
||||
}
|
||||
zip.start_file(entry.file_name().to_str().unwrap(), options)
|
||||
.unwrap();
|
||||
|
||||
let buffer = std::fs::read(entry.path()).unwrap();
|
||||
zip.write_all(&buffer).unwrap();
|
||||
}
|
||||
|
||||
zip.finish().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +324,7 @@ async fn download_chapter_images(
|
||||
.await
|
||||
.unwrap();
|
||||
println!(
|
||||
"Downloaded volume: {:?}, chapter: {:?}, title: {}, [{}/{}]",
|
||||
"\x1b[1A\x1b[2KDownloaded volume: {:?}, chapter: {:?}, title: {}, [{}/{}]",
|
||||
chapter.attributes.volume,
|
||||
chapter.attributes.chapter,
|
||||
chapter.attributes.title,
|
||||
@@ -356,3 +391,80 @@ async fn search(
|
||||
.unwrap();
|
||||
response_deserializer::deserializer(&json)
|
||||
}
|
||||
|
||||
async fn select_manga_from_search(
|
||||
client: &Client,
|
||||
config: &util::Config,
|
||||
results: &SearchResult,
|
||||
) -> u16 {
|
||||
let cover_ex = match config.cover_size {
|
||||
util::CoverSize::Full => "",
|
||||
util::CoverSize::W256 => ".256.jpg",
|
||||
util::CoverSize::W512 => ".512.jpg",
|
||||
};
|
||||
|
||||
let mut entry_futures = Vec::new();
|
||||
for result in results.data.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
|
||||
let future = async move {
|
||||
let image_url = format!(
|
||||
"https://uploads.mangadex.org/covers/{id}/{}{cover_ex}",
|
||||
&cover_data.file_name
|
||||
);
|
||||
let data = 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(future);
|
||||
}
|
||||
}
|
||||
let entries = futures::future::join_all(entry_futures).await;
|
||||
let choice = match select::select(&entries) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("ERROR: Failed to select: {:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
choice
|
||||
}
|
||||
|
||||
async fn id_query_get_info(client: &Client, id: &Id) -> response_deserializer::IdQueryResult {
|
||||
let json = client
|
||||
.get(format!("{BASE}/manga/{id}"))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
response_deserializer::deserialize_id_query(&json)
|
||||
}
|
||||
|
||||
@@ -12,36 +12,243 @@ pub enum ResponseResult {
|
||||
Ok,
|
||||
}
|
||||
|
||||
// https://api.mangadex.org/docs/3-enumerations/
|
||||
// https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
|
||||
// https://www.loc.gov/standards/iso639-2/php/English_list.php
|
||||
// This is a fucking mess, I hate this. I could have just used a library, but I would have to adapt
|
||||
// it to the special cases which mangadex require.
|
||||
// The two-letter codes are not unique, and the code esentially just choose the first one in the
|
||||
// list. Why have identifiers if they are not unique? Who knows what they were smoking when making
|
||||
// this. Also Why is there Bouth SouthNdebele and Ndebele, South? Who knows? Why is there Bokmål
|
||||
// and Norwegian Bokmål, many questions to ask, but few answers to get. Best part of this is that
|
||||
// the updated ISO pdf is behind a paywall. https://www.iso.org/standard/74575.html
|
||||
// If the code works 98% of the time and is 95% correct I guess that is "good enough".
|
||||
#[derive(Debug)]
|
||||
pub enum Language {
|
||||
Tagalog,
|
||||
SimplifiedChinese,
|
||||
Greek,
|
||||
Persian,
|
||||
TraditionalChinese,
|
||||
Ukranian,
|
||||
Romanian,
|
||||
Abkhazian,
|
||||
Afar,
|
||||
Afrikaans,
|
||||
Akan,
|
||||
Albanian,
|
||||
Amharic,
|
||||
Arabic,
|
||||
German,
|
||||
Vietnamese,
|
||||
French,
|
||||
Turkish,
|
||||
Korean,
|
||||
SpanishLatinAmerican,
|
||||
Hungarian,
|
||||
Aragonese,
|
||||
Armenian,
|
||||
Assamese,
|
||||
Avaric,
|
||||
Avestan,
|
||||
Aymara,
|
||||
Azerbaijani,
|
||||
Bambara,
|
||||
Bashkir,
|
||||
Basque,
|
||||
Belarusian,
|
||||
Bengali,
|
||||
Bislama,
|
||||
Bosnian,
|
||||
BrazilianPortugese,
|
||||
English,
|
||||
Japanese,
|
||||
JapaneseRomaji,
|
||||
Italian,
|
||||
Russian,
|
||||
Indonesian,
|
||||
Breton,
|
||||
Bulgarian,
|
||||
Hebrew,
|
||||
Spanish,
|
||||
Esperanto,
|
||||
Polish,
|
||||
Burmese,
|
||||
Castilian,
|
||||
CastilianSpanish,
|
||||
Catalan,
|
||||
Central,
|
||||
Chamorro,
|
||||
Chechen,
|
||||
Chewa,
|
||||
Chichewa,
|
||||
SimplifiedChinese,
|
||||
TraditionalChinese,
|
||||
Chuang,
|
||||
ChurchSlavic,
|
||||
ChurchSlavonic,
|
||||
Chuvash,
|
||||
Cornish,
|
||||
Corsican,
|
||||
Cree,
|
||||
Croatian,
|
||||
Czech,
|
||||
Danish,
|
||||
Dhivehi,
|
||||
Divehi,
|
||||
Dutch,
|
||||
Dzongkha,
|
||||
English,
|
||||
Esperanto,
|
||||
Estonian,
|
||||
Ewe,
|
||||
Faroese,
|
||||
Fijian,
|
||||
Finnish,
|
||||
Flemish,
|
||||
French,
|
||||
Fulah,
|
||||
Gaelic,
|
||||
Galician,
|
||||
Ganda,
|
||||
Georgian,
|
||||
German,
|
||||
Gikuyu,
|
||||
Greek,
|
||||
Greenlandic,
|
||||
Guarani,
|
||||
Gujarati,
|
||||
Gwic,
|
||||
Haitian,
|
||||
Hausa,
|
||||
Hebrew,
|
||||
Herero,
|
||||
Hindi,
|
||||
Hiri,
|
||||
Hungarian,
|
||||
Icelandic,
|
||||
Ido,
|
||||
Igbo,
|
||||
Indonesian,
|
||||
Interlingua,
|
||||
Interlingue,
|
||||
Inuktitut,
|
||||
Inupiaq,
|
||||
Irish,
|
||||
Italian,
|
||||
Japanese,
|
||||
Javanese,
|
||||
Kalaallisut,
|
||||
Kannada,
|
||||
Kanuri,
|
||||
Kashmiri,
|
||||
Kazakh,
|
||||
Kikuyu,
|
||||
Kinyarwanda,
|
||||
Kirghiz,
|
||||
Komi,
|
||||
Kongo,
|
||||
Korean,
|
||||
Kuanyama,
|
||||
Kurdish,
|
||||
Kwanyama,
|
||||
Kyrgyz,
|
||||
Lao,
|
||||
Latin,
|
||||
LatinAmericanSpanish,
|
||||
Latvian,
|
||||
Letzeburgesch,
|
||||
Limburgan,
|
||||
Limburger,
|
||||
Limburgish,
|
||||
Lingala,
|
||||
Lithuanian,
|
||||
LubaKatanga,
|
||||
Luxembourgish,
|
||||
Macedonian,
|
||||
Malagasy,
|
||||
Malay,
|
||||
Malayalam,
|
||||
Maldivian,
|
||||
Maltese,
|
||||
Manx,
|
||||
Maori,
|
||||
Marathi,
|
||||
Marshallese,
|
||||
MiMoldavian,
|
||||
Moldovan,
|
||||
Mongolian,
|
||||
NNauru,
|
||||
Navaho,
|
||||
Navajo,
|
||||
NdebeleNorth,
|
||||
NdebeleSouth,
|
||||
Ndonga,
|
||||
Nepali,
|
||||
North,
|
||||
Northern,
|
||||
Norwegian,
|
||||
NorwegianBokmål,
|
||||
NorwegianNynorsk,
|
||||
Nuosu,
|
||||
Nyanja,
|
||||
Nynors,
|
||||
Occidental,
|
||||
Occitan,
|
||||
Ojibwa,
|
||||
OldBulgarian,
|
||||
OldChurchSlavonic,
|
||||
OldSlavonic,
|
||||
Oriya,
|
||||
Oromo,
|
||||
Ossetian,
|
||||
Ossetic,
|
||||
Pali,
|
||||
Panjabi,
|
||||
Pashto,
|
||||
Persian,
|
||||
Polish,
|
||||
Portuguese,
|
||||
ProvençPunjabi,
|
||||
Pushto,
|
||||
Quechua,
|
||||
Romanian,
|
||||
RomanizedJapanese,
|
||||
RomanizedKorean,
|
||||
RomanizedChinese,
|
||||
Romansh,
|
||||
Rundi,
|
||||
Russian,
|
||||
Samoan,
|
||||
Sango,
|
||||
Sanskrit,
|
||||
Sardinian,
|
||||
Scottish,
|
||||
Serbian,
|
||||
Shona,
|
||||
Sichuan,
|
||||
Sindhi,
|
||||
Sinhala,
|
||||
Sinhalese,
|
||||
Slovak,
|
||||
Slovenian,
|
||||
Somali,
|
||||
Sotho,
|
||||
SouthNdebele,
|
||||
Spanish,
|
||||
Sundanese,
|
||||
Swahili,
|
||||
Swati,
|
||||
Swedish,
|
||||
Tagalog,
|
||||
Tahitian,
|
||||
Tajik,
|
||||
Tamil,
|
||||
Tatar,
|
||||
Telugu,
|
||||
Thai,
|
||||
Tibetan,
|
||||
Tigrinya,
|
||||
Tonga,
|
||||
Tsonga,
|
||||
Tswana,
|
||||
Turkish,
|
||||
Turkmen,
|
||||
Twi,
|
||||
Uighur,
|
||||
Ukrainian,
|
||||
Urdu,
|
||||
Uyghur,
|
||||
Uzbek,
|
||||
Valencian,
|
||||
Venda,
|
||||
Vietnamese,
|
||||
Volapük,
|
||||
Walloon,
|
||||
Welsh,
|
||||
Western,
|
||||
Wolof,
|
||||
Xhosa,
|
||||
Yiddish,
|
||||
Yoruba,
|
||||
Zhuang,
|
||||
Zulu,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -76,6 +283,7 @@ pub enum State {
|
||||
#[derive(Debug)]
|
||||
pub enum Response {
|
||||
Collection,
|
||||
Entity,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -99,6 +307,13 @@ pub struct SearchResult {
|
||||
pub data: Vec<Manga>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdQueryResult {
|
||||
pub result: ResponseResult,
|
||||
pub response: Response,
|
||||
pub data: Manga,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ChapterImagesContent {
|
||||
@@ -216,6 +431,7 @@ pub struct Chapter {
|
||||
pub relationships: Vec<ChapterRelationShip>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChapterAttributes {
|
||||
pub volume: Option<u32>,
|
||||
pub chapter: Option<f32>,
|
||||
@@ -247,6 +463,13 @@ struct SearchResponse {
|
||||
total: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct IdQueryResponse {
|
||||
result: String,
|
||||
response: String,
|
||||
data: ContentData,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ContentData {
|
||||
id: String,
|
||||
@@ -430,34 +653,232 @@ impl TryInto<Language> for &str {
|
||||
|
||||
fn try_into(self) -> Result<Language, ()> {
|
||||
Ok(match self {
|
||||
"ja" => Language::Japanese,
|
||||
"ja-ro" => Language::JapaneseRomaji,
|
||||
"en" => Language::English,
|
||||
"ru" => Language::Russian,
|
||||
"pt-br" => Language::BrazilianPortugese,
|
||||
"tr" => Language::Turkish,
|
||||
"it" => Language::Italian,
|
||||
"es-la" => Language::SpanishLatinAmerican,
|
||||
"hu" => Language::Hungarian,
|
||||
"bg" => Language::Bulgarian,
|
||||
"id" => Language::Indonesian,
|
||||
"he" => Language::Hebrew,
|
||||
"es" => Language::Spanish,
|
||||
"eo" => Language::Esperanto,
|
||||
"pl" => Language::Polish,
|
||||
"ko" => Language::Korean,
|
||||
"fr" => Language::French,
|
||||
"vi" => Language::Vietnamese,
|
||||
"de" => Language::German,
|
||||
"ab" => Language::Abkhazian,
|
||||
"aa" => Language::Afar,
|
||||
"af" => Language::Afrikaans,
|
||||
"ak" => Language::Akan,
|
||||
"sq" => Language::Albanian,
|
||||
"am" => Language::Amharic,
|
||||
"ar" => Language::Arabic,
|
||||
"ro" => Language::Romanian,
|
||||
"uk" => Language::Ukranian,
|
||||
"zh-hk" => Language::TraditionalChinese,
|
||||
"fa" => Language::Persian,
|
||||
"el" => Language::Greek,
|
||||
"an" => Language::Aragonese,
|
||||
"hy" => Language::Armenian,
|
||||
"as" => Language::Assamese,
|
||||
"av" => Language::Avaric,
|
||||
"ae" => Language::Avestan,
|
||||
"ay" => Language::Aymara,
|
||||
"az" => Language::Azerbaijani,
|
||||
"bm" => Language::Bambara,
|
||||
"ba" => Language::Bashkir,
|
||||
"eu" => Language::Basque,
|
||||
"be" => Language::Belarusian,
|
||||
"bn" => Language::Bengali,
|
||||
"bi" => Language::Bislama,
|
||||
"nb" => Language::NorwegianBokmål,
|
||||
"bs" => Language::Bosnian,
|
||||
"br" => Language::Breton,
|
||||
"bg" => Language::Bulgarian,
|
||||
"my" => Language::Burmese,
|
||||
"es" => Language::Castilian,
|
||||
"ca" => Language::Catalan,
|
||||
"km" => Language::Central,
|
||||
"ch" => Language::Chamorro,
|
||||
"ce" => Language::Chechen,
|
||||
"ny" => Language::Chewa,
|
||||
"ny" => Language::Chichewa,
|
||||
"zh" => Language::SimplifiedChinese,
|
||||
"tl" => Language::Tagalog,
|
||||
"za" => Language::Chuang,
|
||||
"cu" => Language::ChurchSlavic,
|
||||
"cu" => Language::ChurchSlavonic,
|
||||
"cv" => Language::Chuvash,
|
||||
"kw" => Language::Cornish,
|
||||
"co" => Language::Corsican,
|
||||
"cr" => Language::Cree,
|
||||
"hr" => Language::Croatian,
|
||||
"cs" => Language::Czech,
|
||||
"da" => Language::Danish,
|
||||
"dv" => Language::Dhivehi,
|
||||
"dv" => Language::Divehi,
|
||||
"nl" => Language::Dutch,
|
||||
"dz" => Language::Dzongkha,
|
||||
"en" => Language::English,
|
||||
"eo" => Language::Esperanto,
|
||||
"et" => Language::Estonian,
|
||||
"ee" => Language::Ewe,
|
||||
"fo" => Language::Faroese,
|
||||
"fj" => Language::Fijian,
|
||||
"fi" => Language::Finnish,
|
||||
"nl" => Language::Flemish,
|
||||
"fr" => Language::French,
|
||||
"ff" => Language::Fulah,
|
||||
"gd" => Language::Gaelic,
|
||||
"gl" => Language::Galician,
|
||||
"lg" => Language::Ganda,
|
||||
"ka" => Language::Georgian,
|
||||
"de" => Language::German,
|
||||
"ki" => Language::Gikuyu,
|
||||
"el" => Language::Greek,
|
||||
"kl" => Language::Greenlandic,
|
||||
"gn" => Language::Guarani,
|
||||
"gu" => Language::Gujarati,
|
||||
"ht" => Language::Haitian,
|
||||
"ha" => Language::Hausa,
|
||||
"he" => Language::Hebrew,
|
||||
"hz" => Language::Herero,
|
||||
"hi" => Language::Hindi,
|
||||
"ho" => Language::Hiri,
|
||||
"hu" => Language::Hungarian,
|
||||
"is" => Language::Icelandic,
|
||||
"io" => Language::Ido,
|
||||
"ig" => Language::Igbo,
|
||||
"id" => Language::Indonesian,
|
||||
"ia" => Language::Interlingua,
|
||||
"ie" => Language::Interlingue,
|
||||
"iu" => Language::Inuktitut,
|
||||
"ik" => Language::Inupiaq,
|
||||
"ga" => Language::Irish,
|
||||
"it" => Language::Italian,
|
||||
"ja" => Language::Japanese,
|
||||
"jv" => Language::Javanese,
|
||||
"kl" => Language::Kalaallisut,
|
||||
"kn" => Language::Kannada,
|
||||
"kr" => Language::Kanuri,
|
||||
"ks" => Language::Kashmiri,
|
||||
"kk" => Language::Kazakh,
|
||||
"ki" => Language::Kikuyu,
|
||||
"rw" => Language::Kinyarwanda,
|
||||
"ky" => Language::Kirghiz,
|
||||
"kv" => Language::Komi,
|
||||
"kg" => Language::Kongo,
|
||||
"ko" => Language::Korean,
|
||||
"kj" => Language::Kuanyama,
|
||||
"ku" => Language::Kurdish,
|
||||
"kj" => Language::Kwanyama,
|
||||
"ky" => Language::Kyrgyz,
|
||||
"lo" => Language::Lao,
|
||||
"la" => Language::Latin,
|
||||
"lv" => Language::Latvian,
|
||||
"lb" => Language::Letzeburgesch,
|
||||
"li" => Language::Limburgan,
|
||||
"li" => Language::Limburger,
|
||||
"li" => Language::Limburgish,
|
||||
"ln" => Language::Lingala,
|
||||
"lt" => Language::Lithuanian,
|
||||
"lu" => Language::LubaKatanga,
|
||||
"lb" => Language::Luxembourgish,
|
||||
"mk" => Language::Macedonian,
|
||||
"mg" => Language::Malagasy,
|
||||
"ms" => Language::Malay,
|
||||
"ml" => Language::Malayalam,
|
||||
"dv" => Language::Maldivian,
|
||||
"mt" => Language::Maltese,
|
||||
"gv" => Language::Manx,
|
||||
"mi" => Language::Maori,
|
||||
"mr" => Language::Marathi,
|
||||
"mh" => Language::Marshallese,
|
||||
"ro" => Language::MiMoldavian,
|
||||
"ro" => Language::Moldovan,
|
||||
"mn" => Language::Mongolian,
|
||||
"na" => Language::NNauru,
|
||||
"nv" => Language::Navaho,
|
||||
"nv" => Language::Navajo,
|
||||
"nd" => Language::NdebeleNorth,
|
||||
"nr" => Language::NdebeleSouth,
|
||||
"ng" => Language::Ndonga,
|
||||
"ne" => Language::Nepali,
|
||||
"nd" => Language::North,
|
||||
"se" => Language::Northern,
|
||||
"no" => Language::Norwegian,
|
||||
"nb" => Language::NorwegianBokmål,
|
||||
"nn" => Language::NorwegianNynorsk,
|
||||
"ii" => Language::Nuosu,
|
||||
"ny" => Language::Nyanja,
|
||||
"nn" => Language::NorwegianNynorsk,
|
||||
"ie" => Language::Occidental,
|
||||
"oc" => Language::Occitan,
|
||||
"oj" => Language::Ojibwa,
|
||||
"cu" => Language::OldBulgarian,
|
||||
"cu" => Language::OldChurchSlavonic,
|
||||
"cu" => Language::OldSlavonic,
|
||||
"or" => Language::Oriya,
|
||||
"om" => Language::Oromo,
|
||||
"os" => Language::Ossetian,
|
||||
"os" => Language::Ossetic,
|
||||
"pi" => Language::Pali,
|
||||
"pa" => Language::Panjabi,
|
||||
"ps" => Language::Pashto,
|
||||
"fa" => Language::Persian,
|
||||
"pl" => Language::Polish,
|
||||
"pt" => Language::Portuguese,
|
||||
"pa" => Language::ProvençPunjabi,
|
||||
"ps" => Language::Pushto,
|
||||
"qu" => Language::Quechua,
|
||||
"ro" => Language::Romanian,
|
||||
"rm" => Language::Romansh,
|
||||
"rn" => Language::Rundi,
|
||||
"ru" => Language::Russian,
|
||||
"sm" => Language::Samoan,
|
||||
"sg" => Language::Sango,
|
||||
"sa" => Language::Sanskrit,
|
||||
"sc" => Language::Sardinian,
|
||||
"gd" => Language::Scottish,
|
||||
"sr" => Language::Serbian,
|
||||
"sn" => Language::Shona,
|
||||
"ii" => Language::Sichuan,
|
||||
"sd" => Language::Sindhi,
|
||||
"si" => Language::Sinhala,
|
||||
"si" => Language::Sinhalese,
|
||||
"sk" => Language::Slovak,
|
||||
"sl" => Language::Slovenian,
|
||||
"so" => Language::Somali,
|
||||
"st" => Language::Sotho,
|
||||
"es" => Language::Spanish,
|
||||
"su" => Language::Sundanese,
|
||||
"sw" => Language::Swahili,
|
||||
"ss" => Language::Swati,
|
||||
"sv" => Language::Swedish,
|
||||
"tl" => Language::Tagalog,
|
||||
"ty" => Language::Tahitian,
|
||||
"tg" => Language::Tajik,
|
||||
"ta" => Language::Tamil,
|
||||
"tt" => Language::Tatar,
|
||||
"te" => Language::Telugu,
|
||||
"th" => Language::Thai,
|
||||
"bo" => Language::Tibetan,
|
||||
"ti" => Language::Tigrinya,
|
||||
"to" => Language::Tonga,
|
||||
"ts" => Language::Tsonga,
|
||||
"tn" => Language::Tswana,
|
||||
"tr" => Language::Turkish,
|
||||
"tk" => Language::Turkmen,
|
||||
"tw" => Language::Twi,
|
||||
"ug" => Language::Uighur,
|
||||
"uk" => Language::Ukrainian,
|
||||
"ur" => Language::Urdu,
|
||||
"ug" => Language::Uyghur,
|
||||
"uz" => Language::Uzbek,
|
||||
"ca" => Language::Valencian,
|
||||
"ve" => Language::Venda,
|
||||
"vi" => Language::Vietnamese,
|
||||
"vo" => Language::Volapük,
|
||||
"wa" => Language::Walloon,
|
||||
"cy" => Language::Welsh,
|
||||
"fy" => Language::Western,
|
||||
"wo" => Language::Wolof,
|
||||
"xh" => Language::Xhosa,
|
||||
"yi" => Language::Yiddish,
|
||||
"yo" => Language::Yoruba,
|
||||
"za" => Language::Zhuang,
|
||||
"zu" => Language::Zulu,
|
||||
|
||||
"zh-ro" => Language::RomanizedChinese,
|
||||
"zh" => Language::SimplifiedChinese,
|
||||
"zh-hk" => Language::TraditionalChinese,
|
||||
"pt" => Language::Portuguese,
|
||||
"es" => Language::CastilianSpanish,
|
||||
"es-la" => Language::LatinAmericanSpanish,
|
||||
"ja-ro" => Language::RomanizedJapanese,
|
||||
"pt-br" => Language::BrazilianPortugese,
|
||||
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
@@ -523,6 +944,7 @@ impl TryInto<Response> for &str {
|
||||
fn try_into(self) -> Result<Response, ()> {
|
||||
match self {
|
||||
"collection" => Ok(Response::Collection),
|
||||
"entity" => Ok(Response::Entity),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
@@ -781,6 +1203,29 @@ fn convert_attributes(
|
||||
})
|
||||
}
|
||||
|
||||
// *****
|
||||
pub fn deserialize_id_query(json: &str) -> IdQueryResult {
|
||||
let id_query_response: IdQueryResponse = match serde_json::from_str(json) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
std::fs::write("out.json", json).unwrap();
|
||||
eprintln!("ERROR: {:#?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
convert_some_test(id_query_response).unwrap()
|
||||
}
|
||||
|
||||
fn convert_some_test(input: IdQueryResponse) -> Result<IdQueryResult, AttributeConversionError> {
|
||||
Ok(IdQueryResult {
|
||||
result: input.result.as_str().try_into().unwrap(),
|
||||
response: input.response.as_str().try_into().unwrap(),
|
||||
data: convert_data_to_manga(input.data).unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
// *****
|
||||
|
||||
pub fn deserialize_chapter_feed(json: &str) -> ChapterFeed {
|
||||
let chapter_feed_response: ChapterFeedResponse = match serde_json::from_str(json) {
|
||||
Ok(v) => v,
|
||||
@@ -957,3 +1402,61 @@ pub fn deserialize_chapter_images(json: &str) -> Result<ChapterImages, ChapterIm
|
||||
};
|
||||
Ok(convert_chapter_images(chapter_images).unwrap())
|
||||
}
|
||||
|
||||
fn convert_data_to_manga(m: ContentData) -> Result<Manga, ResponseConversionError> {
|
||||
Ok(Manga {
|
||||
id: Id(m.id.clone()),
|
||||
data_type: (m.type_name.as_str()).try_into().map_err(|_| {
|
||||
ResponseConversionError::AttributeError(AttributeConversionError::DataType(
|
||||
m.type_name.clone(),
|
||||
))
|
||||
})?,
|
||||
attributes: convert_attributes(&m.attributes)
|
||||
.map_err(ResponseConversionError::AttributeError)?,
|
||||
relationships: m
|
||||
.relationships
|
||||
.iter()
|
||||
.map(|m| {
|
||||
Ok(RelationShip {
|
||||
id: Id(m.id.clone()),
|
||||
data_type: (m.type_name.as_str())
|
||||
.try_into()
|
||||
.map_err(|_| AttributeConversionError::DataType(m.type_name.clone()))?,
|
||||
attributes: {
|
||||
if let Some(attributes) = &m.attributes {
|
||||
Some(CoverAttributes {
|
||||
created_at: DateTime::parse_from_rfc3339(&attributes.created_at)
|
||||
.map_err(|_| {
|
||||
AttributeConversionError::CreatedAtDateTime(
|
||||
attributes.created_at.clone(),
|
||||
)
|
||||
})?,
|
||||
updated_at: DateTime::parse_from_rfc3339(&attributes.created_at)
|
||||
.map_err(|_| {
|
||||
AttributeConversionError::CreatedAtDateTime(
|
||||
attributes.created_at.clone(),
|
||||
)
|
||||
})?,
|
||||
// TODO: Something should probably be done here
|
||||
description: String::new(),
|
||||
file_name: Id(attributes.file_name.clone()),
|
||||
locale: (attributes.locale.as_str()).try_into().map_err(|_| {
|
||||
AttributeConversionError::Locale(attributes.locale.clone())
|
||||
})?,
|
||||
version: attributes.version,
|
||||
volume: match &attributes.volume {
|
||||
Some(v) => v.parse().ok(),
|
||||
None => None,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
related: m.related.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<RelationShip>, AttributeConversionError>>()
|
||||
.map_err(ResponseConversionError::AttributeError)?,
|
||||
})
|
||||
}
|
||||
|
||||
33
src/util.rs
33
src/util.rs
@@ -320,11 +320,28 @@ pub fn args() -> Config {
|
||||
let mut config = Config::new();
|
||||
while args.len() != 0 {
|
||||
match args.next().unwrap().as_ref() {
|
||||
"--selection-range" => {
|
||||
if config.selection_range.is_some() {
|
||||
eprintln!("Value selection range already set.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
config.selection_range = match args.next() {
|
||||
Some(selection_type) => Some(selection_type),
|
||||
None => {
|
||||
eprintln!("Missing value for selection range, type: String");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
"-t" | "--selection-type" => {
|
||||
if config.selection_type.is_some() {
|
||||
eprintln!("Value selection type already set.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
config.selection_type = match args.next() {
|
||||
Some(selection_type) => Some(match selection_type.as_str() {
|
||||
"volume" => ConfigSelectionType::Volume,
|
||||
"chapter" => ConfigSelectionType::Chapter,
|
||||
"v" | "volume" => ConfigSelectionType::Volume,
|
||||
"c" | "chapter" => ConfigSelectionType::Chapter,
|
||||
_ => {
|
||||
eprintln!("Invalid value for selection type, type: SelectionType");
|
||||
std::process::exit(1);
|
||||
@@ -337,6 +354,10 @@ pub fn args() -> Config {
|
||||
};
|
||||
}
|
||||
"-i" | "--id" => {
|
||||
if config.search.is_some() {
|
||||
eprintln!("Conflicting arguments for search.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Some(id) = args.next() {
|
||||
config.search = Some(ConfigSearch::Id(Id(id)));
|
||||
} else {
|
||||
@@ -345,6 +366,10 @@ pub fn args() -> Config {
|
||||
}
|
||||
}
|
||||
"-s" | "--search" => {
|
||||
if config.search.is_some() {
|
||||
eprintln!("Conflicting arguments for search.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Some(query) = args.next() {
|
||||
config.search = Some(ConfigSearch::Query(query));
|
||||
} else {
|
||||
@@ -353,6 +378,10 @@ pub fn args() -> Config {
|
||||
}
|
||||
}
|
||||
"-b" | "--bonus" => {
|
||||
if config.bonus.is_some() {
|
||||
eprintln!("Value bonus already set.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
config.bonus = match args.next() {
|
||||
Some(a) => Some(match a.as_str() {
|
||||
"true" => true,
|
||||
|
||||
Reference in New Issue
Block a user