lib_mal/builder.rs
1use reqwest::Client;
2use std::fs;
3use std::path::PathBuf;
4use std::time::SystemTime;
5
6use crate::client::{decrypt_tokens, encrypt_token, TokenResponse, Tokens};
7use crate::{MALClient, MALError};
8
9///# Example
10///```
11/// use lib_mal::ClientBuilder;
12/// fn example() {
13/// let client = ClientBuilder::new().secret("[YOUR_CLIENT_ID]".to_string()).access_token("exampleExAmPlE".to_string()).build_no_refresh();
14/// }
15///```
16pub struct ClientBuilder {
17 client_secret: Option<String>,
18 dirs: Option<PathBuf>,
19 access_token: Option<String>,
20 caching: bool,
21}
22
23#[allow(clippy::new_without_default)]
24impl ClientBuilder {
25 ///Creates a new ClientBuilder. All fields are set to None by default.
26 pub fn new() -> Self {
27 ClientBuilder {
28 client_secret: None,
29 dirs: None,
30 access_token: None,
31 caching: false,
32 }
33 }
34
35 /// Sets the client_secret
36 /// # Example
37 ///
38 ///```
39 /// # use lib_mal::ClientBuilder;
40 /// # fn test() {
41 /// let client =
42 /// ClientBuilder::new().secret("[YOUR_CLIENT_ID]".to_string()).build_no_refresh();
43 ///
44 /// let another_client = ClientBuilder::new().secret(None).build_no_refresh();
45 /// # }
46 ///
47 ///```
48 pub fn secret(mut self, secret: impl Into<Option<String>>) -> Self {
49 self.client_secret = secret.into();
50 self
51 }
52
53 /// Sets the directory the client will use to cache the tokens
54 /// # Example
55 ///
56 /// ```
57 /// # use lib_mal::ClientBuilder;
58 /// use std::path::PathBuf;
59 /// # fn test() {
60 /// let client = ClientBuilder::new().cache_dir(PathBuf::new()).build_no_refresh();
61 /// # }
62 /// ```
63 pub fn cache_dir(mut self, path: impl Into<Option<PathBuf>>) -> Self {
64 self.dirs = path.into();
65 self
66 }
67
68 /// Sets the access token for the client
69 /// # Example
70 ///
71 /// ```
72 /// # use lib_mal::ClientBuilder;
73 /// # fn test() {
74 /// let client =
75 /// ClientBuilder::new().access_token("exampleToKeN".to_string()).build_no_refresh();
76 /// # }
77 /// ```
78 pub fn access_token(mut self, token: impl Into<Option<String>>) -> Self {
79 self.access_token = token.into();
80 self
81 }
82
83 /// Sets wether or not the client should cache the tokens
84 /// # Example
85 ///
86 /// ```
87 /// # use lib_mal::ClientBuilder;
88 /// # fn test() {
89 /// let client = ClientBuilder::new().caching(false).build_no_refresh();
90 /// # }
91 ///
92 /// ```
93 pub fn caching(mut self, caching: bool) -> Self {
94 self.caching = caching;
95 self
96 }
97
98 /// Builds a `MALClient` without attempting to refresh the access token
99 ///
100 /// # Example
101 ///
102 /// ```
103 /// use lib_mal::ClientBuilder;
104 /// use std::path::PathBuf;
105 /// fn example() {
106 /// let client =
107 /// ClientBuilder::new().secret("[YOUR_CLIENT_ID]".to_string()).caching(true).cache_dir(PathBuf::new()).build_no_refresh();
108 /// }
109 pub fn build_no_refresh(self) -> MALClient {
110 MALClient::new(
111 self.client_secret.unwrap_or_default(),
112 self.dirs.unwrap_or_default(),
113 self.access_token.unwrap_or_default(),
114 Client::new(),
115 self.caching,
116 false,
117 )
118 }
119
120 /// Builds a `MALClient` after attempting to refresh the access token from cache
121 ///
122 /// # Example
123 ///
124 /// ```
125 /// use lib_mal::ClientBuilder;
126 /// use lib_mal::MALError;
127 /// use std::path::PathBuf;
128 /// async fn example() -> Result<(), MALError> {
129 /// let client =
130 /// ClientBuilder::new().secret("[YOUR_CLIENT_ID]".to_string()).caching(true).cache_dir(PathBuf::new()).build_with_refresh().await?;
131 ///
132 /// Ok(())
133 /// }
134 pub async fn build_with_refresh(self) -> Result<MALClient, MALError> {
135 let client = reqwest::Client::new();
136 let mut will_cache = self.caching;
137 let mut n_a = false;
138
139 let dir = if let Some(d) = self.dirs {
140 d
141 } else {
142 will_cache = false;
143 PathBuf::new()
144 };
145
146 let mut token = String::new();
147 if will_cache && dir.join("tokens").exists() {
148 if let Ok(tokens) = fs::read(dir.join("tokens")) {
149 let mut tok: Tokens = decrypt_tokens(&tokens).unwrap();
150 if let Ok(n) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
151 if n.as_secs() - tok.today >= tok.expires_in as u64 {
152 let params = [
153 ("grant_type", "refresh_token"),
154 ("refesh_token", &tok.refresh_token),
155 ];
156 let res = client
157 .post("https://myanimelist.net/v1/oauth2/token")
158 .form(¶ms)
159 .send()
160 .await;
161 if let Err(e) = res {
162 return Err(MALError::new(
163 "Unable to refresh token",
164 e.to_string().as_str(),
165 None,
166 ));
167 }
168 let new_toks = serde_json::from_str::<TokenResponse>(
169 &res.unwrap().text().await.unwrap(),
170 );
171 if let Err(e) = new_toks {
172 return Err(MALError::new(
173 "Unable to parse token reponse",
174 e.to_string().as_str(),
175 None,
176 ));
177 }
178 let new_toks = new_toks.unwrap();
179 token = new_toks.access_token.clone();
180 tok = Tokens {
181 access_token: new_toks.access_token,
182 refresh_token: new_toks.refresh_token,
183 expires_in: new_toks.expires_in,
184 today: SystemTime::now()
185 .duration_since(SystemTime::UNIX_EPOCH)
186 .unwrap()
187 .as_secs(),
188 };
189
190 if let Err(e) = fs::write(dir.join("tokens"), encrypt_token(tok)) {
191 return Err(MALError::new(
192 "Unable to write tokens to cache",
193 e.to_string().as_str(),
194 None,
195 ));
196 }
197 } else {
198 token = tok.access_token;
199 }
200 }
201 }
202 } else {
203 will_cache = self.caching;
204 n_a = true;
205 }
206
207 Ok(MALClient::new(
208 self.client_secret.unwrap_or_default(),
209 dir,
210 token,
211 client,
212 will_cache,
213 n_a,
214 ))
215 }
216}