1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use crate::{
    Error,
    Image,
    SearchResults,
    MAX_FILE_SIZE,
};
use scraper::Html;

/// Client
#[derive(Clone, Debug)]
pub struct Client {
    client: reqwest::Client,
}

impl Client {
    /// Make a new [`Client`].
    pub fn new() -> Self {
        Self {
            client: reqwest::Client::builder()
                .user_agent("iqdb-rs")
                .build()
                .expect("failed to build iqdb client"),
        }
    }

    /// Look up an image.
    pub async fn search(&self, image: impl Into<Image>) -> Result<SearchResults, Error> {
        let mut form =
            reqwest::multipart::Form::new().text("MAX_FILE_SIZE", MAX_FILE_SIZE.to_string());

        match image.into() {
            Image::Url(url) => {
                form = form.text("url", url);
            }
            Image::File { name, body } => {
                let part = reqwest::multipart::Part::stream(body).file_name(name);
                form = form.part("file", part);
            }
        }

        let text = self
            .client
            .post("http://iqdb.org/")
            .multipart(form)
            .send()
            .await?
            .error_for_status()?
            .text()
            .await?;

        let results = tokio::task::spawn_blocking(move || {
            let html = Html::parse_document(&text);
            SearchResults::from_html(&html)
        })
        .await??;

        Ok(results)
    }
}

impl Default for Client {
    fn default() -> Self {
        Self::new()
    }
}