replace zip command with zip lib, do languages properly i suppose, and other stuff i do not remember

This commit is contained in:
2025-06-03 00:25:00 +02:00
parent af002b0149
commit 6d4ffa209a
4 changed files with 824 additions and 179 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ Cargo.lock
/cba
*.jpg
*.png
*.cbz

View File

@@ -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 {
match query {
util::ConfigSearch::Query(query) => search(client, &query, &filters, limit).await,
util::ConfigSearch::Id(_) => todo!(),
}
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 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",
let results = if let Some(ref query) = config.search {
match query {
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
};
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,33 +69,72 @@ async fn main() {
}
};
let selection_type = if let Some(selection_type) = config.selection_type {
match selection_type {
util::ConfigSelectionType::Volume => loop {
let input = util::get_input("Choose volumes: ").replace(" ", "");
if let Some(selection) = util::choose_volumes(input.as_str()) {
break util::SelectionType::Volume(selection);
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 => loop {
let input = util::get_input("Choose chapters: ").replace(" ", "");
if let Some(selection) = util::choose_chapters(input.as_str()) {
break util::SelectionType::Chapter(selection);
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(" ", "");
if let Some(selection) = util::choose_volumes(input.as_str()) {
break util::SelectionType::Volume(selection);
}
},
util::ConfigSelectionType::Chapter => loop {
let input = util::get_input("Choose chapters: ").replace(" ", "");
if let Some(selection) = util::choose_chapters(input.as_str()) {
break util::SelectionType::Chapter(selection);
}
},
}
}
} else {
'outer: loop {
match util::get_input("Select by volume or chapter? [v/c] : ").as_str() {
"v" | "volume" => 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);
"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" => {
let input = util::get_input("Choose chapters: ").replace(" ", "");
if let Some(selection) = util::choose_chapters(input.as_str()) {
break 'outer util::SelectionType::Chapter(selection);
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);
}
}
}
}
_ => {
@@ -146,7 +143,7 @@ async fn main() {
}
}
};
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 path = if let Some(v) = selected_chapters[i].attributes.volume {
format!(
"images/{}/volume_{:0>3}/chapter{:0>3}_image_{:0>3}.png",
results.data[choice as usize].attributes.title.en, v, chapter_n, j
)
let chapter_n = selected_chapters[i].attributes.chapter;
let chapter_text = if let Some(n) = chapter_n {
n.to_string()
} else {
format!(
"images/{}/chapter{:0>3}_image_{:0>3}.png",
results.data[choice as usize].attributes.title.en, chapter_n, j
)
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",
title.en, v, chapter_text, j
)
} else {
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()
.unwrap();
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)
}

View File

@@ -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)?,
})
}

View File

@@ -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,