Skip to content
Snippets Groups Projects
main.rs 4.05 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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;
    
        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")
            .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!");
    
    
    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);
        }
    }