1use std::sync::Arc;
2use std::time::Duration;
3
4use wreq::Client;
5use wreq::cookie::Jar;
6use wreq_util::Emulation;
7
8use crate::error::{self, FlightError};
9
10fn cache_buster() -> String {
11 use std::time::{SystemTime, UNIX_EPOCH};
12 SystemTime::now()
13 .duration_since(UNIX_EPOCH)
14 .unwrap()
15 .as_secs()
16 .to_string()
17}
18
19const BASE_URL: &str = "https://www.google.com/travel/flights";
20
21#[derive(Clone)]
22pub struct FetchOptions {
23 pub proxy: Option<String>,
24 pub timeout: u64,
25}
26
27impl Default for FetchOptions {
28 fn default() -> Self {
29 Self {
30 proxy: None,
31 timeout: 30,
32 }
33 }
34}
35
36pub async fn fetch_html(
37 params: &[(String, String)],
38 options: &FetchOptions,
39) -> Result<String, FlightError> {
40 let jar = Arc::new(Jar::default());
41 let url: wreq::Uri = "https://www.google.com".parse().unwrap();
42 jar.add(
43 "SOCS=CAESEwgDEgk2MjA5NDM1NjAaAmVuIAEaBgiA_Le-Bg",
44 &url,
45 );
46 jar.add("CONSENT=PENDING+987", &url);
47
48 let mut builder = Client::builder()
49 .emulation(Emulation::Chrome137)
50 .cookie_provider(jar)
51 .timeout(Duration::from_secs(options.timeout));
52
53 if let Some(ref proxy) = options.proxy {
54 builder = builder.proxy(
55 wreq::Proxy::all(proxy).map_err(error::from_http_error)?,
56 );
57 }
58
59 let client = builder.build().map_err(error::from_http_error)?;
60
61 let mut params = params.to_vec();
62 params.push(("cx".to_string(), cache_buster()));
63
64 let response = client
65 .get(BASE_URL)
66 .query(¶ms)
67 .send()
68 .await
69 .map_err(error::from_http_error)?;
70
71 let status = response.status().as_u16();
72 match status {
73 200 => {}
74 429 => return Err(FlightError::RateLimited),
75 403 | 503 => return Err(FlightError::Blocked(status)),
76 _ if status >= 400 => return Err(FlightError::HttpStatus(status)),
77 _ => {}
78 }
79
80 response.text().await.map_err(error::from_http_error)
81}