057d68bdc498c02c0649feeea162089484cdb132
[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
49 fn main() -> Result<()> {
50 println!("I need a RTX 3080 right now :)");
51
52 let to_find = "RTX 3080";
53 let to_match = "3080";
54
55 let config = Config::read(FILE_CONF)?;
56
57 let selector = Selector::parse("div.productGridElement > h2 > a:nth-child(1)").unwrap();
58 let url = format!("https://www.steg-electronics.ch/fr/search?suche={}", to_find);
59
60 let client = reqwest::blocking::Client::new();
61
62 loop {
63 println!("Request: {}", url);
64
65 match client.get(&url).send() {
66 Ok(resp) =>
67 if resp.status().is_success() {
68 let html = resp.text()?;
69 let document = Html::parse_document(&html);
70
71 // A vector of (<title>, <url>), empty if nothing matches.
72 let match_items: Vec<(String, String)> =
73 document.select(&selector).filter_map(
74 |element| {
75 if let (Some(title), Some(url_to_item)) = (element.value().attr("title"), element.value().attr("href")) {
76 if title.find(to_match).is_some() {
77 return Some((String::from(title), String::from(url_to_item)))
78 }
79 }
80 None
81 }
82 ).collect();
83
84 if match_items.is_empty() {
85 println!("No matches -_-ยดยด");
86 } else if send_email(&match_items[..], &config.smtp_login, &config.smtp_password) {
87 println!("Some matches has been found! An e-mail has been sent!");
88 return Ok(())
89 } else {
90 println!("Unable to send e-mail");
91 }
92 } else {
93 println!("Request unsuccessful:\n{:#?}", resp);
94 },
95 Err(error) =>
96 println!("Error during request: {:?}", error)
97 }
98
99 thread::sleep(time::Duration::from_secs(60)); // 1 min.
100 }
101 }
102
103 /// Return 'true' if the email has been successfully sent.
104 fn send_email(items: &[(String, String)], login: &str, password: &str) -> bool {
105
106 let items_list_html =
107 items.iter().fold(
108 String::new(),
109 |acc, (title, url)| {
110 format!("{}\n<li><a href=\"{}\">{}</a></li>", acc, url, title)
111 }
112 );
113
114 let email = Email::builder()
115 // Addresses can be specified by the tuple (email, alias)
116 .to(("greg.burri@gmail.com", "Greg Burri"))
117 .from("redmine@d-lan.net")
118 .subject("RTX 3080 AVAILABLE!")
119 .html(format!("<ul>{}</ul>", items_list_html))
120 .build()
121 .unwrap()
122 .into();
123
124 let mut tls_builder = TlsConnector::builder();
125 tls_builder.min_protocol_version(Some(Protocol::Tlsv10));
126 let tls_parameters =
127 ClientTlsParameters::new(
128 "mail.gandi.net".to_string(),
129 tls_builder.build().unwrap()
130 );
131
132 let mut mailer =
133 SmtpClient::new(("mail.gandi.net", 465), ClientSecurity::Wrapper(tls_parameters)).unwrap()
134 .authentication_mechanism(Mechanism::Login)
135 .smtp_utf8(true)
136 .credentials(Credentials::new(String::from(login), String::from(password)))
137 .connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
138 .transport();
139
140 let result = mailer.send(email);
141
142 if result.is_err() {
143 println!("Error when sending E-mail:\n{:?}", &result);
144 }
145
146 mailer.close();
147
148 result.is_ok()
149 }