From 52ce33dcd75cca89e502c0c44cf23740399f6a5e Mon Sep 17 00:00:00 2001 From: Christopher Rose Date: Sun, 14 Aug 2022 21:54:28 +0530 Subject: [PATCH] [add] server helping --- README.md | 7 ++- src/commands.rs | 123 ++++++++++++++++++++++++++++++------------- src/data.rs | 4 ++ src/main.rs | 22 +++++--- src/server_helper.rs | 38 +++++++++++++ 5 files changed, 148 insertions(+), 46 deletions(-) create mode 100644 src/server_helper.rs diff --git a/README.md b/README.md index a76fb99..40107a4 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ A smol discord bot to help start a server, without needing to ssh in. - [X] `start` - Start the server - [X] `status` - Get the server status -- [ ] `help` - Get commands -- [ ] `stop` - Stop the server +- [X] `help` - Get commands +- [X] `stop` - Stop the server ## Env vars @@ -15,4 +15,7 @@ A smol discord bot to help start a server, without needing to ssh in. DISCORD_TOKEN=xyz ENV_PATH=/home/atreyab/server RUNSCRIPT=./start.sh + +GLOBAL_RUST_LOG=warn +RUST_LOG=trace ``` \ No newline at end of file diff --git a/src/commands.rs b/src/commands.rs index e57a668..4aa6be4 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,41 +1,54 @@ -use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::macros::{command, help}; +use serenity::framework::standard::{ + help_commands, Args, CommandGroup, CommandResult, HelpOptions, +}; use serenity::model::channel::Message; +use serenity::model::prelude::UserId; use serenity::prelude::*; +use log::{debug, error, trace, warn, info}; +use std::collections::HashSet; use std::env::{self, set_current_dir}; +use std::io::Write; use std::path::Path; -use std::process::Command; +use std::process::{Command, Stdio}; use crate::data::Serv; +use crate::server_helper::poll_child; + +#[help] +#[individual_command_tip = "Hello!\n\n\ +If you want more information about a specific command, just pass the command as argument."] +#[strikethrough_commands_tip_in_guild = ""] +pub async fn my_help( + context: &Context, + msg: &Message, + args: Args, + help_options: &'static HelpOptions, + groups: &[&'static CommandGroup], + owners: HashSet, +) -> CommandResult { + let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await; + Ok(()) +} #[command] +#[description("Get a pong message")] pub async fn ping(ctx: &Context, msg: &Message) -> CommandResult { msg.reply(ctx, "Pong!").await?; - + debug!("Ping command reply for {}", msg.author); Ok(()) } #[command] +#[description("Query the status of the minecraft server")] pub async fn status(ctx: &Context, msg: &Message) -> CommandResult { let child_status = { - let mut x = ctx.data.write().await; - let mut command = x.get_mut::().expect("Could not unwrap data"); - - match &mut command.child_process { - Some(process) => { - let res_return = process.try_wait(); - match res_return { - Ok(Some(_exit_code)) => true, - Ok(None) => false, - Err(_) => true, - } - } - None => false, - } + let mut command_lock = ctx.data.write().await; + !poll_child(&mut command_lock).await }; - // x. + msg.reply( ctx, format!( @@ -49,30 +62,68 @@ pub async fn status(ctx: &Context, msg: &Message) -> CommandResult { } #[command] +#[description("Start the minecraft server")] pub async fn start(ctx: &Context, msg: &Message) -> CommandResult { let path = env::var("ENV_PATH").expect("ENV_PATH env var missing"); let script = env::var("RUNSCRIPT").expect("RUNSCRIPT env var missing"); + + let mut command_lock = ctx.data.write().await; + let has_exit = poll_child(&mut command_lock).await; set_current_dir(Path::new(path.as_str())).expect("Could not change directory"); + + if has_exit{ + let child = { + Command::new(script.as_str()) + // .args(["-jar", "server.jar"]) + .stdin(Stdio::piped()) + .spawn() + }; + match child { + Ok(command_child) => { + // do it in a block so the lock releases outside the block + + command_lock.insert::(Serv::new_with_child(command_child)); + + // x. + msg.reply(ctx, format!("Started server!",)).await?; + trace!("Started minecraft server"); + } + Err(err) => { + msg.reply(ctx, format!("Could not start! Refer logs",)) + .await?; + error!("Could not start server: {}", err.to_string()); + } + }; - let child = { - Command::new(script.as_str()) - // .args(["-jar", "server.jar"]) - .spawn() - }; + }else{ + info!("Tried to start server when already running!"); + msg.reply(ctx, "The server is already running").await?; + } - if let Ok(command_child) = child { - // do it in a block so the lock releases outside the block - { - let mut command_lock = ctx.data.write().await; - command_lock.insert::(Serv::new_with_child(command_child)); - command_lock.downgrade() - }; - // x. - msg.reply(ctx, format!("Started server!",)).await?; + Ok(()) +} + +#[command] +#[description = "Stop the minecraft server"] +pub async fn stop(ctx: &Context, msg: &Message) -> CommandResult { + let mut command_lock = ctx.data.write().await; + let running = poll_child(&mut command_lock).await; + let x = command_lock + .get_mut::() + .and_then(|x| x.child_process.as_mut()); + + if !running { + match x { + Some(child) => { + let stdin = child.stdin.as_mut().unwrap(); + stdin.write_all(b"stop\n").unwrap(); + // drop(stdin); + } + None => {} + } + msg.reply(ctx, "Stopping the server").await?; } else { - msg.reply(ctx, format!("Could not start! Refer logs",)) - .await?; + msg.reply(ctx, "The server is already stopped").await?; } - Ok(()) } diff --git a/src/data.rs b/src/data.rs index 4eaf1bd..d2c28b0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -2,15 +2,19 @@ use std::process::Child; use serenity::prelude::TypeMapKey; +/// Contains the data maintained by the bot pub struct Serv { + /// Server child process pub child_process: Option, } impl Serv { + /// Creates a new instance, childless pub fn new() -> Self { Serv { child_process: None, } } + /// create with a child pub fn new_with_child(x: Child) -> Self { Serv { child_process: Some(x), diff --git a/src/main.rs b/src/main.rs index a563e66..206dca3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use serenity::framework::standard::StandardFramework; use serenity::model::channel::Message; use serenity::model::prelude::{Activity, Ready}; use serenity::prelude::*; - +use log::{warn ,error,info,trace}; use std::env; mod commands; @@ -13,8 +13,11 @@ use commands::*; mod data; use data::*; +mod server_helper; + #[group] -#[commands(ping, start, status)] +#[commands(ping, start, status,stop)] +#[only_in(guilds)] struct General; struct Handler; @@ -27,28 +30,31 @@ impl EventHandler for Handler { serenity::model::user::OnlineStatus::Online, ) .await; - println!("Ready!") + info!("Ready to accept commands"); } } #[hook] async fn unrecognised_command_hook(_ctx: &Context, msg: &Message, unrecognised_command_name: &str) { - println!( - "A user named {:?} tried to execute an unknown command: {}", - msg.author.name, unrecognised_command_name - ); + warn!("A user named {:?} tried to execute an unknown command: {}", + msg.author.name, unrecognised_command_name); + } #[tokio::main] async fn main() { dotenv::dotenv().ok(); + sensible_env_logger::init!(); + let framework = StandardFramework::new() .configure(|c| c.prefix("~")) // set the bot's prefix to "~" + .help(&MY_HELP) .unrecognised_command(unrecognised_command_hook) .group(&GENERAL_GROUP); // Login with a bot token from the environment let token = env::var("DISCORD_TOKEN").expect("token"); + trace!("Got a token from environment"); let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; let mut client = Client::builder(token, intents) .event_handler(Handler) @@ -59,7 +65,7 @@ async fn main() { // start listening for events by starting a single shard if let Err(why) = client.start().await { - println!("An error occurred while running the client: {:?}", why); + error!("An error occurred while running the client: {:?}", why); } } diff --git a/src/server_helper.rs b/src/server_helper.rs new file mode 100644 index 0000000..ef0d7bd --- /dev/null +++ b/src/server_helper.rs @@ -0,0 +1,38 @@ +use log::{debug, warn}; +use serenity::prelude::TypeMap; +use tokio::sync::RwLockWriteGuard; + +use crate::data::Serv; + +/// get whether child has exit +pub async fn poll_child<'a>(x: &mut RwLockWriteGuard<'a, TypeMap>) -> bool { + let child_status = { + let command = x.get_mut::().expect("Could not unwrap data"); + + match &mut command.child_process { + Some(process) => { + debug!("Waiting for child process"); + let res_return = process.try_wait(); + match res_return { + Ok(Some(_exit_code)) => { + debug!("Exit code {}", _exit_code); + true + } + Ok(None) => { + debug!("Server has not exit yet"); + false + } + Err(e) => { + warn!("Could not wait for child process: {}", e.to_string()); + true + } + } + } + None => { + debug!("No child process for status command to query"); + true + } + } + }; + child_status +}