Skip to main content

unifly_api/legacy/
clients.rs

1// Legacy API client (station) endpoints
2//
3// Client management via stat/sta (read) and cmd/stamgr (commands).
4// Covers listing, blocking, kicking, forgetting, and guest authorization.
5
6use serde_json::json;
7use tracing::debug;
8
9use crate::error::Error;
10use crate::legacy::client::LegacyClient;
11use crate::legacy::models::LegacyClientEntry;
12
13impl LegacyClient {
14    /// List all currently connected clients (stations).
15    ///
16    /// `GET /api/s/{site}/stat/sta`
17    pub async fn list_clients(&self) -> Result<Vec<LegacyClientEntry>, Error> {
18        let url = self.site_url("stat/sta");
19        debug!("listing connected clients");
20        self.get(url).await
21    }
22
23    /// Block a client by MAC address.
24    ///
25    /// `POST /api/s/{site}/cmd/stamgr` with `{"cmd": "block-sta", "mac": "..."}`
26    pub async fn block_client(&self, mac: &str) -> Result<(), Error> {
27        let url = self.site_url("cmd/stamgr");
28        debug!(mac, "blocking client");
29        let _: Vec<serde_json::Value> = self
30            .post(
31                url,
32                &json!({
33                    "cmd": "block-sta",
34                    "mac": mac,
35                }),
36            )
37            .await?;
38        Ok(())
39    }
40
41    /// Unblock a client by MAC address.
42    ///
43    /// `POST /api/s/{site}/cmd/stamgr` with `{"cmd": "unblock-sta", "mac": "..."}`
44    pub async fn unblock_client(&self, mac: &str) -> Result<(), Error> {
45        let url = self.site_url("cmd/stamgr");
46        debug!(mac, "unblocking client");
47        let _: Vec<serde_json::Value> = self
48            .post(
49                url,
50                &json!({
51                    "cmd": "unblock-sta",
52                    "mac": mac,
53                }),
54            )
55            .await?;
56        Ok(())
57    }
58
59    /// Disconnect (kick) a client.
60    ///
61    /// `POST /api/s/{site}/cmd/stamgr` with `{"cmd": "kick-sta", "mac": "..."}`
62    pub async fn kick_client(&self, mac: &str) -> Result<(), Error> {
63        let url = self.site_url("cmd/stamgr");
64        debug!(mac, "kicking client");
65        let _: Vec<serde_json::Value> = self
66            .post(
67                url,
68                &json!({
69                    "cmd": "kick-sta",
70                    "mac": mac,
71                }),
72            )
73            .await?;
74        Ok(())
75    }
76
77    /// Forget (permanently remove) a client by MAC address.
78    ///
79    /// `POST /api/s/{site}/cmd/stamgr` with `{"cmd": "forget-sta", "macs": [...]}`
80    pub async fn forget_client(&self, mac: &str) -> Result<(), Error> {
81        let url = self.site_url("cmd/stamgr");
82        debug!(mac, "forgetting client");
83        let _: Vec<serde_json::Value> = self
84            .post(
85                url,
86                &json!({
87                    "cmd": "forget-sta",
88                    "macs": [mac],
89                }),
90            )
91            .await?;
92        Ok(())
93    }
94
95    /// Authorize a guest client on the hotspot portal.
96    ///
97    /// `POST /api/s/{site}/cmd/stamgr` with guest authorization parameters.
98    ///
99    /// - `mac`: Client MAC address
100    /// - `minutes`: Authorization duration in minutes
101    /// - `up_kbps`: Optional upload bandwidth limit (Kbps)
102    /// - `down_kbps`: Optional download bandwidth limit (Kbps)
103    /// - `quota_mb`: Optional data transfer quota (MB)
104    pub async fn authorize_guest(
105        &self,
106        mac: &str,
107        minutes: u32,
108        up_kbps: Option<u32>,
109        down_kbps: Option<u32>,
110        quota_mb: Option<u32>,
111    ) -> Result<(), Error> {
112        let url = self.site_url("cmd/stamgr");
113        debug!(mac, minutes, "authorizing guest");
114
115        let mut body = json!({
116            "cmd": "authorize-guest",
117            "mac": mac,
118            "minutes": minutes,
119        });
120
121        let obj = body
122            .as_object_mut()
123            .expect("json! macro always produces an object");
124        if let Some(up) = up_kbps {
125            obj.insert("up".into(), json!(up));
126        }
127        if let Some(down) = down_kbps {
128            obj.insert("down".into(), json!(down));
129        }
130        if let Some(quota) = quota_mb {
131            obj.insert("bytes".into(), json!(quota));
132        }
133
134        let _: Vec<serde_json::Value> = self.post(url, &body).await?;
135        Ok(())
136    }
137
138    /// Revoke guest authorization for a client.
139    ///
140    /// `POST /api/s/{site}/cmd/stamgr` with `{"cmd": "unauthorize-guest", "mac": "..."}`
141    pub async fn unauthorize_guest(&self, mac: &str) -> Result<(), Error> {
142        let url = self.site_url("cmd/stamgr");
143        debug!(mac, "revoking guest authorization");
144        let _: Vec<serde_json::Value> = self
145            .post(
146                url,
147                &json!({
148                    "cmd": "unauthorize-guest",
149                    "mac": mac,
150                }),
151            )
152            .await?;
153        Ok(())
154    }
155}