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(config, filtered); } else { println!("Sorry, all products are out of stock. :/"); } } async fn get_html_products() -> Result<Vec<Product>, CmdError> { println!("Fetching items from amd Website"); // start the geckodriver let mut geckodriver = Command::new("sh") .arg("-c") .arg("./geckodriver --log fatal") .spawn() .expect("sh command failed to start geckodriver"); // 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") .body(String::from(&message)) .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); } }