rustenium-identity 0.1.10

A versatile stealth overlay for rustenium
Documentation
//! Drives the real library against CreepJS and prints the ratings, so the
//! injection path can be checked end to end rather than by eye.
//!
//!   cargo run --example verify
//!   cargo run --example verify -- 1 https://abrahamjuliot.github.io/creepjs/

use rustenium::browsers::cdp_browser::CdpBrowser;
use rustenium_identity::{preset, IdentitySession};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let identity = match std::env::args().nth(1) {
        Some(arg) => preset::get_by_id(arg.parse()?)?,
        None => preset::random(),
    };
    let url = std::env::args()
        .nth(2)
        .unwrap_or_else(|| "https://abrahamjuliot.github.io/creepjs/".to_string());

    println!("preset #{:?}: {:?} / {:?}", identity.id, identity.os, identity.browser);

    let mut session = IdentitySession::launch(identity).await?;
    println!("launched, navigating to {url} ...");

    // Collect page errors before anything else runs.
    session.browser().add_init_script(rustenium::cdp::target_manager::InitScript {
        page: Some(r#"window.__errs=[];
            addEventListener('error', e => __errs.push('ERR ' + (e.message||e)));
            addEventListener('unhandledrejection', e => __errs.push('REJ ' + (e.reason && (e.reason.message||e.reason))));
        "#.into()),
        worker: None,
    }).await;

    session.browser_mut().navigate(&url).await?;
    println!("navigated; waiting for CreepJS to finish computing");

    // CreepJS takes a while; poll for the widget rather than guessing a duration.
    let mut rendered = false;
    for _ in 0..40 {
        tokio::time::sleep(Duration::from_secs(2)).await;
        let probe = session
            .browser_mut()
            .evaluate_script("!!document.querySelector('.stealth-rating')", false)
            .await;
        if let Ok(v) = probe {
            if format!("{:?}", v).contains("true") {
                rendered = true;
                break;
            }
        }
        print!(".");
    }
    println!();
    if !rendered {
        println!("WARNING: stealth widget never rendered — page may be blocked or hung");
    }
    tokio::time::sleep(Duration::from_secs(3)).await;

    let expr = r#"(() => {
        const pct = (s) => { const e=document.querySelector(s); return e ? (e.textContent.match(/^\s*(\d+%)/)||['','?'])[1] : 'MISSING'; };
        const flagged = [...document.querySelectorAll('#fingerprint-data span.hash')]
            .map(e => [e.parentElement.textContent.trim().slice(0,12), e.className.replace('hash','').trim()])
            .filter(([,c]) => c).map(([n,c]) => n+'='+c);
        return 'stealth=' + pct('.stealth-rating') + ' headless=' + pct('.headless-rating')
             + ' hashes=' + document.querySelectorAll('span.hash').length
             + ' flagged=' + (flagged.join(',') || 'NONE');
    })()"#;

    match session.browser_mut().evaluate_script(expr, false).await {
        Ok(v) => println!("\n=== CreepJS ===\n{:?}", v.result.value),
        Err(e) => println!("\nevaluate failed: {e:?}"),
    }

    session.close().await;
    Ok(())
}