Newer
Older
use fantoccini::error::CmdError;
use fantoccini::{ClientBuilder, Locator};
use lettre::transport::smtp::authentication::{Credentials, Mechanism};
use lettre::{Message, SmtpTransport, Transport};
use penguin_config::*;
use std::process::Command;
struct Product {
name: String,
price: String,
available: bool,
}
#[derive(Deserialize, PenguinConfigFile, Default)]
#[penguin_config(path = "config.json")]
struct Config {
smtp_host: String,
smtp_user: String,
smtp_pass: String,
smtp_from: String,
filters: Vec<String>,
recipients: Vec<String>,
}
#[tokio::main]
async fn main() {
let config = Config::read_config();
let filters = config.filters;
let products = get_html_products().await.unwrap();
let filtered: Vec<Product> = products
.into_iter()
.filter(|el| {
for s in &filters {
if el.name.contains(s) {
return el.available;
}
false
})
.collect();
if filtered.len() > 0 {
println!("Found {} products.\nSending mail.", filtered.len());
send_email(filtered);
} else {
println!("Sorry, all products are out of stock. :/");
}
}
async fn get_html_products() -> Result<Vec<Product>, CmdError> {
// start the geckodriver
let mut geckodriver = Command::new("sh")
.arg("-c")
.arg("./geckodriver")
.spawn()
.expect("sh command failed to start geckodriver");
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// navigate to amd-site
let mut webc = ClientBuilder::native()
.connect("http://localhost:4444")
.await
.expect("failed to connect to WebDriver");
webc.goto("https://www.amd.com/de/direct-buy/de")
.await
.expect("Could not connect to the amd Website");
// find all articles that are for sale
let html_elements = webc
.find_all(Locator::Css("article"))
.await
.expect("find_all error");
//Interate over all items in the shop and parse the relevant strings
// in the struct Product
let mut products: Vec<Product> = Vec::new();
for mut el in html_elements {
let name = el.find(Locator::Css(".shop-title")).await?.text().await?;
let price = el.find(Locator::Css(".shop-price")).await?.text().await?;
let avail = el.find(Locator::Css(".shop-links")).await?.text().await?;
let product = Product {
name,
price,
available: !avail.contains("ergriffen"),
};
products.push(product);
}
// we do not need the webbrowser anymore
webc.close()
.await
.expect("Failed to terminate webdriver sessions");
// stop the geckodriver
geckodriver.kill().expect("Failed to kill geckodriver!");
return Ok(products);
}
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
fn send_email(config: Config, products: Vec<Product>) -> () {
let mut message = String::from(
"The following products are available at: https://www.amd.com/de/direct-buy \n\n",
);
for product in products {
message.push_str(format!(" - {}, Price: {}\n", product.name, product.price).as_str());
}
for recipient in config.recipients {
let mail = Message::builder()
.from(
config
.smtp_from
.parse()
.expect(format!("From address: {} is invalid", config.smtp_from).as_str()),
)
.to(recipient
.parse()
.expect(format!("To adress: {} is invalid", recipient).as_str()))
.subject("Products are available at the AMD website")
.body(message)
.expect("Failed to construct email message");
let creds = Credentials::new(config.smtp_user, config.smtp_pass);
let mailer = SmtpTransport::relay(&config.smtp_host)
.expect("Failed to connect to mailerver")
.credentials(creds)
.authentication(vec![Mechanism::Plain])
.build();
mailer.send(&mail).expect("Error sending mail");
println!("Mail was send to {}.", recipient);
}
}