use std::time::Duration;
use grabcli_core::cdp;
use grabcli_core::error::{Error, ErrorKind, Result};
use crate::api;
pub const SITE_KEY: &str = "6LfnMFIfAAAAAGCE6O5Kscpsg0ATxuLrmbxtnXCc";
pub mod action {
pub const FEED: &str = "getFeed";
pub const CATEGORY: &str = "getCategory";
pub const SEARCH_MERCHANT: &str = "searchMerchant";
pub const MERCHANT: &str = "getMerchant";
pub const MERCHANT_ITEMS: &str = "getMerchantItems";
pub const SEARCH_IN_STORE: &str = "searchInStore";
pub const ITEM_DETAIL: &str = "getItemDetail";
}
pub fn mint_expression(action: &str) -> Result<String> {
if action.is_empty() || !action.chars().all(|c| c.is_ascii_alphanumeric()) {
return Err(Error::invalid(format!("bad reCAPTCHA action `{action}`")));
}
Ok(format!(
r#"(async () => {{
const key = "{SITE_KEY}";
if (typeof grecaptcha === "undefined" || !grecaptcha.execute) {{
await new Promise((resolve, reject) => {{
const s = document.createElement("script");
s.src = "https://www.google.com/recaptcha/api.js?render=" + key;
s.onload = resolve;
s.onerror = () => reject(new Error("reCAPTCHA script failed to load"));
document.head.appendChild(s);
}});
}}
await new Promise((r) => grecaptcha.ready(r));
return await grecaptcha.execute(key, {{ action: "{action}" }});
}})()"#
))
}
pub async fn mint(cdp_base: &str, action: &str) -> Result<String> {
let expr = mint_expression(action)?;
let v = cdp::evaluate_in_origin(
cdp_base,
api::ORIGIN,
api::RECAPTCHA_PAGE,
&expr,
Duration::from_secs(30),
)
.await
.map_err(|e| {
Error::new(
e.kind,
format!(
"GrabMart needs a reCAPTCHA token minted in a browser over CDP ({cdp_base}): {}. \
Start the browser with remote debugging (config `cdp`).",
e.message
),
)
})?;
match v.as_str() {
Some(t) if t.len() > 20 => Ok(t.to_string()),
_ => Err(Error::new(
ErrorKind::CdpUnavailable,
"the browser returned no reCAPTCHA token",
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expression_embeds_action_and_key() -> Result<()> {
let e = mint_expression(action::SEARCH_IN_STORE)?;
assert!(e.contains(r#"action: "searchInStore""#));
assert!(e.contains(SITE_KEY));
Ok(())
}
#[test]
fn rejects_injection_in_action() {
assert!(mint_expression(r#"x"});alert(1);//"#).is_err());
assert!(mint_expression("").is_err());
}
}