main.rs (3777B)
1 use odysee_dl::config::Config; 2 use odysee_dl::event::Event; 3 use odysee_dl::run; 4 5 static HELP: &str = "Usage: odysee-dl-cli [OPTIONS] <URL> 6 7 Download all content from an Odysee channel. <URL> should be the URL of the channel, 8 e.g. https://odysee.com/@channel:1 9 10 Options: 11 -h, --help Print help information 12 -d, --dir <DIR> Output directory (default: .) 13 -r, --resume Resume an interrupted download 14 "; 15 16 fn main() { 17 let config = parse_args().unwrap_or_else(|e| handle_error(e)); 18 let (tx, rx) = std::sync::mpsc::channel(); 19 let handle = std::thread::spawn(move || { 20 run(config, tx).unwrap_or_else(|e| handle_error(e)); 21 }); 22 23 for event in rx { 24 render_event(&event); 25 } 26 27 handle.join().map_err(|e| { 28 eprintln!("ERROR: {:?}", e); 29 std::process::exit(1); 30 }).ok(); 31 } 32 33 enum ParseArgsErr { 34 MissingUrl, 35 MissingDirValue, 36 UnknownOption(String), 37 } 38 39 impl std::fmt::Display for ParseArgsErr { 40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 match self { 42 ParseArgsErr::MissingUrl => write!(f, "Missing URL"), 43 ParseArgsErr::MissingDirValue => write!(f, "Missing value for --dir"), 44 ParseArgsErr::UnknownOption(option) => write!(f, "Unknown option: {}", option), 45 } 46 } 47 } 48 49 fn handle_error(e: impl std::fmt::Display) -> ! { 50 eprintln!("Error: {}", e); 51 std::process::exit(1); 52 } 53 54 fn parse_args() -> Result<Config, ParseArgsErr> { 55 let args: Vec<String> = std::env::args().collect(); 56 if args.len() < 2 { 57 return Err(ParseArgsErr::MissingUrl); 58 } 59 60 let mut output_dir = std::path::PathBuf::from("."); 61 let mut resume = false; 62 let mut url: Option<String> = None; 63 let mut i = 1; 64 65 while i < args.len() { 66 match args[i].as_str() { 67 "-h" | "--help" => { 68 println!("{}", HELP); 69 std::process::exit(0); 70 } 71 "-d" | "--dir" => { 72 i += 1; 73 if i >= args.len() { 74 return Err(ParseArgsErr::MissingDirValue); 75 } 76 output_dir = std::path::PathBuf::from(&args[i]); 77 } 78 "-r" | "--resume" => { 79 resume = true; 80 } 81 arg if !arg.starts_with('-') => { 82 url = Some(arg.to_string()); 83 } 84 _ => return Err(ParseArgsErr::UnknownOption(args[i].clone())), 85 } 86 i += 1; 87 } 88 89 let url = url.ok_or(ParseArgsErr::MissingUrl)?; 90 Ok(Config { url, output_dir, resume }) 91 } 92 93 fn render_event(event: &Event) { 94 use std::io::Write; 95 match event { 96 Event::GetChannelStarted(url) => { 97 print!("Fetching channel: {}...", url); 98 std::io::stdout().flush().ok(); 99 } 100 Event::GetChannelFailed(url, err) => eprintln!("\nFailed to fetch channel {}: {}", url, err), 101 Event::GetChannelFinished(_) => println!(" Done"), 102 Event::GetPostsStarted(url) => { 103 print!("Fetching posts from: {}...", url); 104 std::io::stdout().flush().ok(); 105 } 106 Event::GetPostsFailed(url, err) => eprintln!("\nFailed to fetch posts from {}: {}", url, err), 107 Event::GetPostsFinished(_) => println!(" Done"), 108 Event::DownloadPostStarted(name) => { 109 print!("Downloading: {}...", name); 110 std::io::stdout().flush().ok(); 111 } 112 Event::DownloadPostFailed(_, err) => println!(" Failed: {}", err), 113 Event::DownloadPostSkipped(_) => println!(" Skipped"), 114 Event::DownloadPostFinished(_) => println!(" Done"), 115 Event::RateLimited(ctx) => println!("\nRate limited ({}), waiting 60s...", ctx), 116 Event::Done => println!("Done."), 117 } 118 }