diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c49ce9b26740d535da19b67423e7dcae18927daf --- /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 47a92ae04b5441c3cf25744cbe98e8e9a603f359..3ec3e0c6e5e2310e266d7f76f9162c5d308e2573 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 0000000000000000000000000000000000000000..20d9b502c4b2fc1a8b5ab45e0b73d200eb463e4e --- /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 94ab335196c40e734f2c256b569c1b591745cafc..65329c9226df03438a1174e49658a82d2f3427df 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); + } +}