html terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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<Player>, Option<Instant>)> = Mutex::const_new((vec![], None));

async fn update(
    stdin: &broadcast::Sender<String>,
) -> Result<MutexGuard<(Vec<Player>, Option<Instant>)>> {
    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<String>,
    ) -> Result<MappedMutexGuard<Vec<Player>>> {
        {
            Ok(MutexGuard::map(update(stdin).await?, |(p, _)| p))
        }
    }

    pub async fn find(
        stdin: &broadcast::Sender<String>,
        name: String,
    ) -> Result<Option<MappedMutexGuard<Player>>> {
        Ok(MutexGuard::try_map(update(stdin).await?, |(p, _)| {
            p.iter_mut().find(|x| x.name == name)
        })
        .ok())
    }
}

async fn get_players(stdin: &broadcast::Sender<String>) -> Result<Vec<Player>> {
    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<Item = String> + '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(())
}