1use once_cell::sync::Lazy;
2use reqwest::{
3 Client,
4 header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue, USER_AGENT},
5};
6use serde::Deserialize;
7use std::sync::Mutex;
8use std::time::Duration;
9use uuid::Uuid;
10
11use crate::error::Error;
12
13pub const DEFAULT_API_HOST: &str = "https://api.mixin.one";
14pub const DEFAULT_BLAZE_HOST: &str = "blaze.mixin.one";
15pub const DEFAULT_USER_AGENT: &str = "Bot-API-Rust-Client";
16
17pub static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
18 Client::builder()
19 .timeout(Duration::from_secs(10))
20 .build()
21 .expect("Failed to create HTTP client")
22});
23
24static HTTP_URI: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(DEFAULT_API_HOST.to_string()));
25static BLAZE_URI: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(DEFAULT_BLAZE_HOST.to_string()));
26static USER_AGENT_STR: Lazy<Mutex<String>> =
27 Lazy::new(|| Mutex::new(DEFAULT_USER_AGENT.to_string()));
28static UID: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
29static SID: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
30static PRIVATE_KEY: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
31
32#[derive(Debug, Deserialize, Default)]
33pub struct ApiResponse<T> {
34 pub data: Option<T>,
35 pub error: Option<ApiError>,
36}
37
38#[derive(Debug, Deserialize, Clone, Default)]
39pub struct ApiError {
40 pub status: i32,
41 pub code: i32,
42 pub description: String,
43}
44
45impl std::fmt::Display for ApiError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(
48 f,
49 "API Error (Status: {}, Code: {}): {}",
50 self.status, self.code, self.description
51 )
52 }
53}
54
55impl std::error::Error for ApiError {}
56
57pub fn with_api_key(user_id: String, session_id: String, private_key: String) {
58 *UID.lock().unwrap() = user_id;
59 *SID.lock().unwrap() = session_id;
60 *PRIVATE_KEY.lock().unwrap() = private_key;
61}
62
63pub fn set_base_uri(base: String) {
64 *HTTP_URI.lock().unwrap() = base;
65}
66
67pub fn set_blaze_uri(blaze: String) {
68 *BLAZE_URI.lock().unwrap() = blaze;
69}
70
71pub fn set_user_agent(ua: String) {
72 *USER_AGENT_STR.lock().unwrap() = ua;
73}
74
75pub async fn request(
76 method: &str,
77 path: &str,
78 body: &[u8],
79 access_token: &str,
80) -> Result<Vec<u8>, Error> {
81 request_with_id(method, path, body, access_token, Uuid::new_v4().to_string()).await
82}
83
84pub async fn request_with_id(
85 method: &str,
86 path: &str,
87 body: &[u8],
88 access_token: &str,
89 request_id: String,
90) -> Result<Vec<u8>, Error> {
91 let uri = format!("{}{}", *HTTP_URI.lock().unwrap(), path);
92
93 let mut headers = HeaderMap::new();
94 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
95 headers.insert(
96 AUTHORIZATION,
97 HeaderValue::from_str(&format!("Bearer {}", access_token))?,
98 );
99 headers.insert("X-Request-Id", HeaderValue::from_str(&request_id)?);
100 headers.insert(
101 USER_AGENT,
102 HeaderValue::from_str(&USER_AGENT_STR.lock().unwrap())?,
103 );
104
105 let response = HTTP_CLIENT
106 .request(reqwest::Method::from_bytes(method.as_bytes())?, &uri)
107 .headers(headers)
108 .body(body.to_vec())
109 .send()
110 .await?;
111
112 if response.status().is_server_error() {
113 let error = ApiError {
114 status: response.status().as_u16() as i32,
115 code: 0,
116 description: "Server error".to_string(),
117 };
118 return Err(error.into());
119 }
120
121 let body = response.bytes().await?;
122 Ok(body.to_vec())
123}
124
125pub async fn simple_request(method: &str, path: &str, body: &[u8]) -> Result<Vec<u8>, Error> {
126 let uri = format!("{}{}", *HTTP_URI.lock().unwrap(), path);
127
128 let mut headers = HeaderMap::new();
129 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
130 let response = HTTP_CLIENT
131 .request(reqwest::Method::from_bytes(method.as_bytes())?, &uri)
132 .headers(headers)
133 .body(body.to_vec())
134 .send()
135 .await?;
136
137 if response.status().is_server_error() {
138 let error = ApiError {
139 status: response.status().as_u16() as i32,
140 code: 0,
141 description: "Server error".to_string(),
142 };
143 return Err(error.into());
144 }
145
146 let body = response.bytes().await?;
147 Ok(body.to_vec())
148}