1use crate::headers::{get_headers_for_type, RequestType};
2use anyhow::{Context, Result};
3use cookie_store::serde;
4use cookie_store::CookieStore;
5use reqwest::Client;
6use reqwest_cookie_store::CookieStoreMutex;
7use std::fs::{create_dir_all, File};
8use std::io::{BufReader, BufWriter};
9use std::path::PathBuf;
10use std::sync::Arc;
11
12#[derive(Clone)]
13
14pub struct ShindenAPI {
15 pub client: Client,
16 jar: Arc<CookieStoreMutex>,
17 cookie_path: PathBuf,
18}
19
20impl ShindenAPI {
21 pub fn new() -> Result<Self> {
22 let mut cookie_path =
23 dirs::data_local_dir().context("Could not find user local data directory")?;
24 cookie_path.push("shinden_api");
25 create_dir_all(&cookie_path)?;
26 cookie_path.push("cookies.json");
27
28 let store = load_cookies(&cookie_path)?;
29 let jar = Arc::new(CookieStoreMutex::new(store));
30
31 let client = Client::builder()
32 .cookie_provider(jar.clone())
33 .redirect(reqwest::redirect::Policy::default())
34 .gzip(true)
35 .deflate(true)
36 .brotli(true)
37 .build()?;
38
39 Ok(Self {
40 client,
41 jar,
42 cookie_path,
43 })
44 }
45
46 pub async fn get_html(&self, url: &str) -> Result<String> {
47 let headers = get_headers_for_type(RequestType::Frontend, Some(url))?;
48 let response = self.client.get(url)
49 .headers(headers)
50 .send()
51 .await?
52 .error_for_status()?;
53
54 let body = response.text().await?;
55
56 save_cookies(&self.jar.lock().unwrap(), &self.cookie_path)?;
57 Ok(body)
58 }
59
60 pub async fn post_form(&self, url: &str, form: &[(String, String)], custom_headers: Option<reqwest::header::HeaderMap>) -> Result<String> {
61 let mut headers = get_headers_for_type(RequestType::Login, Some(url))?;
62
63 if let Some(ch) = custom_headers {
64 headers.extend(ch);
65 }
66
67 let response = self.client.post(url)
68 .form(form)
69 .headers(headers)
70 .send()
71 .await?
72 .error_for_status()?;
73
74 let body = response.text().await?;
75 save_cookies(&self.jar.lock().unwrap(), &self.cookie_path)?;
76 Ok(body)
77 }
78}
79
80fn load_cookies(path: &std::path::Path) -> Result<CookieStore> {
81 if path.exists() {
82 let file = File::open(path)?;
83 let reader = BufReader::new(file);
84 serde::json::load(reader).map_err(|e| anyhow::anyhow!(e.to_string()))
85 } else {
86 Ok(CookieStore::default())
87 }
88}
89
90fn save_cookies(store: &CookieStore, path: &std::path::Path) -> Result<()> {
91 let file = File::create(path)?;
92 let mut writer = BufWriter::new(file);
93
94 serde::json::save(store, &mut writer).map_err(|e| anyhow::anyhow!(e.to_string()))?;
95
96 Ok(())
97}