diff --git a/src/commands/admin.rs b/src/commands/admin.rs index 8b13789..fc92662 100644 --- a/src/commands/admin.rs +++ b/src/commands/admin.rs @@ -1 +1,97 @@ +use crate::debugln; +use serenity::{ + framework::standard::{ + macros::{command, group}, + CommandResult, + }, + model::prelude::*, + prelude::*, +}; +#[group] +#[commands(ban, kick)] +pub struct Admin; + +#[command] +#[description = "Kick an user"] +#[only_in(guilds)] +#[required_permissions("KICK_MEMBERS")] +async fn kick(ctx: &Context, msg: &Message) -> CommandResult { + // TODO CACHE ? + if let Some(sender_member) = msg.member(ctx).await { + for user in &msg.mentions { + if let Some(member) = ctx.cache.member(msg.guild_id.unwrap(), user.id).await { + debugln!("Kicking {:?}", user); + let kick = if let Some(role_member) = sender_member.highest_role_info(ctx).await { + if let Some(role) = member.highest_role_info(ctx).await { + role_member.1 > role.1 + } else { + true + } + } else { + true + }; + if kick { + member.kick(ctx).await?; + } else { + msg.channel_id + .say( + ctx, + &format!( + "Error, you can't kick {}", + if let Some(nick) = member.nick { + nick + } else { + user.name.clone() + } + ), + ) + .await?; + } + } + } + } + Ok(()) +} + +#[command] +#[description = "Ban an user"] +#[only_in(guilds)] +#[required_permissions("BAN_MEMBERS")] +async fn ban(ctx: &Context, msg: &Message) -> CommandResult { + // TODO CACHE ? + if let Some(sender_member) = msg.member(ctx).await { + for user in &msg.mentions { + if let Some(member) = ctx.cache.member(msg.guild_id.unwrap(), user.id).await { + debugln!("Kicking {:?}", user); + let kick = if let Some(role_member) = sender_member.highest_role_info(ctx).await { + if let Some(role) = member.highest_role_info(ctx).await { + role_member.1 > role.1 + } else { + true + } + } else { + true + }; + if kick { + member.ban(ctx, 0).await?; + } else { + msg.channel_id + .say( + ctx, + &format!( + "Error, you can't ban {}", + if let Some(nick) = member.nick { + nick + } else { + user.name.clone() + } + ), + ) + .await?; + } + } + } + } + Ok(()) +} diff --git a/src/commands/general.rs b/src/commands/general.rs index 482bc9f..3b5125c 100644 --- a/src/commands/general.rs +++ b/src/commands/general.rs @@ -1,6 +1,7 @@ -use crate::{api, debugln}; +use crate::{api, debugln, ShardManagerContainer}; use futures::StreamExt; use serenity::{ + client::bridge::gateway::ShardId, framework::standard::{ macros::{command, group}, ArgError, Args, CommandResult, @@ -10,7 +11,7 @@ use serenity::{ }; #[group] -#[commands(longcode, image, older, ping, invite, infos, error, send_message)] +#[commands(error, image, infos, invite, latency, longcode, older, ping)] pub struct General; #[command] @@ -19,6 +20,45 @@ pub async fn error(_ctx: &Context, _msg: &Message) -> CommandResult { Ok(()) } +#[command] +async fn latency(ctx: &Context, msg: &Message) -> CommandResult { + // The shard manager is an interface for mutating, stopping, restarting, and + // retrieving information about shards. + let data = ctx.data.read().await; + + let shard_manager = match data.get::() { + Some(v) => v, + None => { + let _ = msg + .reply(ctx, "There was a problem getting the shard manager") + .await; + + return Ok(()); + } + }; + + let manager = shard_manager.lock().await; + let runners = manager.runners.lock().await; + + // Shards are backed by a "shard runner" responsible for processing events + // over the shard, so we'll get the information about the shard runner for + // the shard this command was sent over. + let runner = match runners.get(&ShardId(ctx.shard_id)) { + Some(runner) => runner, + None => { + let _ = msg.reply(ctx, "No shard found"); + + return Ok(()); + } + }; + + let _ = msg + .reply(ctx, &format!("The shard latency is {:?}", runner.latency)) + .await; + + Ok(()) +} + #[command] #[description = "Split a huge code"] #[bucket = "longcode"] @@ -66,6 +106,58 @@ async fn _longcode(ctx: &Context, msg: &Message, mut args: Args) -> crate::comma Ok(()) } +#[command] +#[description = "Print bot infos"] +async fn infos(ctx: &Context, msg: &Message) -> CommandResult { + let current_user = ctx.http.get_current_user().await; + msg.channel_id + .send_message(ctx, |m| { + m.embed(|e| { + e.title(format!( + "{} v{}, by {}", + option_env!("CARGO_PKG_NAME").unwrap_or("unknown"), + option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"), + option_env!("CARGO_PKG_AUTHORS").unwrap_or("unknows") + )) + .description(format!( + concat!( + "Features : {:?}\n", + "Mode : {}\n", + "Source code : https://git.oupsman.fr/oupson/discord_rusty_bot" + ), + get_features(), + if cfg!(debug_assertions) { + "debug" + } else { + "release" + }, + )) + .colour((247, 76, 0)) + .footer(|f| { + if let Ok(user) = ¤t_user { + let mut f = f.text(&user.name); + if let Some(avatar) = user.avatar_url() { + f = f.icon_url(avatar); + } + f + } else { + f + } + }) + }) + }) + .await?; + Ok(()) +} + +fn get_features<'m>() -> Vec<&'m str> { + let mut res = Vec::new(); + if cfg!(feature = "music") { + res.push("music"); + } + res +} + #[command] #[description("Print the bot invite link")] async fn invite(ctx: &Context, msg: &Message) -> CommandResult { @@ -82,22 +174,6 @@ async fn invite(ctx: &Context, msg: &Message) -> CommandResult { Ok(()) } -#[command] -#[description("Pong !")] -async fn ping(ctx: &Context, msg: &Message) -> CommandResult { - let now = std::time::Instant::now(); - msg.channel_id.say(&ctx.http, "Pong!").await?; - let elapsed = now.elapsed(); - msg.channel_id - .say( - &ctx.http, - format!("Time elapsed : {}ms", elapsed.as_millis()), - ) - .await?; - - Ok(()) -} - #[command] #[description = "Find who is the older"] pub async fn older(ctx: &Context, msg: &Message, args: Args) -> CommandResult { @@ -153,55 +229,19 @@ async fn _older(ctx: &Context, msg: &Message, mut args: Args) -> crate::commands } #[command] -#[description = "Print bot infos"] -async fn infos(ctx: &Context, msg: &Message) -> CommandResult { - let current_user = ctx.http.get_current_user().await; +#[description("Pong !")] +async fn ping(ctx: &Context, msg: &Message) -> CommandResult { + let now = std::time::Instant::now(); + msg.channel_id.say(&ctx.http, "Pong!").await?; + let elapsed = now.elapsed(); msg.channel_id - .send_message(ctx, |m| { - m.embed(|e| { - e.title(format!( - "{} v{}, by {}", - option_env!("CARGO_PKG_NAME").unwrap_or("unknown"), - option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"), - option_env!("CARGO_PKG_AUTHORS").unwrap_or("unknows") - )) - .description(format!( - concat!( - "Features : {:?}\n", - "Mode : {}\n", - "Source code : https://git.oupsman.fr/oupson/discord_rusty_bot" - ), - get_features(), - if cfg!(debug_assertions) { - "debug" - } else { - "release" - }, - )) - .colour((247, 76, 0)) - .footer(|f| { - if let Ok(user) = ¤t_user { - let mut f = f.text(&user.name); - if let Some(avatar) = user.avatar_url() { - f = f.icon_url(avatar); - } - f - } else { - f - } - }) - }) - }) + .say( + &ctx.http, + format!("Time elapsed : {}ms", elapsed.as_millis()), + ) .await?; - Ok(()) -} -fn get_features<'m>() -> Vec<&'m str> { - let mut res = Vec::new(); - if cfg!(feature = "music") { - res.push("music"); - } - res + Ok(()) } #[command] @@ -288,13 +328,3 @@ impl std::str::FromStr for Image { } } } - -#[command] -#[owners_only] -async fn send_message(ctx: &Context, _msg: &Message, mut args: Args) -> CommandResult { - let channel_id = args.single::()?; - let message = args.single::()?; - debugln!("Send {} into {:?}", message, channel_id); - channel_id.say(ctx, message).await?; - Ok(()) -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 5cd84a6..bee744f 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod admin; pub(crate) mod general; +pub(crate) mod owner; pub(crate) mod roulette; #[cfg(feature = "music")] diff --git a/src/commands/owner.rs b/src/commands/owner.rs new file mode 100644 index 0000000..35b3d00 --- /dev/null +++ b/src/commands/owner.rs @@ -0,0 +1,32 @@ +use crate::debugln; +use serenity::{ + framework::standard::{ + macros::{command, group}, + Args, CommandResult, + }, + model::prelude::*, + prelude::*, +}; +use std::convert::TryFrom; + +#[group] +#[commands(leave, send_message)] +pub(crate) struct Owner; + +#[command] +#[owners_only] +pub async fn leave(ctx: &Context, _msg: &Message, mut args: Args) -> CommandResult { + let guild = GuildId::try_from(args.single::()?)?; + guild.leave(&ctx.http).await?; + Ok(()) +} + +#[command] +#[owners_only] +async fn send_message(ctx: &Context, _msg: &Message, mut args: Args) -> CommandResult { + let channel_id = args.single::()?; + let message = args.single::()?; + debugln!("Send {} into {:?}", message, channel_id); + channel_id.say(ctx, message).await?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e6209b5..6e073b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ use crate::commands::{ + admin::ADMIN_GROUP, general::GENERAL_GROUP, + owner::OWNER_GROUP, roulette::{BulletsContainer, NonKickGuildsContainer, ROULETTE_GROUP}, }; use async_trait::async_trait; @@ -111,8 +113,10 @@ async fn main() -> IoResult<()> { .on_dispatch_error(dispatch_error) .after(after_hook) .help(&MY_HELP) + .group(&ADMIN_GROUP) .group(&GENERAL_GROUP) - .group(&ROULETTE_GROUP); + .group(&ROULETTE_GROUP) + .group(&OWNER_GROUP); #[cfg(feature = "music")] { @@ -294,14 +298,29 @@ async fn normal_message(_ctx: &Context, msg: &Message) { #[hook] async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError) { debugln!("Dispatch error : {:?}", error); - if let DispatchError::Ratelimited(seconds) = error { - let _ = msg - .channel_id - .say( - &ctx.http, - &format!("Try this again in {} seconds.", seconds), - ) - .await; + match error { + DispatchError::Ratelimited(seconds) => { + let _ = msg + .channel_id + .say( + &ctx.http, + &format!("Try this again in {} seconds.", seconds), + ) + .await; + } + DispatchError::OnlyForOwners => { + let _ = msg + .channel_id + .say(&ctx.http, "Error : only the owner can call this command") + .await; + } + DispatchError::LackingPermissions(perms) => { + let _ = msg + .channel_id + .say(&ctx.http, &format!("Error, only {:?} can do that", perms)) + .await; + } + _ => {} } }