1use crate::{Error, Message, Result};
4use regex::Regex;
5use reqwest::header::{
6 HeaderMap, HeaderValue, ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, HOST, ORIGIN, REFERER,
7 USER_AGENT,
8};
9use std::time::{SystemTime, UNIX_EPOCH};
10
11#[derive(Debug)]
13pub struct Client {
14 http: reqwest::Client,
15 api_token: String,
16 domains: Vec<String>,
17 proxy: Option<String>,
18 user_agent: String,
19 ajax_url: String,
20}
21
22impl Client {
23 pub fn builder() -> ClientBuilder {
25 ClientBuilder::new()
26 }
27
28 pub async fn new() -> Result<Self> {
32 ClientBuilder::new().build().await
33 }
34
35 pub async fn with_proxy(proxy: Option<&str>) -> Result<Self> {
40 let mut builder = ClientBuilder::new();
41 if let Some(proxy_url) = proxy {
42 builder = builder.proxy(proxy_url);
43 }
44 builder.build().await
45 }
46
47 pub fn domains(&self) -> &[String] {
49 &self.domains
50 }
51
52 pub fn proxy(&self) -> Option<&str> {
54 self.proxy.as_deref()
55 }
56
57 pub async fn create_email(&self, alias: &str) -> Result<String> {
65 let params = [("f", "set_email_user")];
66 let form = [
67 ("email_user", alias),
68 ("lang", "en"),
69 ("site", "guerrillamail.com"),
70 ("in", " Set cancel"),
71 ];
72
73 let response: serde_json::Value = self
74 .http
75 .post(&self.ajax_url)
76 .query(¶ms)
77 .form(&form)
78 .headers(self.headers())
79 .send()
80 .await?
81 .error_for_status()?
82 .json()
83 .await?;
84
85 response
86 .get("email_addr")
87 .and_then(|v| v.as_str())
88 .map(|s| s.to_string())
89 .ok_or(Error::TokenParse)
90 }
91
92 pub async fn get_messages(&self, email: &str) -> Result<Vec<Message>> {
100 let response = self.get_api("check_email", email, None).await?;
101
102 let messages = response
103 .get("list")
104 .and_then(|v| v.as_array())
105 .map(|arr| {
106 arr.iter()
107 .filter_map(|v| serde_json::from_value::<Message>(v.clone()).ok())
108 .collect()
109 })
110 .unwrap_or_default();
111
112 Ok(messages)
113 }
114
115 pub async fn fetch_email(&self, email: &str, mail_id: &str) -> Result<crate::EmailDetails> {
124 let response = self.get_api("fetch_email", email, Some(mail_id)).await?;
125 serde_json::from_value(response).map_err(|_| Error::TokenParse)
126 }
127
128 pub async fn delete_email(&self, email: &str) -> Result<bool> {
136 let alias = Self::extract_alias(email);
137 let params = [("f", "forget_me")];
138 let form = [("site", "guerrillamail.com"), ("in", alias)];
139
140 let response = self
141 .http
142 .post(&self.ajax_url)
143 .query(¶ms)
144 .form(&form)
145 .headers(self.headers())
146 .send()
147 .await?;
148
149 Ok(response.status().is_success())
150 }
151
152 async fn get_api(
154 &self,
155 function: &str,
156 email: &str,
157 email_id: Option<&str>,
158 ) -> Result<serde_json::Value> {
159 let alias = Self::extract_alias(email);
160 let timestamp = Self::timestamp();
161
162 let mut params = vec![
163 ("f", function.to_string()),
164 ("site", "guerrillamail.com".to_string()),
165 ("in", alias.to_string()),
166 ("_", timestamp),
167 ];
168
169 if let Some(id) = email_id {
170 params.insert(1, ("email_id", id.to_string()));
171 }
172
173 if function == "check_email" {
174 params.insert(1, ("seq", "1".to_string()));
175 }
176
177 let mut headers = self.headers();
178 headers.remove(CONTENT_TYPE);
179
180 self.http
181 .get(&self.ajax_url)
182 .query(¶ms)
183 .headers(headers)
184 .send()
185 .await?
186 .error_for_status()?
187 .json()
188 .await
189 .map_err(Into::into)
190 }
191
192 fn extract_alias(email: &str) -> &str {
194 email.split('@').next().unwrap_or(email)
195 }
196
197 fn timestamp() -> String {
199 SystemTime::now()
200 .duration_since(UNIX_EPOCH)
201 .unwrap()
202 .as_millis()
203 .to_string()
204 }
205
206 fn headers(&self) -> HeaderMap {
208 let mut headers = HeaderMap::new();
209 headers.insert(HOST, HeaderValue::from_static("www.guerrillamail.com"));
210 if let Ok(value) = HeaderValue::from_str(&self.user_agent) {
211 headers.insert(USER_AGENT, value);
212 }
213 headers.insert(
214 ACCEPT,
215 HeaderValue::from_static("application/json, text/javascript, */*; q=0.01"),
216 );
217 headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.5"));
218 headers.insert(
219 CONTENT_TYPE,
220 HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8"),
221 );
222 headers.insert(
223 "Authorization",
224 HeaderValue::from_str(&format!("ApiToken {}", self.api_token)).unwrap(),
225 );
226 headers.insert(
227 "X-Requested-With",
228 HeaderValue::from_static("XMLHttpRequest"),
229 );
230 headers.insert(
231 ORIGIN,
232 HeaderValue::from_static("https://www.guerrillamail.com"),
233 );
234 headers.insert(
235 REFERER,
236 HeaderValue::from_static("https://www.guerrillamail.com/"),
237 );
238 headers.insert("Sec-Fetch-Dest", HeaderValue::from_static("empty"));
239 headers.insert("Sec-Fetch-Mode", HeaderValue::from_static("cors"));
240 headers.insert("Sec-Fetch-Site", HeaderValue::from_static("same-origin"));
241 headers.insert("Priority", HeaderValue::from_static("u=0"));
242 headers
243 }
244}
245
246const BASE_URL: &str = "https://www.guerrillamail.com";
247const AJAX_URL: &str = "https://www.guerrillamail.com/ajax.php";
248const USER_AGENT_VALUE: &str =
249 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0";
250
251#[derive(Debug, Clone)]
253pub struct ClientBuilder {
254 proxy: Option<String>,
255 danger_accept_invalid_certs: bool,
256 user_agent: String,
257 ajax_url: String,
258}
259
260impl ClientBuilder {
261 pub fn new() -> Self {
263 Self {
264 proxy: None,
265 danger_accept_invalid_certs: true,
266 user_agent: USER_AGENT_VALUE.to_string(),
267 ajax_url: AJAX_URL.to_string(),
268 }
269 }
270
271 pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
273 self.proxy = Some(proxy.into());
274 self
275 }
276
277 pub fn danger_accept_invalid_certs(mut self, value: bool) -> Self {
279 self.danger_accept_invalid_certs = value;
280 self
281 }
282
283 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
285 self.user_agent = user_agent.into();
286 self
287 }
288
289 pub fn ajax_url(mut self, ajax_url: impl Into<String>) -> Self {
291 self.ajax_url = ajax_url.into();
292 self
293 }
294
295 pub async fn build(self) -> Result<Client> {
297 let mut builder =
298 reqwest::Client::builder().danger_accept_invalid_certs(self.danger_accept_invalid_certs);
299
300 if let Some(proxy_url) = &self.proxy {
301 builder = builder.proxy(reqwest::Proxy::all(proxy_url)?);
302 }
303
304 let http = builder.cookie_store(true).build()?;
306
307 let response = http.get(BASE_URL).send().await?.text().await?;
309
310 let token_re = Regex::new(r"api_token\s*:\s*'(\w+)'").unwrap();
312 let api_token = token_re
313 .captures(&response)
314 .and_then(|c| c.get(1))
315 .map(|m| m.as_str().to_string())
316 .ok_or(Error::TokenParse)?;
317
318 let domain_re = Regex::new(r#"<option value="([\w.]+)">"#).unwrap();
320 let domains: Vec<String> = domain_re
321 .captures_iter(&response)
322 .filter_map(|c| c.get(1).map(|m| m.as_str().to_string()))
323 .collect();
324
325 if domains.is_empty() {
326 return Err(Error::DomainParse);
327 }
328
329 Ok(Client {
330 http,
331 api_token,
332 domains,
333 proxy: self.proxy,
334 user_agent: self.user_agent,
335 ajax_url: self.ajax_url,
336 })
337 }
338}