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 proxy: Option<String>,
17 user_agent: String,
18 ajax_url: String,
19}
20
21impl Client {
22 pub fn builder() -> ClientBuilder {
24 ClientBuilder::new()
25 }
26
27 pub async fn new() -> Result<Self> {
31 ClientBuilder::new().build().await
32 }
33
34 pub fn proxy(&self) -> Option<&str> {
36 self.proxy.as_deref()
37 }
38
39 pub async fn create_email(&self, alias: &str) -> Result<String> {
47 let params = [("f", "set_email_user")];
48 let form = [
49 ("email_user", alias),
50 ("lang", "en"),
51 ("site", "guerrillamail.com"),
52 ("in", " Set cancel"),
53 ];
54
55 let response: serde_json::Value = self
56 .http
57 .post(&self.ajax_url)
58 .query(¶ms)
59 .form(&form)
60 .headers(self.headers())
61 .send()
62 .await?
63 .error_for_status()?
64 .json()
65 .await?;
66
67 response
68 .get("email_addr")
69 .and_then(|v| v.as_str())
70 .map(|s| s.to_string())
71 .ok_or(Error::TokenParse)
72 }
73
74 pub async fn get_messages(&self, email: &str) -> Result<Vec<Message>> {
82 let response = self.get_api("check_email", email, None).await?;
83
84 let messages = response
85 .get("list")
86 .and_then(|v| v.as_array())
87 .map(|arr| {
88 arr.iter()
89 .filter_map(|v| serde_json::from_value::<Message>(v.clone()).ok())
90 .collect()
91 })
92 .unwrap_or_default();
93
94 Ok(messages)
95 }
96
97 pub async fn fetch_email(&self, email: &str, mail_id: &str) -> Result<crate::EmailDetails> {
106 let response = self.get_api("fetch_email", email, Some(mail_id)).await?;
107 serde_json::from_value(response).map_err(|_| Error::TokenParse)
108 }
109
110 pub async fn delete_email(&self, email: &str) -> Result<bool> {
118 let alias = Self::extract_alias(email);
119 let params = [("f", "forget_me")];
120 let form = [("site", "guerrillamail.com"), ("in", alias)];
121
122 let response = self
123 .http
124 .post(&self.ajax_url)
125 .query(¶ms)
126 .form(&form)
127 .headers(self.headers())
128 .send()
129 .await?;
130
131 Ok(response.status().is_success())
132 }
133
134 async fn get_api(
136 &self,
137 function: &str,
138 email: &str,
139 email_id: Option<&str>,
140 ) -> Result<serde_json::Value> {
141 let alias = Self::extract_alias(email);
142 let timestamp = Self::timestamp();
143
144 let mut params = vec![
145 ("f", function.to_string()),
146 ("site", "guerrillamail.com".to_string()),
147 ("in", alias.to_string()),
148 ("_", timestamp),
149 ];
150
151 if let Some(id) = email_id {
152 params.insert(1, ("email_id", id.to_string()));
153 }
154
155 if function == "check_email" {
156 params.insert(1, ("seq", "1".to_string()));
157 }
158
159 let mut headers = self.headers();
160 headers.remove(CONTENT_TYPE);
161
162 self.http
163 .get(&self.ajax_url)
164 .query(¶ms)
165 .headers(headers)
166 .send()
167 .await?
168 .error_for_status()?
169 .json()
170 .await
171 .map_err(Into::into)
172 }
173
174 fn extract_alias(email: &str) -> &str {
176 email.split('@').next().unwrap_or(email)
177 }
178
179 fn timestamp() -> String {
181 SystemTime::now()
182 .duration_since(UNIX_EPOCH)
183 .unwrap()
184 .as_millis()
185 .to_string()
186 }
187
188 fn headers(&self) -> HeaderMap {
190 let mut headers = HeaderMap::new();
191 headers.insert(HOST, HeaderValue::from_static("www.guerrillamail.com"));
192 if let Ok(value) = HeaderValue::from_str(&self.user_agent) {
193 headers.insert(USER_AGENT, value);
194 }
195 headers.insert(
196 ACCEPT,
197 HeaderValue::from_static("application/json, text/javascript, */*; q=0.01"),
198 );
199 headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.5"));
200 headers.insert(
201 CONTENT_TYPE,
202 HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8"),
203 );
204 headers.insert(
205 "Authorization",
206 HeaderValue::from_str(&format!("ApiToken {}", self.api_token)).unwrap(),
207 );
208 headers.insert(
209 "X-Requested-With",
210 HeaderValue::from_static("XMLHttpRequest"),
211 );
212 headers.insert(
213 ORIGIN,
214 HeaderValue::from_static("https://www.guerrillamail.com"),
215 );
216 headers.insert(
217 REFERER,
218 HeaderValue::from_static("https://www.guerrillamail.com/"),
219 );
220 headers.insert("Sec-Fetch-Dest", HeaderValue::from_static("empty"));
221 headers.insert("Sec-Fetch-Mode", HeaderValue::from_static("cors"));
222 headers.insert("Sec-Fetch-Site", HeaderValue::from_static("same-origin"));
223 headers.insert("Priority", HeaderValue::from_static("u=0"));
224 headers
225 }
226}
227
228const BASE_URL: &str = "https://www.guerrillamail.com";
229const AJAX_URL: &str = "https://www.guerrillamail.com/ajax.php";
230const USER_AGENT_VALUE: &str =
231 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0";
232
233#[derive(Debug, Clone)]
235pub struct ClientBuilder {
236 proxy: Option<String>,
237 danger_accept_invalid_certs: bool,
238 user_agent: String,
239 ajax_url: String,
240}
241
242impl ClientBuilder {
243 pub fn new() -> Self {
245 Self {
246 proxy: None,
247 danger_accept_invalid_certs: true,
248 user_agent: USER_AGENT_VALUE.to_string(),
249 ajax_url: AJAX_URL.to_string(),
250 }
251 }
252
253 pub fn proxy(mut self, proxy: impl Into<String>) -> Self {
255 self.proxy = Some(proxy.into());
256 self
257 }
258
259 pub fn danger_accept_invalid_certs(mut self, value: bool) -> Self {
261 self.danger_accept_invalid_certs = value;
262 self
263 }
264
265 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
267 self.user_agent = user_agent.into();
268 self
269 }
270
271 pub fn ajax_url(mut self, ajax_url: impl Into<String>) -> Self {
273 self.ajax_url = ajax_url.into();
274 self
275 }
276
277 pub async fn build(self) -> Result<Client> {
279 let mut builder =
280 reqwest::Client::builder().danger_accept_invalid_certs(self.danger_accept_invalid_certs);
281
282 if let Some(proxy_url) = &self.proxy {
283 builder = builder.proxy(reqwest::Proxy::all(proxy_url)?);
284 }
285
286 let http = builder.cookie_store(true).build()?;
288
289 let response = http.get(BASE_URL).send().await?.text().await?;
291
292 let token_re = Regex::new(r"api_token\s*:\s*'(\w+)'").unwrap();
294 let api_token = token_re
295 .captures(&response)
296 .and_then(|c| c.get(1))
297 .map(|m| m.as_str().to_string())
298 .ok_or(Error::TokenParse)?;
299
300 Ok(Client {
301 http,
302 api_token,
303 proxy: self.proxy,
304 user_agent: self.user_agent,
305 ajax_url: self.ajax_url,
306 })
307 }
308}