From 5deb3436de66d23fac229911be11028333c50f23 Mon Sep 17 00:00:00 2001 From: marvin <marvin@weiler.rocks> Date: Sun, 6 Mar 2022 20:38:56 +0100 Subject: [PATCH] Added email notification and an config-parser --- .gitignore | 17 +++++++ Cargo.toml | 9 ++-- config.json.defaut | 1 + src/main.rs | 115 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 111 insertions(+), 31 deletions(-) create mode 100644 .gitignore create mode 100644 config.json.defaut diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c49ce9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +#exclude the config file +config.json diff --git a/Cargo.toml b/Cargo.toml index 47a92ae..3ec3e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" [dependencies] fantoccini = "0.18.0" -scraper = "0.12.0" +penguin-config = "0.1.1" +serde = "1.0.136" tokio = { version = "1", features = ["full"] } -lettre = "0.9" -lettre_email = "0.9" +lettre = { version = "0.10.0-rc.4", features = [ + "smtp-transport", + "native-tls", +] } diff --git a/config.json.defaut b/config.json.defaut new file mode 100644 index 0000000..20d9b50 --- /dev/null +++ b/config.json.defaut @@ -0,0 +1 @@ +{"smtp_host":"","smtp_user":"","smtp_pass":"","smtp_from":"","filters":[],"recipients":[]} diff --git a/src/main.rs b/src/main.rs index 94ab335..65329c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,62 @@ 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 filters = vec!["RX 6800", "RX 6700"]; - - loop { - 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; - } + 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!("The following products are available"); - } else { - println!("Sorry, all products are out of stock. :/"); - } - } -} + } + false + }) + .collect(); -#[derive(Debug)] -pub struct Product { - name: String, - price: String, - available: bool, + 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"); + // navigate to amd-site let mut webc = ClientBuilder::native() .connect("http://localhost:4444") @@ -71,5 +92,43 @@ async fn get_html_products() -> Result<Vec<Product>, CmdError> { 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(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); + } +} -- GitLab