steam-user 0.1.0

Steam User web client for Rust - HTTP-based Steam Community interactions
Documentation
//! Parse a locally-saved GCPD matchmaking HTML file and print all extracted
//! fields.
//!
//! Run with:
//!   cargo run --example matchmaking_stats --
//! "C:\Users\Admin\Downloads\view-source_https___steamcommunity.
//! com_profiles_76561199405417179_gcpd_730_tab=matchmaking.html"
//!
//! If no path is given it defaults to the path above.

use steam_user::services::apps::parse_matchmaking_html;
use tracing::{error, info};

/// Firefox saves view-source pages as an HTML syntax-highlighter wrapper
/// where the original source is entity-encoded inside `<span>` elements.
/// This strips the wrapper and decodes the entities to recover the real HTML.
fn unwrap_viewsource(raw: &str) -> String {
    let stripped = regex::Regex::new(r"<[^>]+>").expect("regex compilation failed").replace_all(raw, "");
    stripped.replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", "\"").replace("&#39;", "'").replace("&amp;", "&")
}

fn main() {
    let path = std::env::args().nth(1).unwrap_or_else(|| r"C:\Users\Admin\Downloads\view-source_https___steamcommunity.com_profiles_76561199405417179_gcpd_730_tab=matchmaking.html".to_string());

    let raw = std::fs::read_to_string(&path).unwrap_or_else(|e| {
        error!("Failed to read '{}': {}", path, e);
        std::process::exit(1);
    });

    let html = if raw.contains(r#"class="line-content""#) {
        error!("[info] Detected Firefox view-source file — unwrapping...");
        unwrap_viewsource(&raw)
    } else {
        raw
    };

    let stats = parse_matchmaking_html(&html);

    // ── Cooldown ───────────────────────────────────────────────────────────────
    info!("═══════════════════  MATCHMAKING COOLDOWN  ══════════════════════");
    if let Some(entries) = &stats.matchmaking_cooldown {
        for (i, e) in entries.iter().enumerate() {
            info!("  [{}] expiration : {:?}", i + 1, e.competitive_cooldown_expiration);
            info!("       level      : {:?}", e.competitive_cooldown_level);
            info!("       acknowledged: {:?}", e.acknowledged);
        }
    } else {
        info!("  (none)");
    }

    // ── Summary ────────────────────────────────────────────────────────────────
    info!("\n════════════════════  MATCHMAKING SUMMARY ({})  ═════════════════", stats.matchmaking_summary.len());
    for row in &stats.matchmaking_summary {
        info!("  {:20} W:{:4} T:{:4} L:{:4}  skill:{:?}  region:{:?}  last:{}", row.matchmaking_mode.as_deref().unwrap_or("?"), row.wins.unwrap_or(0), row.ties.unwrap_or(0), row.losses.unwrap_or(0), row.skill_group, row.region, row.last_match.as_deref().unwrap_or("?"),);
    }

    // ── Per Map ────────────────────────────────────────────────────────────────
    info!("\n══════════════════  MATCHMAKING PER MAP ({})  ════════════════════", stats.matchmaking_per_map.len());
    for row in &stats.matchmaking_per_map {
        info!("  {:22} {:15} W:{:4} T:{:4} L:{:4}  skill:{:?}  region:{:?}  last:{}", row.matchmaking_mode.as_deref().unwrap_or("?"), row.map.as_deref().unwrap_or("?"), row.wins.unwrap_or(0), row.ties.unwrap_or(0), row.losses.unwrap_or(0), row.skill_group, row.region, row.last_match.as_deref().unwrap_or("?"),);
    }

    // ── Last Played ────────────────────────────────────────────────────────────
    info!("\n═════════════════════  LAST PLAYED MODES  ═══════════════════════");
    if let Some(entries) = &stats.last_played_modes {
        for row in entries {
            info!("  {:20}  last:{}", row.matchmaking_mode.as_deref().unwrap_or("?"), row.last_match.as_deref().unwrap_or("?"),);
        }
    } else {
        info!("  (none)");
    }
}