// Copyright © 2023 Aravinth Manivannan // SPDX-FileCopyrightText: 2023 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: MIT use std::time::Instant; use clap::*; use mcaptcha_pow_sha256::ConfigBuilder; use mcaptcha_pow_sha256::PoW; use reqwest::Client; use scraper::{Html, Selector}; use serde::{Deserialize, Serialize}; use url::Url; #[derive(Deserialize, Parser, Serialize, Clone, Debug)] /// Compute PoW with offline parameters struct Offline { /// Salt with which PoW should be computed #[arg(short, long)] salt: String, /// Phrase over which PoW should be computed #[arg(short, long)] phrase: String, /// Difficulty Factor #[arg(short, long)] difficulty_factor: u32, } #[derive(Deserialize, Parser, Serialize, Clone, Debug)] /// Compute PoW by fetching parameters from protected page URL struct ProtectedPage { /// URL of the protected page. Example: https://example.org/protected.html url: Url, } impl ProtectedPage { async fn extract_url(self) -> Option { let c = Client::default(); let text = c.get(self.url).send().await.unwrap().text().await.unwrap(); let fragement = Html::parse_fragment(&text); let selector = Selector::parse("label#mcaptcha__token-label").unwrap(); if let Some(label) = fragement.select(&selector).next() { if let Some(val) = label.value().attr("data-mcaptcha_url") { if let Ok(url) = Url::parse(val) { return Some(WidgetUrl { url }); } } } None } } #[derive(Deserialize, Parser, Serialize, Clone, Debug)] /// Compute PoW by fetching parameters from CAPTCHA URL struct WidgetUrl { url: Url, } impl WidgetUrl { async fn run(&self) { let c = Client::default(); let sitekey = self.extract_sitekey(); if sitekey.is_none() { println!("ERROR: Sitekey not found in URL. Please enter correct URL"); return; } let sitekey = sitekey.unwrap(); let url = self.get_config_url(); #[derive(Clone, Debug, Serialize, Deserialize)] struct ConfigRequest { key: String, } #[derive(Clone, Debug, Serialize, Deserialize)] struct ConfigResp { string: String, difficulty_factor: u32, salt: String, } let req = ConfigRequest { key: sitekey.to_string(), }; let resp: ConfigResp = c .post(url) .json(&req) .send() .await .unwrap() .json() .await .unwrap(); let start = Instant::now(); let work = prove_work(resp.string.clone(), resp.salt, resp.difficulty_factor); let finish = Instant::now(); let time_elapsed = finish.duration_since(start); let time = time_elapsed.as_millis() as usize; #[derive(Clone, Debug, Serialize, Deserialize)] struct VerifyRequest { key: String, nonce: u64, result: String, string: String, time: usize, worker_type: String, } #[derive(Clone, Debug, Serialize, Deserialize)] struct VerifyResp { token: String, } let req = VerifyRequest { key: sitekey.clone(), nonce: work.nonce, result: work.result, string: resp.string, time, worker_type: String::from("mCaptcha CLI"), }; let url = self.get_verify_url(); let resp: VerifyResp = c .post(url) .json(&req) .send() .await .unwrap() .json() .await .unwrap(); println!("Authorization token: {}", resp.token); } fn extract_sitekey(&self) -> Option { let mut sitekey = None; for (k, v) in self.url.query_pairs() { if &k == "sitekey" { let x: &str = &v; sitekey = Some(x.to_string()) } } sitekey } fn get_config_url(&self) -> Url { let mut url = self.url.clone(); url.set_path("/api/v1/pow/config"); url } fn get_verify_url(&self) -> Url { let mut url = self.url.clone(); url.set_path("/api/v1/pow/verify"); url } } #[derive(Deserialize, Parser, Serialize, Clone, Debug)] #[command(author, version, about, long_about = None)] enum Args { Offline(Offline), ProtectedPage(ProtectedPage), WidgetUrl(WidgetUrl), } fn prove_work(phrase: String, salt: String, difficulty_factor: u32) -> PoW { let config = ConfigBuilder::default().salt(salt).build().unwrap(); config.prove_work(&phrase, difficulty_factor).unwrap() } #[tokio::main] async fn main() { let args = Args::parse(); match args { Args::Offline(matches) => { let phrase = matches.phrase; let salt = matches.salt; let difficulty_factor = matches.difficulty_factor; let work = prove_work(phrase.clone(), salt, difficulty_factor); // let config = ConfigBuilder::default().salt(salt.into()).build().unwrap(); // let work = config.prove_work(&phrase, difficulty_factor).unwrap(); println!("difficulty: {}", &difficulty_factor); println!("nonce: {}", &work.nonce); println!("original phrase: {}", &phrase); println!("result: {}", &work.result); } Args::WidgetUrl(matches) => { matches.run().await; } Args::ProtectedPage(matches) => { let widget = matches .extract_url() .await .unwrap_or_else(|| panic!("No mCaptcha widget found in the web page")); widget.run().await; } } }