dehashed_rs/
api.rs

1use std::fmt::{Display, Formatter, Write};
2use std::time::Duration;
3
4use reqwest::header::{HeaderMap, HeaderValue};
5use reqwest::{Client, StatusCode};
6use serde::{Deserialize, Serialize};
7use serde_json::json;
8#[cfg(feature = "tokio")]
9use tokio::time::sleep;
10use tracing::{debug, error, warn};
11
12#[cfg(feature = "tokio")]
13use crate::Scheduler;
14use crate::error::DehashedError;
15use crate::res::{Entry, Response};
16
17const URL: &str = "https://api.dehashed.com/v2/search";
18const RESERVED: [char; 21] = [
19    '+', '-', '=', '&', '|', '>', '<', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?',
20    ':', '\\',
21];
22
23fn escape(q: &str) -> String {
24    let mut s = String::new();
25    for c in q.chars() {
26        if RESERVED.contains(&c) {
27            s.write_str(&format!("\\{c}")).unwrap();
28        } else {
29            s.write_char(c).unwrap();
30        }
31    }
32    s
33}
34
35/// A specific search type
36#[derive(Clone, Debug, Serialize, Deserialize)]
37#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
38#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
39pub enum SearchType {
40    /// Search for a simple pattern
41    Simple(String),
42    /// Search for an exact pattern
43    Exact(String),
44    /// A regex search pattern
45    Regex(String),
46    /// Add multiple [SearchType]s with an OR
47    Or(Vec<SearchType>),
48    /// Add multiple [SearchType]s with an AND
49    And(Vec<SearchType>),
50}
51
52impl Display for SearchType {
53    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54        write!(
55            f,
56            "{}",
57            match self {
58                SearchType::Simple(x) => x.clone(),
59                SearchType::Exact(x) => format!("\"{}\"", escape(x)),
60                SearchType::Regex(x) => format!("/{}/", escape(x)),
61                SearchType::Or(x) => x
62                    .iter()
63                    .map(|x| x.to_string())
64                    .collect::<Vec<_>>()
65                    .join(" OR "),
66                SearchType::And(x) => x
67                    .iter()
68                    .map(|x| x.to_string())
69                    .collect::<Vec<_>>()
70                    .join(" "),
71            }
72        )
73    }
74}
75
76/// A query for dehashed
77#[derive(Clone, Debug, Serialize, Deserialize)]
78#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
79#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
80pub enum Query {
81    /// Search for an email
82    Email(SearchType),
83    /// Search for an ip address
84    IpAddress(SearchType),
85    /// Search for an username
86    Username(SearchType),
87    /// Search for an password
88    Password(SearchType),
89    /// Search for an hashed password
90    HashedPassword(SearchType),
91    /// Search for a name
92    Name(SearchType),
93    /// Search for a domain
94    Domain(SearchType),
95    /// Search for a vin
96    Vin(SearchType),
97    /// Search for a phone
98    Phone(SearchType),
99    /// Search for an address
100    Address(SearchType),
101}
102
103impl Display for Query {
104    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
105        write!(
106            f,
107            "{}",
108            match self {
109                Query::Email(x) => format!("email:{x}"),
110                Query::IpAddress(x) => format!("ip_address:{x}"),
111                Query::Username(x) => format!("username:{x}"),
112                Query::Password(x) => format!("password:{x}"),
113                Query::HashedPassword(x) => format!("hashed_password:{x}"),
114                Query::Name(x) => format!("name:{x}"),
115                Query::Domain(x) => format!("domain:{x}"),
116                Query::Vin(x) => format!("vin:{x}"),
117                Query::Phone(x) => format!("phone:{x}"),
118                Query::Address(x) => format!("address:{x}"),
119            }
120        )
121    }
122}
123
124/// The result of a search query
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
127#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
128pub struct SearchResult {
129    /// A list of results
130    pub entries: Vec<Entry>,
131    /// The remaining balance
132    pub balance: usize,
133}
134
135/// The instance of the dehashed api
136#[derive(Clone, Debug)]
137pub struct DehashedApi {
138    client: Client,
139}
140
141impl DehashedApi {
142    /// Create a new instance of the SDK.
143    ///
144    /// **Parameter**:
145    /// - `email`: The mail address that is used for authentication
146    /// - `api_key`: The api key for your account (found on your profile page)
147    ///
148    /// This method fails if the [Client] could not be constructed
149    pub fn new(api_key: String) -> Result<Self, DehashedError> {
150        let mut header_map = HeaderMap::new();
151        header_map.insert("Accept", HeaderValue::from_static("application/json"));
152        header_map.insert("Dehashed-Api-Key", HeaderValue::from_str(&api_key)?);
153
154        let client = Client::builder()
155            .timeout(Duration::from_secs(10))
156            .https_only(true)
157            .default_headers(header_map)
158            .build()?;
159
160        Ok(Self { client })
161    }
162
163    async fn raw_req(
164        &self,
165        size: usize,
166        page: usize,
167        query: String,
168    ) -> Result<Response, DehashedError> {
169        let res = self
170            .client
171            .post(URL)
172            .json(&json!({"query": query, "size": size, "page": page}))
173            .send()
174            .await?;
175
176        let status = res.status();
177        let raw = res.text().await?;
178        debug!("status code: {status}. Raw: {raw}");
179        if status == StatusCode::from_u16(302).unwrap() {
180            Err(DehashedError::InvalidQuery)
181        } else if status == StatusCode::from_u16(400).unwrap() {
182            Err(DehashedError::Unknown(raw))
183        } else if status == StatusCode::from_u16(401).unwrap() {
184            Err(DehashedError::Unauthorized)
185        } else if status == StatusCode::from_u16(200).unwrap() {
186            match serde_json::from_str(&raw) {
187                Ok(result) => Ok(result),
188                Err(err) => {
189                    error!("Error deserializing data: {err}. Raw data: {raw}");
190                    Err(DehashedError::Unknown(raw))
191                }
192            }
193        } else {
194            warn!("Invalid response, status code: {status}. Raw: {raw}");
195
196            Err(DehashedError::Unknown(raw))
197        }
198    }
199
200    /// Query the API
201    ///
202    /// Please note, that dehashed has a ratelimit protection active, that bans every account
203    /// that is doing more than 5 req / s.
204    ///
205    /// This method will take care of pagination and will delay requests if necessary.
206    pub async fn search(&self, query: Query) -> Result<SearchResult, DehashedError> {
207        let q = query.to_string();
208        debug!("Query: {q}");
209
210        let mut search_result = SearchResult {
211            entries: vec![],
212            balance: 0,
213        };
214        for page in 1.. {
215            let res = self.raw_req(10_000, page, q.clone()).await?;
216
217            if let Some(entries) = res.entries {
218                for entry in entries {
219                    search_result.entries.push(entry)
220                }
221            }
222
223            search_result.balance = res.balance;
224
225            if res.total < page * 10_000 {
226                break;
227            }
228
229            #[cfg(feature = "tokio")]
230            sleep(Duration::from_millis(200)).await;
231        }
232
233        Ok(search_result)
234    }
235
236    /// Start a new scheduler.
237    ///
238    /// The [Scheduler] manages stay in bounds of the rate limit of the unhashed API.
239    /// It lets you push queries and receive the results.
240    #[cfg(feature = "tokio")]
241    pub fn start_scheduler(&self) -> Scheduler {
242        Scheduler::new(self)
243    }
244}