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}