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 products = get_html_products().await.unwrap();
let filtered: Vec<Product> = products
.into_iter()
.filter(|el| {
if el.name.contains(s) {
return el.available;
}
false
})
.collect();
if filtered.len() > 0 {
println!("Found {} products.\nSending mail.", filtered.len());
} 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")
.spawn()
.expect("sh command failed to start geckodriver");
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
95
// 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);
}
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")
.expect("Failed to construct email message");
let creds = Credentials::new(config.smtp_user.to_string(), config.smtp_pass.to_string());
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);
}
}