sauce_api/source/
saucenao.rs1use async_trait::async_trait;
2use reqwest::{header, Client};
3use serde::{Deserialize, Serialize};
4
5use crate::error::Error;
6
7use super::{Item, Output, Source};
8
9#[derive(Debug)]
14pub struct SauceNao {
15 api_key: String,
17}
18
19#[async_trait]
20impl Source for SauceNao {
21 type Argument = String;
22
23 async fn check(&self, url: &str) -> Result<Output, Error> {
24 let client = Client::new();
25
26 let head = client.head(url).send().await?;
28
29 let content_type = head.headers().get(header::CONTENT_TYPE);
30
31 if let Some(content_type) = content_type {
32 let content_type = content_type.to_str()?;
33
34 if !content_type.contains("image") {
35 return Err(Error::LinkIsNotImage);
36 }
37 } else {
38 return Err(Error::LinkIsNotImage);
39 }
40
41 let req = {
44 client
45 .get("https://saucenao.com/search.php")
46 .query(&Query::default().url(url).api_key(&self.api_key))
47 .header(header::ACCEPT_ENCODING, "utf-8")
48 };
49
50 let resp = req.send().await?;
53
54 let text = resp.text().await?;
57 let json: ApiResponse = serde_json::from_str(&text)?;
58
59 let mut result = Output {
60 original_url: url.to_string(),
61 items: Vec::new(),
62 };
63
64 for item in json.results {
65 if let Some(links) = item.data.ext_urls {
66 let item = Item {
67 similarity: item.header.similarity.parse::<f32>()?,
68 link: links[0].clone(),
69 };
70
71 result.items.push(item);
72 }
73 }
74
75 Ok(result)
76 }
77
78 async fn create(arg: Self::Argument) -> Result<Self, Error> {
79 Ok(Self { api_key: arg })
80 }
81}
82
83#[derive(Debug, Serialize)]
84struct Query {
85 url: String,
86 api_key: String,
87 db: u16,
88 output_type: u8,
89 #[serde(rename = "testmode")]
90 test_mode: u8,
91 #[serde(rename = "numres")]
92 num_res: u8,
93}
94
95impl Query {
96 pub fn url(mut self, url: &str) -> Self {
97 self.url = url.to_string();
98 self
99 }
100
101 pub fn api_key(mut self, api_key: &str) -> Self {
102 self.api_key = api_key.to_string();
103 self
104 }
105}
106
107impl Default for Query {
108 fn default() -> Self {
109 Self {
110 url: String::new(),
111 api_key: String::new(),
112 db: 999,
113 output_type: 2,
114 test_mode: 1,
115 num_res: 16,
116 }
117 }
118}
119
120#[derive(Debug, Deserialize)]
121struct ApiResponse {
122 results: Vec<ApiItem>,
123}
124
125#[derive(Debug, Deserialize)]
126struct ApiItem {
127 header: ApiItemHeader,
128 data: ApiItemData,
129}
130
131#[derive(Debug, Deserialize)]
132struct ApiItemHeader {
133 similarity: String,
134}
135
136#[derive(Debug, Deserialize)]
137struct ApiItemData {
138 ext_urls: Option<Vec<String>>,
139}