use anyhow::{Context, Result, bail};
use clap::{Args, Parser, Subcommand};
use url::Url;
#[derive(Subcommand, Debug)]
pub enum Command {
#[command(name = "announce")]
Announce(Announce),
#[command(name = "scrape")]
Scrape(Scrape),
}
#[derive(Parser, Debug)]
#[command(name = "btpeer", version, about = "Simple CLI tool and library to get peers from TCP/HTTP and UDP BitTorrent trackers", long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Args, Debug)]
#[command(version, about, long_about = None)]
pub struct Announce {
#[arg(short, long)]
pub tracker: Vec<Url>,
#[arg(short = 'T', long, default_value_t = 15)]
pub timeout: u64,
#[arg(short, long, value_parser = info_hash_v1)]
pub info_hash: [u8; 20],
#[arg(short, long, default_value_t = 6881)]
pub port: u16,
#[arg(short = 'B', long, default_value_t = 100)]
pub peers_buffer_capacity: usize,
#[arg(short = 'P', long)]
pub proxy_url: Option<String>,
}
#[derive(Args, Debug)]
#[command(version, about, long_about = None)]
pub struct Scrape {
#[arg(short, long)]
pub tracker: Vec<Url>,
#[arg(short = 'T', long, default_value_t = 15)]
pub timeout: u64,
#[arg(short, long, value_parser = info_hash_v1)]
pub info_hash: Vec<[u8; 20]>,
#[arg(short, long, default_value_t = 6881)]
pub port: u16,
#[arg(short = 'B', long, default_value_t = 100)]
pub info_hash_buffer_capacity: usize,
#[arg(short = 'P', long)]
pub proxy_url: Option<String>,
}
fn info_hash_v1(s: &str) -> Result<[u8; 20]> {
if s.len() != 40 {
bail!("Info-hash v1 must be exactly 40 hex characters long");
}
let mut bytes = [0u8; 20];
for (i, b) in bytes.iter_mut().enumerate() {
let start = i * 2;
*b = u8::from_str_radix(&s[start..start + 2], 16)
.with_context(|| format!("Invalid hex character at index {start}"))?;
}
Ok(bytes)
}