1use std::io::{BufRead, BufReader, Read};
2use std::marker::PhantomData;
3use std::time::Duration;
4
5use log::{debug, trace};
6
7use serde::de::DeserializeOwned;
8use serde_json::{from_str, json};
9use ureq::{Agent, Request};
10use url::Url;
11
12use crate::model::{Config, Connections, Delay, Log, Proxies, Proxy, Rules, Traffic, Version};
13use crate::{Error, Result};
14
15trait Convert<T: DeserializeOwned> {
16 fn convert(self) -> Result<T>;
17}
18
19impl<T: DeserializeOwned> Convert<T> for String {
20 fn convert(self) -> Result<T> {
21 from_str(&self).map_err(Error::BadResponseFormat)
22 }
23}
24
25#[derive(Debug, Clone)]
26pub struct ClashBuilder {
27 url: Url,
28 secret: Option<String>,
29 timeout: Option<Duration>,
30}
31
32impl ClashBuilder {
33 pub fn new<S: Into<String>>(url: S) -> Result<Self> {
34 let mut url_str = url.into();
35 if !url_str.ends_with('/') {
37 url_str += "/";
38 };
39 let url = Url::parse(&url_str).map_err(|_| Error::UrlParseError)?;
40 Ok(Self {
41 url,
42 secret: None,
43 timeout: None,
44 })
45 }
46
47 pub fn secret(mut self, secret: Option<String>) -> Self {
48 self.secret = secret;
49 self
50 }
51
52 pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
53 self.timeout = timeout;
54 self
55 }
56
57 pub fn build(self) -> Clash {
58 let mut clash = Clash::new(self.url);
59 clash.secret = self.secret;
60 clash.timeout = self.timeout;
61 clash
62 }
63}
64
65#[derive(Debug, Clone)]
71pub struct Clash {
72 url: Url,
73 secret: Option<String>,
74 timeout: Option<Duration>,
75 agent: Agent,
76}
77
78impl Clash {
79 pub fn builder<S: Into<String>>(url: S) -> Result<ClashBuilder> {
80 ClashBuilder::new(url)
81 }
82
83 pub fn new(url: Url) -> Self {
84 debug!("Url of clash RESTful API: {}", url);
85 Self {
86 url,
87 secret: None,
88 timeout: None,
89 agent: Agent::new(),
90 }
91 }
92
93 fn build_request(&self, endpoint: &str, method: &str) -> Result<Request> {
94 let url = self.url.join(endpoint).map_err(|_| Error::UrlParseError)?;
95 let mut req = self.agent.request_url(method, &url);
96
97 if let Some(timeout) = self.timeout {
98 req = req.timeout(timeout)
99 }
100
101 if let Some(ref secret) = self.secret {
102 req = req.set("Authorization", &format!("Bearer {}", secret))
103 }
104
105 Ok(req)
106 }
107
108 fn build_request_without_timeout(&self, endpoint: &str, method: &str) -> Result<Request> {
109 let url = self.url.join(endpoint).map_err(|_| Error::UrlParseError)?;
110 let mut req = self.agent.request_url(method, &url);
111
112 if let Some(ref secret) = self.secret {
113 req = req.set("Authorization", &format!("Bearer {}", secret))
114 }
115
116 Ok(req)
117 }
118
119 pub fn oneshot_req_with_body(
121 &self,
122 endpoint: &str,
123 method: &str,
124 body: Option<String>,
125 ) -> Result<String> {
126 trace!("Body: {:#?}", body);
127 let resp = if let Some(body) = body {
128 self.build_request(endpoint, method)?.send_string(&body)?
129 } else {
130 self.build_request(endpoint, method)?.call()?
131 };
132
133 if resp.status() >= 400 {
134 return Err(Error::FailedResponse(resp.status()));
135 }
136
137 let text = resp.into_string().map_err(|_| Error::BadResponseEncoding)?;
138 trace!("Received response: {}", text);
139
140 Ok(text)
141 }
142
143 pub fn oneshot_req(&self, endpoint: &str, method: &str) -> Result<String> {
145 self.oneshot_req_with_body(endpoint, method, None)
146 }
147
148 pub fn longhaul_req<T: DeserializeOwned>(
168 &self,
169 endpoint: &str,
170 method: &str,
171 ) -> Result<LongHaul<T>> {
172 let resp = self
173 .build_request_without_timeout(endpoint, method)?
174 .call()?;
175
176 if resp.status() >= 400 {
177 return Err(Error::FailedResponse(resp.status()));
178 }
179
180 Ok(LongHaul::new(Box::new(resp.into_reader())))
181 }
182
183 pub fn get<T: DeserializeOwned>(&self, endpoint: &str) -> Result<T> {
185 self.oneshot_req(endpoint, "GET").and_then(Convert::convert)
186 }
187
188 pub fn delete(&self, endpoint: &str) -> Result<()> {
190 self.oneshot_req(endpoint, "DELETE").map(|_| ())
191 }
192
193 pub fn put<T: DeserializeOwned>(&self, endpoint: &str, body: Option<String>) -> Result<T> {
195 self.oneshot_req_with_body(endpoint, "PUT", body)
196 .and_then(Convert::convert)
197 }
198
199 pub fn get_version(&self) -> Result<Version> {
201 self.get("version")
202 }
203
204 pub fn get_configs(&self) -> Result<Config> {
206 self.get("configs")
207 }
208
209 pub fn reload_configs(&self, force: bool, path: &str) -> Result<()> {
216 let body = json!({ "path": path }).to_string();
217 debug!("{}", body);
218 self.put::<String>(if force { "configs?force" } else { "configs" }, Some(body))
219 .map(|_| ())
220 }
221
222 pub fn get_proxies(&self) -> Result<Proxies> {
224 self.get("proxies")
225 }
226
227 pub fn get_rules(&self) -> Result<Rules> {
229 self.get("rules")
230 }
231
232 pub fn get_proxy(&self, proxy: &str) -> Result<Proxy> {
234 self.get(&format!("proxies/{}", proxy))
235 }
236
237 pub fn get_connections(&self) -> Result<Connections> {
239 self.get("connections")
240 }
241
242 pub fn close_connections(&self) -> Result<()> {
244 self.delete("connections")
245 }
246
247 pub fn close_one_connection(&self, id: &str) -> Result<()> {
249 self.delete(&format!("connections/{}", id))
250 }
251
252 pub fn get_traffic(&self) -> Result<LongHaul<Traffic>> {
260 self.longhaul_req("traffic", "GET")
261 }
262
263 pub fn get_log(&self) -> Result<LongHaul<Log>> {
271 self.longhaul_req("logs", "GET")
272 }
273
274 pub fn get_proxy_delay(&self, proxy: &str, test_url: &str, timeout: u64) -> Result<Delay> {
276 use urlencoding::encode as e;
277 let (proxy, test_url) = (e(proxy), e(test_url));
278 self.get(&format!(
279 "proxies/{}/delay?url={}&timeout={}",
280 proxy, test_url, timeout
281 ))
282 }
283
284 pub fn set_proxygroup_selected(&self, group: &str, proxy: &str) -> Result<()> {
286 let body = format!("{{\"name\":\"{}\"}}", proxy);
287 self.oneshot_req_with_body(&format!("proxies/{}", group), "PUT", Some(body))?;
288 Ok(())
289 }
290}
291
292pub struct LongHaul<T: DeserializeOwned> {
293 reader: BufReader<Box<dyn Read + Send>>,
294 ty: PhantomData<T>,
295}
296
297impl<T: DeserializeOwned> LongHaul<T> {
298 pub fn new(reader: Box<dyn Read + Send>) -> Self {
299 Self {
300 reader: BufReader::new(reader),
301 ty: PhantomData,
302 }
303 }
304
305 pub fn next_item(&mut self) -> Option<Result<T>> {
306 Some(self.next_raw()?.and_then(Convert::convert))
307 }
308
309 pub fn next_raw(&mut self) -> Option<Result<String>> {
310 let mut buf = String::with_capacity(30);
311 match self.reader.read_line(&mut buf) {
312 Ok(0) => None,
313 Ok(_) => Some(Ok(buf)),
314 Err(e) => Some(Err(Error::Other(format!("{:}", e)))),
315 }
316 }
317}
318
319impl<T: DeserializeOwned> Iterator for LongHaul<T> {
320 type Item = Result<T>;
321 fn next(&mut self) -> Option<Self::Item> {
322 self.next_item()
323 }
324}