use super::{get_nextblock, send, strip_colors, Context, FAIL, SUCCESS}; use anyhow::Result; use futures_util::StreamExt; use itertools::Itertools; use poise::serenity_prelude::*; use std::net::Ipv4Addr; use std::str::FromStr; use std::time::Instant; use tokio::sync::{broadcast, MappedMutexGuard, Mutex, MutexGuard}; #[derive(Clone, Debug)] pub struct Player { pub admin: bool, pub name: String, pub uuid: String, pub ip: Ipv4Addr, } static PLAYERS: Mutex<(Vec, Option)> = Mutex::const_new((vec![], None)); async fn update( stdin: &broadcast::Sender, ) -> Result, Option)>> { let mut lock = PLAYERS.lock().await; if lock.1.is_none() || lock.1.unwrap().elapsed().as_millis() > 500 { lock.0 = get_players(stdin).await?; lock.1 = Some(Instant::now()); } Ok(lock) } pub struct Players {} impl Players { pub async fn get_all( stdin: &broadcast::Sender, ) -> Result>> { { Ok(MutexGuard::map(update(stdin).await?, |(p, _)| p)) } } pub async fn find( stdin: &broadcast::Sender, name: String, ) -> Result>> { Ok(MutexGuard::try_map(update(stdin).await?, |(p, _)| { p.iter_mut().find(|x| x.name == name) }) .ok()) } } async fn get_players(stdin: &broadcast::Sender) -> Result> { let mut players = vec![]; send!(stdin, "players")?; let recv = get_nextblock().await; for line in recv.lines() { if line.starts_with("No") { break; } else if line.is_empty() { continue; } if let Some((first, uuid, ip)) = line.split('|').collect_tuple() { if let Some((admin, name)) = first.split_once(' ') { players.push(Player { admin: admin == "[A]", name: strip_colors(name), uuid: uuid.to_owned(), ip: Ipv4Addr::from_str(ip).unwrap(), }); } } } Ok(players) } pub async fn autocomplete<'a>( ctx: Context<'a>, partial: &'a str, ) -> impl futures::Stream + 'a { let x = Players::get_all(&ctx.data().stdin).await.unwrap().clone(); futures::stream::iter(x) .filter(move |p| futures::future::ready(p.name.starts_with(partial))) .map(|p| p.name) } #[poise::command(slash_command, category = "Info", rename = "players")] /// lists the currently online players. pub async fn list(ctx: Context<'_>) -> Result<()> { let _ = ctx.defer().await; let players = Players::get_all(&ctx.data().stdin).await.unwrap().clone(); poise::send_reply( ctx, poise::CreateReply::default().embed(if players.is_empty() { CreateEmbed::new().title("no players online.").color(FAIL) } else { CreateEmbed::new() .fields(players.into_iter().map(|p| { let admins = if p.admin { crate::emoji::named::ADMIN } else { "" }; (p.name, admins, true) })) .description("currently online players.") .color(SUCCESS) }), ) .await?; Ok(()) }