silly goober bot

feat: moderation commands

+272
+1
src/commands/mod.rs
··· 1 1 pub mod bot; 2 2 pub mod fun; 3 3 pub mod misc; 4 + pub mod moderation; 4 5 pub mod user;
+73
src/commands/moderation/ban.rs
··· 1 + use crate::Result; 2 + use poise::serenity_prelude::all::User; 3 + 4 + use crate::types::Context; 5 + 6 + #[poise::command(slash_command, guild_only, required_permissions = "BAN_MEMBERS")] 7 + pub async fn ban( 8 + ctx: Context<'_>, 9 + user: User, 10 + delete_messages_day_count: Option<u8>, 11 + reason: Option<String>, 12 + ) -> Result<()> { 13 + let guild = ctx 14 + .serenity_context() 15 + .http 16 + .get_guild(ctx.guild_id().unwrap()) 17 + .await?; 18 + 19 + let user_member = guild.member(ctx, user.id).await.unwrap(); 20 + let bot_member = guild.member(ctx, ctx.framework().bot_id).await.unwrap(); 21 + let author_member = guild.member(ctx, ctx.author()).await.unwrap(); 22 + 23 + let user_highest_role = user_member 24 + .roles 25 + .iter() 26 + .filter_map(|role| guild.roles.get(role)) 27 + .max_by_key(|role| role.position); 28 + 29 + let author_highest_role = author_member 30 + .roles 31 + .iter() 32 + .filter_map(|role| guild.roles.get(role)) 33 + .max_by_key(|role| role.position); 34 + 35 + let bot_highest_role = bot_member 36 + .roles 37 + .iter() 38 + .filter_map(|role| guild.roles.get(role)) 39 + .max_by_key(|role| role.position); 40 + 41 + if !guild 42 + .user_permissions_in( 43 + &ctx.guild_channel().await.unwrap(), 44 + &guild.member(ctx, ctx.framework().bot_id).await.unwrap(), 45 + ) 46 + .ban_members() 47 + { 48 + ctx.say("Bot missing permission: ``Ban Members``").await?; 49 + return Ok(()); 50 + } 51 + 52 + if author_highest_role < user_highest_role { 53 + ctx.say("User has higher role then you.").await?; 54 + return Ok(()); 55 + } 56 + 57 + if bot_highest_role < user_highest_role { 58 + ctx.say("User has higher role then bot!").await?; 59 + return Ok(()); 60 + } 61 + 62 + guild 63 + .member(ctx, user.id) 64 + .await? 65 + .ban_with_reason( 66 + ctx, 67 + delete_messages_day_count.unwrap_or(0), 68 + &reason.unwrap_or("No reason provided.".to_string()), 69 + ) 70 + .await?; 71 + 72 + Ok(()) 73 + }
+63
src/commands/moderation/kick.rs
··· 1 + use crate::types::Context; 2 + use color_eyre::Result; 3 + use poise::serenity_prelude::all::User; 4 + 5 + #[poise::command(slash_command, guild_only, required_permissions = "KICK_MEMBERS")] 6 + pub async fn kick(ctx: Context<'_>, user: User, reason: Option<String>) -> Result<()> { 7 + let guild = ctx 8 + .serenity_context() 9 + .http 10 + .get_guild(ctx.guild_id().unwrap()) 11 + .await?; 12 + 13 + let user_member = guild.member(ctx, user.id).await.unwrap(); 14 + let bot_member = guild.member(ctx, ctx.framework().bot_id).await.unwrap(); 15 + let author_member = guild.member(ctx, ctx.author()).await.unwrap(); 16 + 17 + let user_highest_role = user_member 18 + .roles 19 + .iter() 20 + .filter_map(|role| guild.roles.get(role)) 21 + .max_by_key(|role| role.position); 22 + 23 + let author_highest_role = author_member 24 + .roles 25 + .iter() 26 + .filter_map(|role| guild.roles.get(role)) 27 + .max_by_key(|role| role.position); 28 + 29 + let bot_highest_role = bot_member 30 + .roles 31 + .iter() 32 + .filter_map(|role| guild.roles.get(role)) 33 + .max_by_key(|role| role.position); 34 + 35 + if !guild 36 + .user_permissions_in( 37 + &ctx.guild_channel().await.unwrap(), 38 + &guild.member(ctx, ctx.framework().bot_id).await.unwrap(), 39 + ) 40 + .kick_members() 41 + { 42 + ctx.say("Bot missing permission: ``Kick Members``").await?; 43 + return Ok(()); 44 + } 45 + 46 + if author_highest_role < user_highest_role { 47 + ctx.say("User has higher role then you.").await?; 48 + return Ok(()); 49 + } 50 + 51 + if bot_highest_role < user_highest_role { 52 + ctx.say("User has higher role then bot!").await?; 53 + return Ok(()); 54 + } 55 + 56 + guild 57 + .member(ctx, user.id) 58 + .await? 59 + .kick_with_reason(ctx, &reason.unwrap_or("No reason provided.".to_string())) 60 + .await?; 61 + 62 + Ok(()) 63 + }
+3
src/commands/moderation/mod.rs
··· 1 + pub mod ban; 2 + pub mod kick; 3 + pub mod timeout;
+128
src/commands/moderation/timeout.rs
··· 1 + use crate::types::Context; 2 + use color_eyre::Result; 3 + use poise::serenity_prelude::all::{EditMember, Timestamp, User}; 4 + use regex::Regex; 5 + 6 + #[poise::command(slash_command, guild_only, required_permissions = "MODERATE_MEMBERS")] 7 + pub async fn timeout( 8 + ctx: Context<'_>, 9 + user: User, 10 + #[description = "how long the time out is for. (5s, 2m, 12h, 3d, 2w)"] duration: String, 11 + reason: Option<String>, 12 + ) -> Result<()> { 13 + let guild = ctx 14 + .serenity_context() 15 + .http 16 + .get_guild(ctx.guild_id().unwrap()) 17 + .await?; 18 + 19 + let timeout_max_value = 2419200; 20 + 21 + let user_member = guild.member(ctx, user.id).await.unwrap(); 22 + let bot_member = guild.member(ctx, ctx.framework().bot_id).await.unwrap(); 23 + let author_member = guild.member(ctx, ctx.author()).await.unwrap(); 24 + 25 + let user_highest_role = user_member 26 + .roles 27 + .iter() 28 + .filter_map(|role| guild.roles.get(role)) 29 + .max_by_key(|role| role.position); 30 + 31 + let author_highest_role = author_member 32 + .roles 33 + .iter() 34 + .filter_map(|role| guild.roles.get(role)) 35 + .max_by_key(|role| role.position); 36 + 37 + let bot_highest_role = bot_member 38 + .roles 39 + .iter() 40 + .filter_map(|role| guild.roles.get(role)) 41 + .max_by_key(|role| role.position); 42 + 43 + if guild 44 + .user_permissions_in( 45 + &ctx.guild_channel().await.unwrap(), 46 + &guild.member(ctx, user.id).await.unwrap(), 47 + ) 48 + .administrator() 49 + { 50 + ctx.say("Target user has permission: ``Administrator``") 51 + .await?; 52 + return Ok(()); 53 + } 54 + 55 + if !guild 56 + .user_permissions_in( 57 + &ctx.guild_channel().await.unwrap(), 58 + &guild.member(ctx, ctx.framework().bot_id).await.unwrap(), 59 + ) 60 + .moderate_members() 61 + { 62 + ctx.say("Bot missing permission: ``Moderate Members``") 63 + .await?; 64 + return Ok(()); 65 + } 66 + 67 + if author_highest_role < user_highest_role { 68 + ctx.say("User has higher role then you.").await?; 69 + return Ok(()); 70 + } 71 + 72 + if bot_highest_role < user_highest_role { 73 + ctx.say("User has higher role then bot!").await?; 74 + return Ok(()); 75 + } 76 + 77 + let Some(seconds) = parse_duration(&duration) else { 78 + ctx.say("Invalid duration! (Valid durations: 5s, 2m, 12h, 3d, 2w)") 79 + .await?; 80 + return Ok(()); 81 + }; 82 + 83 + if seconds > timeout_max_value { 84 + ctx.say("Duration too long! (Max duration: 28d)").await?; 85 + return Ok(()); 86 + } 87 + 88 + let timeout_until = 89 + Timestamp::from_unix_timestamp(Timestamp::now().unix_timestamp() + seconds as i64)?; 90 + 91 + guild 92 + .member(ctx, user.id) 93 + .await? 94 + .edit( 95 + ctx, 96 + EditMember::new() 97 + .disable_communication_until(timeout_until.to_string()) 98 + .audit_log_reason(&reason.unwrap_or("No reason provided.".to_string())), 99 + ) 100 + .await?; 101 + 102 + ctx.say(format!("Timed out <@{}> for {}!", user.id, duration)) 103 + .await?; 104 + 105 + Ok(()) 106 + } 107 + 108 + fn parse_duration(input: &str) -> Option<u64> { 109 + let re = Regex::new(r"(\d+)([smhdw])").unwrap(); 110 + 111 + if let Some(caps) = re.captures(input) { 112 + let value: u64 = caps[1].parse().ok()?; 113 + let unit = &caps[2]; 114 + 115 + let seconds = match unit { 116 + "s" => value, 117 + "m" => value * 60, 118 + "h" => value * 3600, 119 + "d" => value * 86400, 120 + "w" => value * 604800, 121 + _ => return None, 122 + }; 123 + 124 + Some(seconds) 125 + } else { 126 + None 127 + } 128 + }
+4
src/main.rs
··· 35 35 // misc commands 36 36 commands::misc::nixpkgs::nixpkgs(), 37 37 commands::misc::crates::crates(), 38 + // moderation commands 39 + commands::moderation::ban::ban(), 40 + commands::moderation::kick::kick(), 41 + commands::moderation::timeout::timeout(), 38 42 // fun commands 39 43 commands::fun::nix::nix(), 40 44 commands::fun::chance::roll(),