shindan_maker/client.rs
1use anyhow::Result;
2use reqwest::Client;
3use scraper::Html;
4use std::time::Duration;
5
6use crate::html_utils;
7use crate::http_utils;
8use crate::shindan_domain::ShindanDomain;
9
10#[cfg(feature = "segments")]
11use crate::segment::Segments;
12
13/// A client for interacting with ShindanMaker.
14#[derive(Clone, Debug)]
15pub struct ShindanClient {
16 client: Client,
17 domain: ShindanDomain,
18}
19
20impl ShindanClient {
21 /**
22 Create a new ShindanMaker client.
23
24 # Arguments
25 - `domain` - The domain of ShindanMaker to use.
26
27 # Returns
28 A new ShindanMaker client.
29
30 # Examples
31 ```
32 use anyhow::Result;
33 use shindan_maker::{ShindanClient, ShindanDomain};
34
35 fn main() -> Result<()> {
36 let client = ShindanClient::new(ShindanDomain::En)?; // Enum variant
37 let client = ShindanClient::new("Jp".parse()?)?; // String slice
38 let client = ShindanClient::new("EN".parse()?)?; // Case-insensitive
39 let client = ShindanClient::new(String::from("cn").parse()?)?; // String
40 Ok(())
41 }
42 ```
43 */
44 pub fn new(domain: ShindanDomain) -> Result<Self> {
45 const TIMEOUT_SECS: u64 = 3;
46
47 Ok(Self {
48 domain,
49 client: Client::builder()
50 .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0")
51 .use_rustls_tls()
52 .timeout(Duration::from_secs(TIMEOUT_SECS))
53 .build()?,
54 })
55 }
56
57 /**
58 Fetches and extracts title from a shindan page.
59
60 # Arguments
61 - `id` - The ID of the shindan
62
63 # Returns
64 The title of the shindan page.
65
66 # Errors
67 Returns error if network request fails or title cannot be extracted.
68
69 # Examples
70 ```
71 use anyhow::Result;
72 use shindan_maker::{ShindanClient, ShindanDomain};
73
74 #[tokio::main]
75 async fn main() -> Result<()> {
76 let client = ShindanClient::new(ShindanDomain::En)?;
77
78 let title = client
79 .get_title("1222992")
80 .await?;
81
82 println!("Title: {}", title);
83
84 Ok(())
85 }
86 ```
87 */
88 pub async fn get_title(&self, id: &str) -> Result<String> {
89 let document = self.fetch_document(id).await?;
90 html_utils::extract_title(&document)
91 }
92
93 /**
94 Fetches and extracts description from a shindan page.
95
96 # Arguments
97 - `id` - The ID of the shindan
98
99 # Returns
100 The description of the shindan page.
101
102 # Errors
103 Returns error if network request fails or description cannot be extracted.
104
105 # Examples
106 ```
107 use anyhow::Result;
108 use shindan_maker::{ShindanClient, ShindanDomain};
109
110 #[tokio::main]
111 async fn main() -> Result<()> {
112 let client = ShindanClient::new(ShindanDomain::En)?;
113
114 let desc = client
115 .get_description("1222992")
116 .await?;
117
118 println!("Description: {}", desc);
119
120 Ok(())
121 }
122 ```
123 */
124 pub async fn get_description(&self, id: &str) -> Result<String> {
125 let document = self.fetch_document(id).await?;
126 html_utils::extract_description(&document)
127 }
128
129 /**
130 Fetches and extracts both title and description from a shindan page.
131
132 # Arguments
133 - `id` - The ID of the shindan
134
135 # Returns
136 A tuple containing the title and description.
137
138 # Errors
139 Returns error if network request fails or content cannot be extracted.
140
141 # Examples
142 ```
143 use anyhow::Result;
144 use shindan_maker::{ShindanClient, ShindanDomain};
145
146 #[tokio::main]
147 async fn main() -> Result<()> {
148 let client = ShindanClient::new(ShindanDomain::En)?;
149
150 let (title, desc) = client
151 .get_title_with_description("1222992")
152 .await?;
153
154 println!("Title: {}", title);
155 println!("Description: {}", desc);
156
157 Ok(())
158 }
159 ```
160 */
161 pub async fn get_title_with_description(&self, id: &str) -> Result<(String, String)> {
162 let document = self.fetch_document(id).await?;
163
164 Ok((
165 html_utils::extract_title(&document)?,
166 html_utils::extract_description(&document)?,
167 ))
168 }
169
170 async fn fetch_document(&self, id: &str) -> Result<Html> {
171 let url = format!("{}{}", self.domain, id);
172
173 let text = self.client.get(&url).send().await?.text().await?;
174
175 Ok(Html::parse_document(&text))
176 }
177
178 async fn fetch_with_form_data(
179 &self,
180 id: &str,
181 name: &str,
182 extract_title: bool,
183 ) -> Result<(Option<String>, String)> {
184 let url = format!("{}{}", self.domain, id);
185
186 let initial_response = self.client.get(&url).send().await?;
187 let session_cookie = http_utils::extract_session_cookie(&initial_response)?;
188 let initial_response_text = initial_response.text().await?;
189
190 let (title, form_data) = if extract_title {
191 let (title, form_data) =
192 html_utils::extract_title_and_form_data(&initial_response_text, name)?;
193 (Some(title), form_data)
194 } else {
195 let document = Html::parse_document(&initial_response_text);
196 let form_data = html_utils::extract_form_data(&document, name)?;
197 (None, form_data)
198 };
199
200 let headers = http_utils::prepare_headers(&session_cookie)?;
201 let response_text = self
202 .client
203 .post(&url)
204 .headers(headers)
205 .form(&form_data)
206 .send()
207 .await?
208 .text()
209 .await?;
210
211 Ok((title, response_text))
212 }
213
214 async fn init_res(&self, id: &str, name: &str) -> Result<String> {
215 let (_, response_text) = self.fetch_with_form_data(id, name, false).await?;
216 Ok(response_text)
217 }
218
219 async fn get_title_and_init_res(&self, id: &str, name: &str) -> Result<(String, String)> {
220 let (title, response_text) = self.fetch_with_form_data(id, name, true).await?;
221 Ok((title.unwrap(), response_text))
222 }
223
224 /**
225 Get the segments of a shindan.
226
227 # Arguments
228 - `id` - The ID of the shindan.
229 - `name` - The name to use for the shindan.
230
231 # Returns
232 The segments of the shindan.
233
234 # Examples
235 ```
236 use shindan_maker::{ShindanClient, ShindanDomain};
237
238 #[tokio::main]
239 async fn main() {
240 let client = ShindanClient::new(ShindanDomain::En).unwrap();
241
242 let segments = client
243 .get_segments("1222992", "test_user")
244 .await
245 .unwrap();
246
247 println!("Result segments: {:#?}", segments);
248 }
249 ```
250 */
251 #[cfg(feature = "segments")]
252 pub async fn get_segments(&self, id: &str, name: &str) -> Result<Segments> {
253 let response_text = self.init_res(id, name).await?;
254 html_utils::get_segments(&response_text)
255 }
256
257 /**
258 Get the segments of a shindan and the title of the shindan.
259
260 # Arguments
261 - `id` - The ID of the shindan.
262 - `name` - The name to use for the shindan.
263
264 # Returns
265 The segments of the shindan and the title of the shindan.
266
267 # Examples
268 ```
269 use shindan_maker::{ShindanClient, ShindanDomain};
270
271 #[tokio::main]
272 async fn main() {
273 let client = ShindanClient::new(ShindanDomain::En).unwrap();
274
275 let (segments, title) = client
276 .get_segments_with_title("1222992", "test_user")
277 .await
278 .unwrap();
279
280 assert_eq!("Fantasy Stats", title);
281
282 println!("Result title: {}", title);
283 println!("Result text: {}", segments);
284
285 println!("Result segments: {:#?}", segments);
286 }
287 ```
288 */
289 #[cfg(feature = "segments")]
290 pub async fn get_segments_with_title(
291 &self,
292 id: &str,
293 name: &str,
294 ) -> Result<(Segments, String)> {
295 let (title, response_text) = self.get_title_and_init_res(id, name).await?;
296
297 let segments = html_utils::get_segments(&response_text)?;
298
299 Ok((segments, title))
300 }
301
302 /**
303 Get the HTML string of a shindan.
304
305 # Arguments
306 - `id` - The ID of the shindan.
307 - `name` - The name to use for the shindan.
308
309 # Returns
310 The HTML string of the shindan.
311
312 # Examples
313 ```
314 use shindan_maker::{ShindanClient, ShindanDomain};
315
316 #[tokio::main]
317 async fn main() {
318 let client = ShindanClient::new(ShindanDomain::En).unwrap();
319
320 let html_str = client
321 .get_html_str("1222992", "test_user")
322 .await
323 .unwrap();
324
325 println!("{}", html_str);
326 }
327 ```
328 */
329 #[cfg(feature = "html")]
330 pub async fn get_html_str(&self, id: &str, name: &str) -> Result<String> {
331 let response_text = self.init_res(id, name).await?;
332 html_utils::get_html_str(id, &response_text, &self.domain.to_string())
333 }
334
335 /**
336 Get the HTML string of a shindan and the title of the shindan.
337
338 # Arguments
339 - `id` - The ID of the shindan.
340 - `name` - The name to use for the shindan.
341
342 # Returns
343 The HTML string of the shindan and the title of the shindan.
344
345 # Examples
346 ```
347 use shindan_maker::{ShindanClient, ShindanDomain};
348
349 #[tokio::main]
350 async fn main() {
351 let client = ShindanClient::new(ShindanDomain::En).unwrap();
352
353 let (_html_str, title) = client
354 .get_html_str_with_title("1222992", "test_user")
355 .await
356 .unwrap();
357
358 assert_eq!("Fantasy Stats", title);
359 }
360 ```
361 */
362 #[cfg(feature = "html")]
363 pub async fn get_html_str_with_title(&self, id: &str, name: &str) -> Result<(String, String)> {
364 let (title, response_text) = self.get_title_and_init_res(id, name).await?;
365
366 let html = html_utils::get_html_str(id, &response_text, &self.domain.to_string())?;
367
368 Ok((html, title))
369 }
370}