Skip to main content

htb_cli/api/
ctf.rs

1use serde_json::json;
2
3use crate::error::HtbError;
4use crate::models::ctf::{
5    CtfChallengeSolve, CtfEvent, CtfEventData, CtfEventDetail, CtfFlagResult, CtfMenu,
6    CtfScoreboard, CtfSolve, CtfUserProfile,
7};
8use crate::models::ActionResponse;
9
10use super::HtbClient;
11
12pub struct CtfApi<'a>(pub(crate) &'a HtbClient);
13
14impl CtfApi<'_> {
15    pub async fn profile(&self) -> Result<CtfUserProfile, HtbError> {
16        self.0.get("/api/users/profile").await
17    }
18
19    pub async fn events(&self) -> Result<Vec<CtfEvent>, HtbError> {
20        self.0.get("/api/ctfs").await
21    }
22
23    pub async fn event_details(&self, slug: &str) -> Result<CtfEventDetail, HtbError> {
24        let encoded = super::encode_path(slug);
25        self.0.get(&format!("/api/ctfs/details/{encoded}")).await
26    }
27
28    pub async fn event_data(&self, event_id: u64) -> Result<CtfEventData, HtbError> {
29        self.0.get(&format!("/api/ctfs/{event_id}")).await
30    }
31
32    pub async fn menu(&self, event_id: u64) -> Result<CtfMenu, HtbError> {
33        self.0.get(&format!("/api/ctfs/{event_id}/menu")).await
34    }
35
36    pub async fn submit_flag(
37        &self,
38        challenge_id: u64,
39        flag: &str,
40    ) -> Result<CtfFlagResult, HtbError> {
41        self.0
42            .post(
43                "/api/flags/own",
44                &json!({"challenge_id": challenge_id, "flag": flag}),
45            )
46            .await
47    }
48
49    pub async fn download_file(&self, challenge_id: u64) -> Result<Vec<u8>, HtbError> {
50        self.0
51            .get_bytes(&format!("/api/challenges/{challenge_id}/download"))
52            .await
53    }
54
55    pub async fn container_start(&self, challenge_id: u64) -> Result<ActionResponse, HtbError> {
56        self.0
57            .post(
58                "/api/challenges/containers/start",
59                &json!({"id": challenge_id}),
60            )
61            .await
62    }
63
64    pub async fn container_stop(&self, challenge_id: u64) -> Result<ActionResponse, HtbError> {
65        self.0
66            .post(
67                "/api/challenges/containers/stop",
68                &json!({"id": challenge_id}),
69            )
70            .await
71    }
72
73    pub async fn scoreboard(&self, event_id: u64) -> Result<CtfScoreboard, HtbError> {
74        self.0.get(&format!("/api/ctfs/scores/{event_id}")).await
75    }
76
77    pub async fn solves(&self, event_id: u64) -> Result<Vec<CtfSolve>, HtbError> {
78        self.0.get(&format!("/api/ctfs/solves/{event_id}")).await
79    }
80
81    pub async fn challenge_solves(
82        &self,
83        challenge_id: u64,
84    ) -> Result<Vec<CtfChallengeSolve>, HtbError> {
85        self.0
86            .get(&format!("/api/challenges/{challenge_id}/solves"))
87            .await
88    }
89}