1use super::models::{
2 anime::Anime,
3 common::{ApiError, ResponseError},
4};
5use async_trait::async_trait;
6use governor::Quota;
7use reqwest::Method;
8use reqwest_middleware::ClientBuilder;
9use reqwest_tracing::TracingMiddleware;
10use std::time::Duration;
11
12const API_URL: &str = "http://api.anidb.net:9001/httpapi";
13const CLIENT_NAME: &str = "anidbapirs";
14const CLIENT_VER: usize = 1;
15const HTTP_PROTO_VER: usize = 1;
16
17#[derive(Clone)]
18pub struct AniDbHttpClient {
19 base_url: reqwest::Url,
20 client: reqwest_middleware::ClientWithMiddleware,
21}
22
23struct MyRateLimiter {
24 limiter: governor::DefaultDirectRateLimiter,
25}
26
27#[async_trait]
28impl reqwest_ratelimit::RateLimiter for MyRateLimiter {
29 async fn acquire_permit(&self) {
30 self.limiter.until_ready().await;
31 }
32}
33
34impl AniDbHttpClient {
35 pub fn new() -> Result<Self, ApiError> {
39 let limit = Duration::from_secs(2);
40 Self::with_ratelimit(limit)
41 }
42
43 pub fn with_ratelimit(limit: Duration) -> Result<Self, ApiError> {
48 let base_url = format!(
49 "{API_URL}?client={CLIENT_NAME}&clientver={CLIENT_VER}&protover={HTTP_PROTO_VER}"
50 );
51 let base_url = reqwest::Url::parse(&base_url).map_err(ApiError::UrlParse)?;
52
53 let req_client = reqwest::Client::builder()
54 .gzip(true)
55 .build()
56 .map_err(|e| ApiError::Reqwest(e.into()))?;
57
58 let client = if let Some(quota) = Quota::with_period(limit) {
59 let rate_limiter = MyRateLimiter {
60 limiter: governor::RateLimiter::direct(quota),
61 };
62 ClientBuilder::new(req_client).with(reqwest_ratelimit::all(rate_limiter))
63 } else {
64 ClientBuilder::new(req_client)
65 };
66
67 let client = client.with(TracingMiddleware::default()).build();
68 Ok(Self { base_url, client })
69 }
70
71 pub async fn get_anime(&self, anime_id: &str) -> Result<Anime, ApiError> {
75 let params = [("request", "anime"), ("aid", anime_id)];
76 let url = reqwest::Url::parse_with_params(self.base_url.as_str(), ¶ms)
77 .map_err(ApiError::UrlParse)?;
78 let request = self.client.request(Method::GET, url);
79 let response = request.send().await.map_err(ApiError::Reqwest)?;
80
81 let body_text = response
84 .text()
85 .await
86 .map_err(|e| ApiError::Reqwest(e.into()))?;
87
88 let anidb_error = serde_xml_rs::from_str::<ResponseError>(&body_text);
89
90 if let Ok(err) = anidb_error {
91 Err(ApiError::HttpError {
92 status: err.status.unwrap_or_default(),
93 message: err.text.unwrap_or("Empty error message".to_string()),
94 })
95 } else {
96 Ok(serde_xml_rs::from_str::<Anime>(&body_text).map_err(ApiError::Deserialize)?)
97 }
98 }
99}