use anyhow::Result; use tracing::info; use super::rcon::RconClient; /// Player information from an ARK RCON `ListPlayers` response. pub struct ArkPlayer { pub name: String, pub steamid: String, } /// Query an ARK server for its connected players. pub async fn get_players(rcon_address: &str, rcon_password: &str) -> Result> { let mut client = RconClient::connect(rcon_address, rcon_password).await?; let response = client.command("ListPlayers").await?; let players = parse_list_players_response(&response); info!(count = players.len(), "ARK player list retrieved"); Ok(players) } /// Parses lines shaped like `0. PlayerName, 76561198000000000`. fn parse_list_players_response(response: &str) -> Vec { let mut players = Vec::new(); for line in response.lines() { let trimmed = line.trim(); if trimmed.is_empty() { continue; } // "No Players Connected" if trimmed.eq_ignore_ascii_case("no players connected") { break; } // Strip the ". " prefix. let entry = match trimmed.split_once('.') { Some((index, rest)) if index.trim().chars().all(|c| c.is_ascii_digit()) => rest.trim(), _ => continue, }; let (name, steamid) = match entry.rsplit_once(',') { Some((name, steamid)) => (name.trim(), steamid.trim()), None => (entry, ""), }; if name.is_empty() { continue; } players.push(ArkPlayer { name: name.to_string(), steamid: steamid.to_string(), }); } players } #[cfg(test)] mod tests { use super::*; #[test] fn parses_connected_players() { let response = "0. Alper, 76561198000000001\n1. Rezan, 76561198000000002\n"; let players = parse_list_players_response(response); assert_eq!(players.len(), 2); assert_eq!(players[0].name, "Alper"); assert_eq!(players[0].steamid, "76561198000000001"); assert_eq!(players[1].name, "Rezan"); } #[test] fn handles_empty_server() { assert!(parse_list_players_response("No Players Connected\n").is_empty()); } }