1use clap::Subcommand;
2
3use crate::api::HtbClient;
4use crate::output::{self, OutputFormat};
5
6#[derive(Subcommand)]
7pub enum SherlockCommand {
8 List {
10 #[arg(long, help = "Filter by category")]
11 category: Option<String>,
12 #[arg(long, help = "Page number")]
13 page: Option<u32>,
14 },
15 Categories,
17 Info {
19 slug: String,
21 },
22 Download {
24 slug: String,
26 },
27 Submit {
29 id: u64,
31 task_id: u64,
33 flag: String,
35 },
36}
37
38pub async fn handle(
39 client: &HtbClient,
40 cmd: SherlockCommand,
41 format: OutputFormat,
42) -> anyhow::Result<()> {
43 match cmd {
44 SherlockCommand::List { category, page } => {
45 let result = client.sherlocks().list(page.unwrap_or(1), 100).await?;
46 let mut sherlocks = result.data;
47
48 if let Some(ref cat_filter) = category {
49 sherlocks.retain(|s| {
50 s.category_name
51 .as_ref()
52 .is_some_and(|cn| cn.eq_ignore_ascii_case(cat_filter))
53 });
54 }
55
56 output::print_list(&sherlocks, format);
57 output::print_pagination(
58 result.meta.current_page,
59 result.meta.last_page,
60 result.meta.total,
61 format,
62 );
63 }
64
65 SherlockCommand::Categories => {
66 let categories = client.sherlocks().categories().await?;
67 output::print_list(&categories, format);
68 }
69
70 SherlockCommand::Info { slug } => {
71 let sherlock = client.sherlocks().info(&slug).await?;
72 let fields = vec![
73 ("ID", sherlock.id.to_string()),
74 ("Name", sherlock.name.clone()),
75 (
76 "Difficulty",
77 sherlock.difficulty.clone().unwrap_or_default(),
78 ),
79 (
80 "Category",
81 sherlock.category_name.clone().unwrap_or_default(),
82 ),
83 ("Solves", sherlock.solves.to_string()),
84 (
85 "Rating",
86 sherlock
87 .rating
88 .map(|r| format!("{r:.1}"))
89 .unwrap_or_default(),
90 ),
91 (
92 "Progress",
93 sherlock
94 .progress
95 .map(|p| format!("{p}%"))
96 .unwrap_or_else(|| "-".into()),
97 ),
98 ("State", sherlock.state.clone().unwrap_or_default()),
99 ];
100 output::print_detail(&sherlock, format, &fields);
101 }
102
103 SherlockCommand::Download { slug } => {
104 let sherlock = client.sherlocks().info(&slug).await?;
105 let url = client.sherlocks().download_link(sherlock.id).await?;
106 let bytes = client.challenges().download_file(&url).await?;
107 let filename = crate::sanitize_filename(&format!("{slug}.zip"), &format!("{slug}.zip"));
108 std::fs::write(&filename, &bytes)?;
109 output::print_message(&format!("Downloaded {} ({} bytes)", filename, bytes.len()));
110 }
111
112 SherlockCommand::Submit { id, task_id, flag } => {
113 let resp = client.sherlocks().submit_flag(id, task_id, &flag).await?;
114 output::print_message(&resp.message);
115 }
116 }
117 Ok(())
118}