Skip to content
Snippets Groups Projects
main.rs 4.15 KiB
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();
Marvin Weiler's avatar
Marvin Weiler committed
    let filters = &config.filters;

    let products = get_html_products().await.unwrap();

    let filtered: Vec<Product> = products
        .into_iter()
        .filter(|el| {
Marvin Weiler's avatar
Marvin Weiler committed
            for s in filters {
                if el.name.contains(s) {
                    return el.available;
    if filtered.len() > 0 {
        println!("Found {} products.\nSending mail.", filtered.len());
Marvin Weiler's avatar
Marvin Weiler committed
        send_email(config, filtered);
    } else {
        println!("Sorry, all products are out of stock. :/");
    }
}

async fn get_html_products() -> Result<Vec<Product>, CmdError> {
Marvin Weiler's avatar
Marvin Weiler committed
    println!("Fetching items from amd Website");
    // start the geckodriver
    let mut geckodriver = Command::new("sh")
        .arg("-c")
Marvin Weiler's avatar
Marvin Weiler committed
        .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!");

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")
Marvin Weiler's avatar
Marvin Weiler committed
            .body(String::from(&message))
            .expect("Failed to construct email message");

Marvin Weiler's avatar
Marvin Weiler committed
        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);
    }
}