Use of now() to have a precise period of time
[rtx3080.git] / src / main.rs
1 use std::{ thread, time, fs::File };
2 use scraper::{ Html, Selector };
3 use lettre::{
4 ClientSecurity,
5 ClientTlsParameters,
6 Transport,
7 SmtpClient,
8 smtp::{
9 ConnectionReuseParameters,
10 authentication::{
11 Credentials,
12 Mechanism
13 }
14 }
15 };
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 };
20
21 type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
22
23 #[derive(Debug, Deserialize, Serialize)]
24 struct Config {
25 smtp_login: String,
26 smtp_password: String,
27 }
28
29 impl Config {
30 fn default() -> Self {
31 Config { smtp_login: "login".to_string(), smtp_password: "password".to_string() }
32 }
33
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()),
37 Err(_) => {
38 let file = File::create(file_path)?;
39 let default_config = Config::default();
40 to_writer(file, &default_config)?;
41 Ok(default_config)
42 }
43 }
44 }
45 }
46
47 const FILE_CONF: &str = "config.ron";
48 const PULL_PERIOD: time::Duration = time::Duration::from_secs(60); // 1 min.
49
50 fn main() -> Result<()> {
51 println!("I need a RTX 3080 right now :)");
52
53 let to_find = "RTX 3080";
54 let to_match = "3080";
55
56 let config = Config::read(FILE_CONF)?;
57
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);
60
61 let client = reqwest::blocking::Client::new();
62
63 loop {
64 let time_beginning_loop = time::Instant::now();
65 println!("Request: {}", url);
66
67 match client.get(&url).send() {
68 Ok(resp) =>
69 if resp.status().is_success() {
70 let html = resp.text()?;
71 let document = Html::parse_document(&html);
72
73 // A vector of (<title>, <url>), empty if nothing matches.
74 let match_items: Vec<(String, String)> =
75 document.select(&selector).filter_map(
76 |element| {
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)))
80 }
81 }
82 None
83 }
84 ).collect();
85
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!");
90 return Ok(())
91 } else {
92 println!("Unable to send e-mail");
93 }
94 } else {
95 println!("Request unsuccessful:\n{:#?}", resp);
96 },
97 Err(error) =>
98 println!("Error during request: {:?}", error)
99 }
100
101 let elapsed = time::Instant::now() - time_beginning_loop;
102
103 if elapsed < PULL_PERIOD {
104 let to_wait = PULL_PERIOD - elapsed;
105 thread::sleep(to_wait);
106 }
107 }
108 }
109
110 /// Return 'true' if the email has been successfully sent.
111 fn send_email(items: &[(String, String)], login: &str, password: &str) -> bool {
112
113 let items_list_html =
114 items.iter().fold(
115 String::new(),
116 |acc, (title, url)| {
117 format!("{}\n<li><a href=\"{}\">{}</a></li>", acc, url, title)
118 }
119 );
120
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))
127 .build()
128 .unwrap()
129 .into();
130
131 let mut tls_builder = TlsConnector::builder();
132 tls_builder.min_protocol_version(Some(Protocol::Tlsv10));
133 let tls_parameters =
134 ClientTlsParameters::new(
135 "mail.gandi.net".to_string(),
136 tls_builder.build().unwrap()
137 );
138
139 let mut mailer =
140 SmtpClient::new(("mail.gandi.net", 465), ClientSecurity::Wrapper(tls_parameters)).unwrap()
141 .authentication_mechanism(Mechanism::Login)
142 .smtp_utf8(true)
143 .credentials(Credentials::new(String::from(login), String::from(password)))
144 .connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
145 .transport();
146
147 let result = mailer.send(email);
148
149 if result.is_err() {
150 println!("Error when sending E-mail:\n{:?}", &result);
151 }
152
153 mailer.close();
154
155 result.is_ok()
156 }