rustybook-messenger 0.2.1

Messenger client for Rustybook
Documentation
use regex::Regex;

use super::MqttConfig;
use crate::error::MessengerError;

fn extract_first(html: &str, patterns: &[&str]) -> Result<Option<String>, MessengerError> {
    for pattern in patterns {
        let regex = Regex::new(pattern)?;
        if let Some(value) = regex
            .captures(html)
            .and_then(|captures| captures.get(1).map(|capture| capture.as_str().to_string()))
        {
            return Ok(Some(value));
        }
    }

    Ok(None)
}

pub(super) fn extract_fb_dtsg(html: &str) -> Result<Option<String>, MessengerError> {
    extract_first(
        html,
        &[
            r#""DTSGInitialData".*?"token":"([^"]+)""#,
            r#""DTSGInitData"(?:\s*,\s*\[\])?(?:\s*,\s*)\{[^}]*"token"\s*:\s*"([^"]+)""#,
            r#""fb_dtsg":\{"value":"([^"]+)""#,
            r#"name="fb_dtsg"\s+value="([^"]+)""#,
        ],
    )
}

pub(super) fn extract_lsd(html: &str) -> Result<Option<String>, MessengerError> {
    extract_first(
        html,
        &[
            r#""LSD"\s*,\s*\[\s*\]\s*,\s*\{\s*"token"\s*:\s*"([^"]+)""#,
            r#""LSD"\s*,\s*\[\s*\]\s*,\s*\{\s*"token"\s*:\s*"([A-Za-z0-9_-]+)""#,
            r#""lsd":\{"token":"([^"]+)""#,
            r#"name="lsd"\s+value="([^"]+)""#,
        ],
    )
}

pub(super) fn extract_jazoest(html: &str) -> Result<Option<String>, MessengerError> {
    if let Some(value) = extract_first(
        html,
        &[
            r#""jazoest":\{"value":"([^"]+)""#,
            r#"name="jazoest"\s+value="([^"]+)""#,
        ],
    )? {
        return Ok(Some(value));
    }

    if let Some(fb_dtsg) = extract_fb_dtsg(html)? {
        let sum: u32 = fb_dtsg.bytes().map(u32::from).sum();
        return Ok(Some(format!("2{sum}")));
    }

    Ok(None)
}

pub(super) fn extract_mqtt_config(html: &str) -> Option<MqttConfig> {
    let client_id = extract_mqtt_client_id(html)?;
    let app_id = extract_mqtt_app_id(html)?;
    let region = extract_mqtt_region(html)?;

    Some(MqttConfig {
        client_id,
        app_id,
        region,
    })
}

pub(super) fn extract_client_revision(html: &str) -> Option<u64> {
    let patterns = [
        r#""client_revision"\s*:\s*(\d+)"?"#,
        r#""__spin_r"\s*:\s*(\d+)"?"#,
    ];

    for pattern in patterns {
        let regex = Regex::new(pattern).ok()?;
        if let Some(value) = regex
            .captures(html)
            .and_then(|captures| captures.get(1).map(|capture| capture.as_str().to_string()))
            .and_then(|value| value.parse::<u64>().ok())
        {
            return Some(value);
        }
    }

    None
}

fn extract_mqtt_client_id(html: &str) -> Option<String> {
    let regex =
        Regex::new("\\[\"MqttWebDeviceID\".*?\"clientID\"\\s*:\\s*\"([a-f0-9\\-]+)\"").ok()?;
    regex
        .captures(html)
        .and_then(|captures| captures.get(1).map(|capture| capture.as_str().to_string()))
}

fn extract_mqtt_app_id(html: &str) -> Option<String> {
    let regex = Regex::new("\\[\"MqttWebConfig\".*?\"appID\"\\s*:\\s*(\\d+)").ok()?;
    regex
        .captures(html)
        .and_then(|captures| captures.get(1).map(|capture| capture.as_str().to_string()))
}

fn extract_mqtt_region(html: &str) -> Option<String> {
    let regex = Regex::new(
        "\\[\"MqttWebConfig\".*?\"endpoint\"\\s*:\\s*\"[^\"]*?region=([a-zA-Z0-9_-]+)[^\"]*\"",
    )
    .ok()?;
    regex.captures(html).and_then(|captures| {
        captures
            .get(1)
            .map(|capture| capture.as_str().to_ascii_lowercase())
    })
}