aurora-modules 0.1.0

Git, filesystem, system, network, archive, docker, unix, crypto, calculator, QR, color, timer, notes, clipboard, and text processing modules
Documentation
use aurora_core::{AuroraResult, Pipeline, Value};
use std::process::Command;

fn check_tool(name: &str) -> AuroraResult<()> {
    let status = Command::new("which")
        .arg(name)
        .output()
        .ok()
        .map(|o| o.status.success())
        .unwrap_or(false);
    if !status {
        return Err(aurora_core::AuroraError::CommandNotFound(
            format!("{} is not installed", name)
        ));
    }
    Ok(())
}

pub fn net_ping(host: &str) -> AuroraResult<Pipeline> {
    check_tool("ping")?;
    let output = Command::new("ping")
        .args(["-c", "4", host])
        .output()
        .map_err(|e| aurora_core::AuroraError::ModuleError(
            format!("ping failed: {}", e)
        ))?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    let combined = format!("{}{}", stdout, stderr);

    let sent = 4i64;
    let received = combined.lines()
        .filter(|l| l.contains("bytes from") || l.contains("icmp_seq"))
        .count() as i64;

    let mut min_ms = String::new();
    let mut avg_ms = String::new();
    let mut max_ms = String::new();

    for line in combined.lines() {
        if line.contains("rtt min/avg/max") || line.contains("round-trip") {
            let parts: Vec<&str> = line.split('=').collect();
            if parts.len() >= 2 {
                let stats = parts[1].trim();
                let nums: Vec<&str> = stats.split('/').collect();
                if nums.len() >= 3 {
                    min_ms = nums[0].trim().to_string();
                    avg_ms = nums[1].trim().to_string();
                    max_ms = nums[2].trim().to_string();
                }
            }
        }
    }

    Ok(Pipeline::table(
        vec!["sent".into(), "received".into(), "min_ms".into(), "avg_ms".into(), "max_ms".into()],
        vec![vec![
            Value::Int(sent),
            Value::Int(received),
            Value::String(min_ms),
            Value::String(avg_ms),
            Value::String(max_ms),
        ]],
    ))
}

pub fn net_dns(domain: &str) -> AuroraResult<Pipeline> {
    if find_tool("dig") {
        let output = Command::new("dig")
            .args([domain, "+short"])
            .output()
            .map_err(|e| aurora_core::AuroraError::ModuleError(
                format!("dig failed: {}", e)
            ))?;
        let stdout = String::from_utf8_lossy(&output.stdout);
        let rows: Vec<Vec<Value>> = stdout.lines()
            .filter(|l| !l.is_empty())
            .map(|l| vec![Value::String(l.into())])
            .collect();
        return Ok(Pipeline::table(vec!["record".into()], rows));
    }

    if find_tool("nslookup") {
        let output = Command::new("nslookup")
            .arg(domain)
            .output()
            .map_err(|e| aurora_core::AuroraError::ModuleError(
                format!("nslookup failed: {}", e)
            ))?;
        let stdout = String::from_utf8_lossy(&output.stdout);
        return Ok(Pipeline::table(
            vec!["record".into()],
            vec![vec![Value::String(stdout.trim().into())]],
        ));
    }

    Err(aurora_core::AuroraError::CommandNotFound(
        "neither dig nor nslookup are installed".into()
    ))
}

fn find_tool(name: &str) -> bool {
    Command::new("which")
        .arg(name)
        .output()
        .ok()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

pub fn net_http(url: &str) -> AuroraResult<Pipeline> {
    check_tool("curl")?;
    let output = Command::new("curl")
        .args(["-sI", url])
        .output()
        .map_err(|e| aurora_core::AuroraError::ModuleError(
            format!("curl failed: {}", e)
        ))?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let mut rows: Vec<Vec<Value>> = Vec::new();

    let mut status = String::new();
    for line in stdout.lines() {
        if line.is_empty() { continue; }
        if line.starts_with("HTTP/") {
            status = line.to_string();
        } else {
            if let Some(idx) = line.find(':') {
                let key = &line[..idx];
                let val = &line[idx + 1..];
                rows.push(vec![
                    Value::String(key.trim().into()),
                    Value::String(val.trim().into()),
                ]);
            }
        }
    }

    if !status.is_empty() {
        rows.insert(0, vec![
            Value::String("Status".into()),
            Value::String(status),
        ]);
    }

    Ok(Pipeline::table(
        vec!["header".into(), "value".into()],
        rows,
    ))
}

pub fn net_ip() -> AuroraResult<Pipeline> {
    if find_tool("curl") {
        let output = Command::new("curl")
            .args(["-s", "https://api.ipify.org?format=json"])
            .output()
            .map_err(|e| aurora_core::AuroraError::ModuleError(
                format!("curl failed: {}", e)
            ))?;
        let stdout = String::from_utf8_lossy(&output.stdout);
        if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&stdout) {
            if let Some(ip) = parsed.get("ip").and_then(|v| v.as_str()) {
                return Ok(Pipeline::table(
                    vec!["ip".into()],
                    vec![vec![Value::String(ip.into())]],
                ));
            }
        }
    }

    let output = Command::new("hostname")
        .args(["-I"])
        .output()
        .ok();
    if let Some(out) = output {
        let stdout = String::from_utf8_lossy(&out.stdout);
        let ip = stdout.trim().split(' ').next().unwrap_or("").to_string();
        if !ip.is_empty() {
            return Ok(Pipeline::table(
                vec!["ip".into()],
                vec![vec![Value::String(ip)]],
            ));
        }
    }

    Ok(Pipeline::table(
        vec!["ip".into()],
        vec![vec![Value::String("unknown".into())]],
    ))
}

pub fn net_scan(host: &str) -> AuroraResult<Pipeline> {
    if find_tool("nmap") {
        let output = Command::new("nmap")
            .args(["-F", host])
            .output()
            .map_err(|e| aurora_core::AuroraError::ModuleError(
                format!("nmap failed: {}", e)
            ))?;
        let stdout = String::from_utf8_lossy(&output.stdout);
        let mut rows: Vec<Vec<Value>> = Vec::new();

        for line in stdout.lines() {
            let line = line.trim();
            if line.contains("/tcp") || line.contains("/udp") {
                let parts: Vec<&str> = line.split_whitespace().collect();
                if let Some(port_part) = parts.first() {
                    rows.push(vec![
                        Value::String(port_part.to_string()),
                        Value::String(parts.get(1).copied().unwrap_or("").into()),
                        Value::String(parts.get(2).copied().unwrap_or("").into()),
                    ]);
                }
            }
        }

        return Ok(Pipeline::table(
            vec!["port".into(), "state".into(), "service".into()],
            rows,
        ));
    }

    Err(aurora_core::AuroraError::CommandNotFound(
        "nmap is not installed".into()
    ))
}