use clap::{Parser, ValueEnum};
use gbscraper::*;
use std::collections::HashSet;
use crate::scraper::FALLBACK_TLD;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(value_name = "URL")] url: String,
#[arg(
short = 'o',
long = "target-dir",
value_name = "DIRECTORY",
default_value = "."
)]
target_dir: String,
#[arg(short, long = "keep-images", default_value_t = false)]
keep_images: bool,
#[arg(value_enum, short, long, value_delimiter = ',', num_args = 1.., default_value ="pdf")]
format: Option<Vec<Format>>,
#[arg(value_enum, short = 'm', long = "download-mode", value_name = "MODE", default_value_t = DownloadMode::Single)]
download_mode: DownloadMode,
#[arg(short, long)]
archive: Option<String>,
#[arg(short, long, short = 'r', default_value_t = 3)]
download_attempts: u32,
#[arg(short, long, default_value_t = false)]
verbose: bool,
#[arg(short, long)]
tld_override: Option<String>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Format {
None,
Pdf,
Cbz,
All,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum DownloadMode {
Single,
Period,
Full,
}
impl Args {
fn to_options(&self) -> std::io::Result<scraper::ScraperOptions> {
Ok(scraper::ScraperOptions {
keep_images: self.keep_images,
formats: {
match self.format.as_ref() {
None => scraper::FormatFlags::Pdf,
Some(v) => {
let mut flags = scraper::FormatFlags::None;
for f in v {
flags |= match f {
Format::None => scraper::FormatFlags::None,
Format::Pdf => scraper::FormatFlags::Pdf,
Format::Cbz => scraper::FormatFlags::Cbz,
Format::All => scraper::FormatFlags::All,
}
}
flags
}
}
},
archive_file: self.archive.clone(),
skip_download: false,
download_attempts: self.download_attempts,
verbose: self.verbose,
tld: match &self.tld_override {
None => FALLBACK_TLD.to_string(),
Some(tld) => match tld.to_lowercase().as_str() {
"none" => match tldextract::TldExtractor::new(tldextract::TldOption::default())
.extract(&self.url)
{
Ok(x) => match x.suffix {
Some(x) => format!(".{x}"),
None => FALLBACK_TLD.to_string(),
},
Err(_) => FALLBACK_TLD.to_string(),
},
_ => tld.to_string(),
},
},
})
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let options = match args.to_options() {
Ok(opts) => opts,
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
};
let mut already_downloaded = HashSet::<String>::new();
if let Some(file) = args.archive.as_ref() {
if std::fs::exists(file)? {
for line in std::fs::read_to_string(file)?.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() {
already_downloaded.insert(trimmed.to_string());
}
}
}
}
let result = match args.download_mode {
DownloadMode::Single => scraper::download_issue_skip_downloaded(
&args.url,
&args.target_dir,
&options,
Some(&mut already_downloaded),
)
.map(|_| ()),
DownloadMode::Period => scraper::download_period(
&args.url,
&args.target_dir,
&options,
&mut already_downloaded,
),
DownloadMode::Full => scraper::download_all(
&args.url,
&args.target_dir,
&options,
&mut already_downloaded,
),
};
if let Err(x) = result {
eprintln!("Scraper error: {}", x);
}
Ok(())
}