1 use std
::{ thread
, time
, fs
::File
};
2 use scraper
::{ Html
, Selector
};
9 ConnectionReuseParameters
,
16 use lettre_email
::Email
;
17 use native_tls
::{ Protocol
, TlsConnector
};
18 use ron
::{ de
::from_reader
, ser
::to_writer
};
19 use serde
::{ Deserialize
, Serialize
};
21 type Result
<T
> = std
::result
::Result
<T
, Box
<dyn std
::error
::Error
>>;
23 #[derive(Debug, Deserialize, Serialize)]
26 smtp_password
: String
,
30 fn default() -> Self {
31 Config
{ smtp_login
: "login".to_string(), smtp_password
: "password".to_string() }
34 fn read(file_path
: &str) -> Result
<Config
> {
35 match File
::open(file_path
) {
36 Ok(file
) => from_reader(file
).map_err(|e
| e
.into()),
38 let file
= File
::create(file_path
)?
;
39 let default_config
= Config
::default();
40 to_writer(file
, &default_config
)?
;
47 const FILE_CONF
: &str = "config.ron";
48 const PULL_PERIOD
: time
::Duration
= time
::Duration
::from_secs(60); // 1 min.
50 fn main() -> Result
<()> {
51 println!("I need a RTX 3080 right now :)");
53 let to_find
= "RTX 3080";
54 let to_match
= "3080";
56 let config
= Config
::read(FILE_CONF
)?
;
58 let selector
= Selector
::parse("div.productGridElement > h2 > a:nth-child(1)").unwrap();
59 let url
= format!("https://www.steg-electronics.ch/fr/search?suche={}", to_find
);
61 let client
= reqwest
::blocking
::Client
::new();
64 let time_beginning_loop
= time
::Instant
::now();
65 println!("Request: {}", url
);
67 match client
.get(&url
).send() {
69 if resp
.status().is_success() {
70 let html
= resp
.text()?
;
71 let document
= Html
::parse_document(&html
);
73 // A vector of (<title>, <url>), empty if nothing matches.
74 let match_items
: Vec
<(String
, String
)> =
75 document
.select(&selector
).filter_map(
77 if let (Some(title
), Some(url_to_item
)) = (element
.value().attr("title"), element
.value().attr("href")) {
78 if title
.find(to_match
).is_some() {
79 return Some((String
::from(title
), String
::from(url_to_item
)))
86 if match_items
.is_empty() {
87 println!("No matches 😿");
88 } else if send_email(&match_items
[..], &config
.smtp_login
, &config
.smtp_password
) {
89 println!("Some matches has been found 😺! An e-mail has been sent!");
92 println!("Unable to send e-mail");
95 println!("Request unsuccessful:\n{:#?}", resp
);
98 println!("Error during request: {:?}", error
)
101 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
103 if elapsed
< PULL_PERIOD
{
104 let to_wait
= PULL_PERIOD
- elapsed
;
105 thread
::sleep(to_wait
);
110 /// Return 'true' if the email has been successfully sent.
111 fn send_email(items
: &[(String
, String
)], login
: &str, password
: &str) -> bool
{
113 let items_list_html
=
116 |acc
, (title
, url
)| {
117 format!("{}\n<li><a href=\"{}\">{}</a></li>", acc
, url
, title
)
121 let email
= Email
::builder()
122 // Addresses can be specified by the tuple (email, alias)
123 .to(("greg.burri@gmail.com", "Greg Burri"))
124 .from("redmine@d-lan.net")
125 .subject("RTX 3080 AVAILABLE!")
126 .html(format!("<ul>{}</ul>", items_list_html
))
131 let mut tls_builder
= TlsConnector
::builder();
132 tls_builder
.min_protocol_version(Some(Protocol
::Tlsv10
));
134 ClientTlsParameters
::new(
135 "mail.gandi.net".to_string(),
136 tls_builder
.build().unwrap()
140 SmtpClient
::new(("mail.gandi.net", 465), ClientSecurity
::Wrapper(tls_parameters
)).unwrap()
141 .authentication_mechanism(Mechanism
::Login
)
143 .credentials(Credentials
::new(String
::from(login
), String
::from(password
)))
144 .connection_reuse(ConnectionReuseParameters
::ReuseUnlimited
)
147 let result
= mailer
.send(email
);
150 println!("Error when sending E-mail:\n{:?}", &result
);