rusty_booru/danbooru/
client.rs

1use derive_more::From;
2use reqwest::{header, header::HeaderMap, Response};
3
4use super::*;
5use crate::{
6    generic::AutoCompleteItem,
7    shared::{self, client::*},
8};
9
10// This is only here because of Danbooru, thanks Danbooru, really cool :)
11pub fn get_headers() -> HeaderMap {
12    let mut headers = header::HeaderMap::new();
13    headers.insert(
14        header::USER_AGENT,
15        header::HeaderValue::from_static("PostmanRuntime/7.30.0"),
16    );
17    headers
18}
19
20/// Client that sends requests to the Danbooru API to retrieve the data.
21#[derive(From, Debug, Clone)]
22pub struct DanbooruClient(pub ClientBuilder<Self>);
23
24impl ClientInformation for DanbooruClient {
25    const URL: &'static str = "https://danbooru.donmai.us";
26    const SORT: &'static str = "order:";
27}
28
29impl ClientTypes for DanbooruClient {
30    type Rating = DanbooruRating;
31    type Post = DanbooruPost;
32}
33
34#[derive(Deserialize, Debug, thiserror::Error, Display)]
35pub enum DanbooruError {
36    #[serde(rename = "PostQuery::TagLimitError")]
37    TagLimitError,
38}
39
40#[derive(Deserialize)]
41struct DanbooruErrorStruct {
42    pub error: DanbooruError,
43}
44
45impl From<DanbooruErrorStruct> for shared::Error {
46    fn from(value: DanbooruErrorStruct) -> Self {
47        value.error.into()
48    }
49}
50
51async fn send_error<T>(response: Response) -> Result<T, shared::Error> {
52    Err(response
53        .json::<DanbooruErrorStruct>()
54        .await
55        .map(Into::into)?)
56}
57
58impl QueryDispatcher<DanbooruClient> for ClientQueryDispatcher<DanbooruClient> {
59    async fn get_autocomplete<In: Into<String> + Send>(
60        &self,
61        input: In,
62    ) -> Result<Vec<AutoCompleteItem>, reqwest::Error> {
63        self.builder
64            .client
65            .get(format!("{}/autocomplete.json", self.builder.url))
66            .headers(get_headers())
67            .query(&[
68                ("limit", self.query.limit.to_string().as_str()),
69                ("search[type]", "tag_query"),
70                ("search[query]", &input.into()),
71                ("version", "1"),
72            ])
73            .send()
74            .await?
75            .json::<Vec<AutoCompleteItem>>()
76            .await
77    }
78
79    async fn get_by_id(&self, id: u32) -> Result<Option<DanbooruPost>, shared::Error> {
80        let response = self
81            .builder
82            .client
83            .get(format!("{}/posts/{id}.json", self.builder.url))
84            .headers(get_headers())
85            .send()
86            .await?;
87
88        if response.status().is_success() {
89            response
90                .json::<DanbooruPost>()
91                .await
92                .map(Into::into)
93                .map_err(Into::into)
94        } else {
95            send_error(response).await?
96        }
97    }
98
99    async fn get(&self) -> Result<Vec<DanbooruPost>, shared::Error> {
100        let response = self
101            .builder
102            .client
103            .get(format!("{}/posts.json", self.builder.url))
104            .headers(get_headers())
105            .query(&[
106                ("limit", &self.query.limit.to_string()),
107                ("tags", &self.query.tags.unpack()),
108            ])
109            .send()
110            .await?;
111
112        if response.status().is_success() {
113            Ok(response.json::<Vec<DanbooruPost>>().await?)
114        } else {
115            send_error(response).await?
116        }
117    }
118}