use scraper::{Html, Selector};
use std::collections::BTreeMap;
use std::sync::LazyLock;
static FORM: LazyLock<Selector> = LazyLock::new(|| Selector::parse("form").unwrap());
static HIDDEN_FIELDS: LazyLock<Selector> =
LazyLock::new(|| Selector::parse("form input[type=hidden]").unwrap());
static SF_BUTTONS: LazyLock<Selector> =
LazyLock::new(|| Selector::parse("form button[name=sf]").unwrap());
static TOKEN: LazyLock<Selector> = LazyLock::new(|| Selector::parse("input[name=token]").unwrap());
static CONFIRM: LazyLock<Selector> = LazyLock::new(|| Selector::parse("#confirm").unwrap());
pub struct Page(Html);
impl Page {
pub fn parse(html: &str) -> Self {
Self(Html::parse_document(html))
}
pub fn form_action(&self) -> Option<&str> {
self.0
.select(&FORM)
.next()
.and_then(|form| form.attr("action"))
}
pub fn hidden_fields(&self) -> BTreeMap<String, String> {
self.0
.select(&HIDDEN_FIELDS)
.filter_map(|input| {
let name = input.attr("name")?;
Some((
name.to_owned(),
input.attr("value").unwrap_or_default().to_owned(),
))
})
.collect()
}
pub fn token(&self) -> Option<String> {
self.value(&TOKEN)
}
pub fn confirm(&self) -> Option<String> {
self.value(&CONFIRM)
}
pub fn twofa_methods(&self) -> TwoFaMethods {
let mut methods = TwoFaMethods::default();
for button in self.0.select(&SF_BUTTONS) {
match button.attr("value") {
Some("mail") => methods.email = true,
Some("totp") => methods.totp = true,
_ => {}
}
}
methods
}
fn value(&self, selector: &Selector) -> Option<String> {
self.0
.select(selector)
.next()
.and_then(|element| element.attr("value"))
.map(str::to_owned)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct TwoFaMethods {
pub email: bool,
pub totp: bool,
}