diff --git a/Cargo.lock b/Cargo.lock index 5d0a985..99099f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ name = "affy" version = "0.1.0" dependencies = [ "affluences-api", + "async-minecraft-ping", "chrono", "poise", "tokio", @@ -97,6 +98,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-minecraft-ping" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b459c14dd8d9ef21e296af3f2a3651ff7dc3536e092fb0b09e528daaa6d89" +dependencies = [ + "async-trait", + "serde", + "serde_json", + "thiserror", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.68" diff --git a/Cargo.toml b/Cargo.toml index d77de7b..0a82e38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ tokio = { version = "1.28.1", features = ["full"] } chrono = "*" uuid = "*" poise = "0.5.5" +async-minecraft-ping = "0.8.0" diff --git a/Dockerfile b/Dockerfile index 130951a..b2cbacc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,8 @@ COPY --from=builder /build/dumb-init /build/affy/target/release/affy /bin/ WORKDIR /data +ENV TZ=Europe/Brussels + USER www-data:www-data ENTRYPOINT ["/bin/dumb-init", "--"] diff --git a/src/commands.rs b/src/commands/affluence.rs similarity index 75% rename from src/commands.rs rename to src/commands/affluence.rs index dc74e0c..60b8730 100644 --- a/src/commands.rs +++ b/src/commands/affluence.rs @@ -1,30 +1,11 @@ use crate::{Context, Error}; + use chrono::NaiveDate; use uuid::{uuid, Uuid}; const STERRE_BIB_ID: Uuid = uuid!("4737e57a-ee05-4f7b-901a-7bb541eeb297"); const TIME_FORMAT: &str = "%H:%M"; -/// Show this help menu -#[poise::command(prefix_command, track_edits, slash_command)] -pub async fn help( - ctx: Context<'_>, - #[description = "Specific command to show help about"] - #[autocomplete = "poise::builtins::autocomplete_command"] - command: Option, -) -> Result<(), Error> { - poise::builtins::help( - ctx, - command.as_deref(), - poise::builtins::HelpConfiguration { - extra_text_at_bottom: "This is an example bot made to showcase features of my custom Discord bot framework", - ..Default::default() - }, - ) - .await?; - Ok(()) -} - /// List available timeslots for day #[poise::command(prefix_command, slash_command)] pub async fn available(ctx: Context<'_>, date: NaiveDate) -> Result<(), Error> { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs new file mode 100644 index 0000000..fbbedd9 --- /dev/null +++ b/src/commands/minecraft.rs @@ -0,0 +1,47 @@ +use crate::{Context, Error}; +use async_minecraft_ping::ServerDescription; + +const DEFAULT_SERVER: &str = "rustybever.be"; + +/// Ping a minecraft server +#[poise::command(prefix_command, slash_command)] +pub async fn ping_mc( + ctx: Context<'_>, + #[description = "Address of the server"] address: Option, + #[description = "Port the server runs on"] port: Option, +) -> Result<(), Error> { + let mut full_name = address.unwrap_or(DEFAULT_SERVER.to_string()); + let mut builder = async_minecraft_ping::ConnectionConfig::build(&full_name); + + if let Some(port) = port { + builder = builder.with_port(port); + full_name += &format!(":{}", port); + } + + let conn = builder.connect().await?; + let status = conn.status().await?.status; + + let description = match status.description { + ServerDescription::Plain(s) => s, + ServerDescription::Object { text } => text, + }; + + ctx.send(|f| { + f.embed(|e| { + e.description(format!("Server information for {}", full_name)) + .field("version", status.version.name, false) + .field("description", description, false) + .field( + "players", + format!( + "{} of {} player(s) online", + status.players.online, status.players.max + ), + false, + ) + }) + }) + .await?; + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..24ca959 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,28 @@ +mod affluence; +mod minecraft; + +use crate::{Context, Data, Error}; + +pub fn commands() -> Vec> { + vec![help(), affluence::available(), minecraft::ping_mc()] +} + +/// Show this help menu +#[poise::command(prefix_command, track_edits, slash_command)] +pub async fn help( + ctx: Context<'_>, + #[description = "Specific command to show help about"] + #[autocomplete = "poise::builtins::autocomplete_command"] + command: Option, +) -> Result<(), Error> { + poise::builtins::help( + ctx, + command.as_deref(), + poise::builtins::HelpConfiguration { + extra_text_at_bottom: "This is an example bot made to showcase features of my custom Discord bot framework", + ..Default::default() + }, + ) + .await?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index b18e13e..c052b07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ mod commands; use affluences_api::AffluencesClient; use poise::serenity_prelude as serenity; -use std::{collections::HashMap, env::var, sync::Mutex, time::Duration}; +use std::{env::var, time::Duration}; // Types used by all command functions type Error = Box; @@ -10,7 +10,6 @@ type Context<'a> = poise::Context<'a, Data, Error>; // Custom user data passed to all command functions pub struct Data { - votes: Mutex>, client: AffluencesClient, } @@ -36,7 +35,7 @@ async fn main() { // FrameworkOptions contains all of poise's configuration option in one struct // Every option can be omitted to use its default value let options = poise::FrameworkOptions { - commands: vec![commands::help(), commands::available()], + commands: commands::commands(), prefix_options: poise::PrefixFrameworkOptions { prefix: Some("~".into()), edit_tracker: Some(poise::EditTracker::for_timespan(Duration::from_secs(3600))), @@ -91,7 +90,6 @@ async fn main() { println!("Logged in as {}", _ready.user.name); poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { - votes: Mutex::new(HashMap::new()), client: AffluencesClient::new(), }) })