2 use scraper::{Html, Selector};
5 client: &reqwest::Client,
8 ) -> anyhow::Result<Vec<(String, String)>> {
10 Html::parse_document(&client.get(addr).send().await?.text().await?)
11 .select(&Selector::parse(form).unwrap())
13 .ok_or(anyhow!("form for selector {form} not found"))?
14 .select(&Selector::parse("input").unwrap())
15 .map(|item| item.value())
17 if matches!(item.attr("type"), Some(x) if x == "checkbox" || x == "submit") {
22 item.attr("name")?.into(),
23 item.attr("value").unwrap_or_default().into(),
30 // this is dumb but it works
31 // we don't use a hashmap because keys may be duplicated
32 // this overwrites the first entry with a certain key
33 fn form_set(form: &mut Vec<(String, String)>, key: &str, value: String) {
34 for entry in form.iter_mut() {
41 form.push((key.into(), value));
45 async fn main() -> anyhow::Result<()> {
50 let target_host = env::var("DDNS_TARGET_HOST")?;
51 let username = env::var("DDNS_USERNAME")?;
52 let password = env::var("DDNS_PASSWORD")?;
53 let domain_id = env::var("DDNS_DOMAIN_ID")?;
55 let target_ip = *dns_lookup::lookup_host(&target_host)?
57 .ok_or(anyhow!("no ip found for {target_host}"))?;
59 let client = reqwest::ClientBuilder::new().cookie_store(true).build()?;
61 let mut login = form_get(
63 "https://cp.istanco.net/clientarea.php",
64 r#"form[action="https://cp.istanco.net/dologin.php"]"#,
68 form_set(&mut login, "username", username);
69 form_set(&mut login, "password", password);
72 .post("https://cp.istanco.net/dologin.php")
77 let manage_url = format!("https://cp.istanco.net/index.php?m=br_dnsmanager&id={domain_id}");
79 let mut manage = form_get(&client, &manage_url, r#"form[name="br-dnsrecord-manager"]"#).await?;
81 form_set(&mut manage, "value[]", target_ip.to_string());
82 form_set(&mut manage, "btnSave", "Save Changes".into());
84 client.post(&manage_url).form(&manage).send().await?;