1use crate::utils::system::get_system_proxy;
2use awc::Client;
3use case_insensitive_string::CaseInsensitiveString;
4use std::collections::HashMap;
5use std::error::Error as StdError;
6use std::time::Duration;
7const DEFAULT_TIMEOUT: u64 = 15;
9
10#[derive(Debug, Clone)]
11pub struct ProxyConfig {
12 pub proxy: Option<String>,
13}
14
15impl Default for ProxyConfig {
16 fn default() -> Self {
17 ProxyConfig { proxy: None }
18 }
19}
20
21#[derive(Debug, Clone)]
23pub struct HttpResponse {
24 pub status: u16,
26 pub body: String,
28 pub headers: HashMap<String, String>,
30}
31
32#[derive(Debug, Clone)]
34pub struct HttpError {
35 pub message: String,
37 pub status: Option<u16>,
39}
40
41impl std::fmt::Display for HttpError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 if let Some(status) = self.status {
44 write!(f, "HTTP error {}: {}", status, self.message)
45 } else {
46 write!(f, "HTTP error: {}", self.message)
47 }
48 }
49}
50
51impl StdError for HttpError {
52 fn source(&self) -> Option<&(dyn StdError + 'static)> {
53 None
54 }
55}
56
57pub fn parse_proxy(proxy_str: &str) -> ProxyConfig {
58 if proxy_str == "SYSTEM" {
59 return ProxyConfig {
60 proxy: Some(get_system_proxy()),
61 };
62 } else if proxy_str == "NONE" {
63 return ProxyConfig { proxy: None };
64 } else if !proxy_str.is_empty() {
65 return ProxyConfig {
66 proxy: Some(proxy_str.to_string()),
67 };
68 }
69 ProxyConfig { proxy: None }
70}
71
72pub async fn web_get_async(
83 url: &str,
84 proxy_config: &ProxyConfig,
85 headers: Option<&HashMap<CaseInsensitiveString, String>>,
86) -> Result<HttpResponse, HttpError> {
87 let mut client_builder = Client::builder().timeout(Duration::from_secs(DEFAULT_TIMEOUT));
90
91 let client = client_builder.finish();
108
109 let mut client_request = client
111 .get(url)
112 .insert_header(("User-Agent", "subconverter-rs"));
113 if let Some(custom_headers) = headers {
114 for (key, value) in custom_headers {
115 client_request = client_request.insert_header((key.to_string(), value.to_string()));
116 }
117 }
118
119 let mut response = match client_request.send().await {
121 Ok(resp) => resp,
122 Err(e) => {
123 return Err(HttpError {
124 message: format!("Failed to send request: {}", e),
125 status: None,
126 });
127 }
128 };
129
130 let status = response.status().as_u16();
132
133 let mut resp_headers = HashMap::new();
135 for (key, value) in response.headers() {
136 if let Ok(v) = value.to_str() {
137 resp_headers.insert(key.to_string(), v.to_string());
138 }
139 }
140
141 match response.body().await {
143 Ok(body) => Ok(HttpResponse {
144 status,
145 body: String::from_utf8(body.to_vec()).unwrap(),
146 headers: resp_headers,
147 }),
148 Err(e) => Err(HttpError {
149 message: format!("Failed to read response body: {}", e),
150 status: Some(status),
151 }),
152 }
153}
154
155pub fn web_get(
160 url: &str,
161 proxy_config: &ProxyConfig,
162 headers: Option<&HashMap<CaseInsensitiveString, String>>,
163) -> Result<HttpResponse, HttpError> {
164 let rt = match tokio::runtime::Builder::new_current_thread()
166 .enable_all()
167 .build()
168 {
169 Ok(rt) => rt,
170 Err(e) => {
171 return Err(HttpError {
172 message: format!("Failed to create tokio runtime: {}", e),
173 status: None,
174 });
175 }
176 };
177
178 rt.block_on(web_get_async(url, proxy_config, headers))
180}
181
182pub async fn web_get_content_async(
187 url: &str,
188 proxy_config: &ProxyConfig,
189 headers: Option<&HashMap<CaseInsensitiveString, String>>,
190) -> Result<String, String> {
191 match web_get_async(url, proxy_config, headers).await {
192 Ok(response) => {
193 if (200..300).contains(&response.status) {
194 Ok(response.body)
195 } else {
196 Err(format!("HTTP error {}: {}", response.status, response.body))
197 }
198 }
199 Err(e) => Err(e.message),
200 }
201}
202
203pub fn get_sub_info_from_header(headers: &HashMap<String, String>) -> String {
211 let mut sub_info = String::new();
212
213 let mut upload: u64 = 0;
215 let mut download: u64 = 0;
216 let mut total: u64 = 0;
217 let mut expire: String = String::new();
218
219 if let Some(userinfo) = headers.get("subscription-userinfo") {
221 for info_item in userinfo.split(';') {
222 let info_item = info_item.trim();
223 if info_item.starts_with("upload=") {
224 if let Ok(value) = info_item[7..].parse::<u64>() {
225 upload = value;
226 }
227 } else if info_item.starts_with("download=") {
228 if let Ok(value) = info_item[9..].parse::<u64>() {
229 download = value;
230 }
231 } else if info_item.starts_with("total=") {
232 if let Ok(value) = info_item[6..].parse::<u64>() {
233 total = value;
234 }
235 } else if info_item.starts_with("expire=") {
236 expire = info_item[7..].to_string();
237 }
238 }
239 }
240
241 if upload > 0 || download > 0 {
243 sub_info.push_str(&format!("upload={}, download={}", upload, download));
244 }
245
246 if total > 0 {
248 if !sub_info.is_empty() {
249 sub_info.push_str(", ");
250 }
251 sub_info.push_str(&format!("total={}", total));
252 }
253
254 if !expire.is_empty() {
256 if !sub_info.is_empty() {
257 sub_info.push_str(", ");
258 }
259 sub_info.push_str(&format!("expire={}", expire));
260 }
261
262 sub_info
263}
264
265pub fn get_sub_info_from_response(
274 headers: &HashMap<String, String>,
275 sub_info: &mut String,
276) -> bool {
277 let header_info = get_sub_info_from_header(headers);
278 if !header_info.is_empty() {
279 if !sub_info.is_empty() {
280 sub_info.push_str(", ");
281 }
282 sub_info.push_str(&header_info);
283 true
284 } else {
285 false
286 }
287}
288
289pub async fn web_post_async(
301 url: &str,
302 data: String,
303 proxy_config: &ProxyConfig,
304 headers: Option<&HashMap<CaseInsensitiveString, String>>,
305) -> Result<HttpResponse, HttpError> {
306 let mut client_builder = Client::builder().timeout(Duration::from_secs(DEFAULT_TIMEOUT));
307
308 let client = client_builder.finish();
314
315 let mut client_request = client
316 .post(url)
317 .insert_header(("Content-Type", "application/json")); if let Some(custom_headers) = headers {
320 for (key, value) in custom_headers {
321 client_request = client_request.insert_header((key.to_string(), value.to_string()));
322 }
323 }
324
325 let mut response = match client_request.send_body(data).await {
327 Ok(resp) => resp,
328 Err(e) => {
329 return Err(HttpError {
330 message: format!("Failed to send POST request: {}", e),
331 status: None,
332 });
333 }
334 };
335
336 let status = response.status().as_u16();
337
338 let mut resp_headers = HashMap::new();
339 for (key, value) in response.headers() {
340 if let Ok(v) = value.to_str() {
341 resp_headers.insert(key.to_string(), v.to_string());
342 }
343 }
344
345 match response.body().limit(10_000_000).await {
346 Ok(body) => Ok(HttpResponse {
348 status,
349 body: String::from_utf8(body.to_vec())
350 .unwrap_or_else(|_| "Invalid UTF-8 body".to_string()),
351 headers: resp_headers,
352 }),
353 Err(e) => Err(HttpError {
354 message: format!("Failed to read POST response body: {}", e),
355 status: Some(status),
356 }),
357 }
358}
359
360pub async fn web_patch_async(
372 url: &str,
373 data: String,
374 proxy_config: &ProxyConfig,
375 headers: Option<&HashMap<CaseInsensitiveString, String>>,
376) -> Result<HttpResponse, HttpError> {
377 let mut client_builder = Client::builder().timeout(Duration::from_secs(DEFAULT_TIMEOUT));
378
379 let client = client_builder.finish();
382
383 let mut client_request = client
384 .patch(url)
385 .insert_header(("Content-Type", "application/json")); if let Some(custom_headers) = headers {
388 for (key, value) in custom_headers {
389 client_request = client_request.insert_header((key.to_string(), value.to_string()));
390 }
391 }
392
393 let mut response = match client_request.send_body(data).await {
395 Ok(resp) => resp,
396 Err(e) => {
397 return Err(HttpError {
398 message: format!("Failed to send PATCH request: {}", e),
399 status: None,
400 });
401 }
402 };
403
404 let status = response.status().as_u16();
405
406 let mut resp_headers = HashMap::new();
407 for (key, value) in response.headers() {
408 if let Ok(v) = value.to_str() {
409 resp_headers.insert(key.to_string(), v.to_string());
410 }
411 }
412
413 match response.body().limit(10_000_000).await {
414 Ok(body) => Ok(HttpResponse {
416 status,
417 body: String::from_utf8(body.to_vec())
418 .unwrap_or_else(|_| "Invalid UTF-8 body".to_string()),
419 headers: resp_headers,
420 }),
421 Err(e) => Err(HttpError {
422 message: format!("Failed to read PATCH response body: {}", e),
423 status: Some(status),
424 }),
425 }
426}