-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from Panini-Devs/0.0.1-staging-changes
moderation prototyping
- Loading branch information
Showing
9 changed files
with
330 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,119 @@ | ||
use crate::{ | ||
utilities::{ | ||
messages, models, | ||
modlog::{self, ModType}, | ||
}, | ||
Context, Error, | ||
}; | ||
|
||
use chrono::Utc; | ||
use poise::serenity_prelude::UserId; | ||
use tracing::{error, info}; | ||
|
||
#[poise::command( | ||
prefix_command, | ||
slash_command, | ||
category = "Moderator", | ||
required_permissions = "BAN_MEMBERS", | ||
required_bot_permissions = "BAN_MEMBERS | SEND_MESSAGES", | ||
guild_only, | ||
ephemeral | ||
)] | ||
pub async fn ban( | ||
context: Context<'_>, | ||
#[description = "The user to ban."] | ||
#[rename = "user"] | ||
user_id: UserId, | ||
#[description = "Reason for the ban."] | ||
#[max_length = 80] | ||
reason: Option<String>, | ||
) -> Result<(), Error> { | ||
let database = &context.data().sqlite; | ||
|
||
let user = models::user(context, user_id).await?; | ||
|
||
let moderator = context.author(); | ||
let moderator_id = moderator.id; | ||
|
||
if user.system { | ||
let reply = messages::error_reply("Cannot ban a system user.", false); | ||
context.send(reply).await?; | ||
return Ok(()); | ||
} | ||
|
||
if user_id == moderator_id { | ||
let reply = messages::error_reply("Sorry, but you cannot ban yourself.", true); | ||
context.send(reply).await?; | ||
|
||
return Ok(()); | ||
} | ||
|
||
let reason = reason.unwrap_or_else(|| "No reason provided.".to_string()); | ||
|
||
let reason_char_count = reason.chars().count(); | ||
if reason_char_count > 80 { | ||
let reply = messages::info_reply("Reason must be no more than 80 characters long.", true); | ||
context.send(reply).await?; | ||
|
||
return Ok(()); | ||
} | ||
|
||
let result = { | ||
let (user_name, user_mention) = (&user.name, models::user_mention(context, user_id).await?); | ||
|
||
let (moderator_name, moderator_mention) = | ||
(&moderator.name, models::author_mention(context)?); | ||
|
||
let (guild_id, guild_name) = { | ||
let guild_id = context.guild_id().unwrap(); | ||
let guild = context.guild().unwrap(); | ||
(guild_id, guild.name.clone()) | ||
}; | ||
|
||
let created_at = Utc::now().naive_utc(); | ||
|
||
let mut user_mod_history = modlog::select_modlog_from_users(&user_id, database).await?; | ||
|
||
let message = messages::info_message(format!( | ||
"You've been banned from {guild_name} by {moderator_mention} for {reason}.", | ||
)); | ||
let dm = user.direct_message(context, message).await; | ||
|
||
if let Err(why) = dm { | ||
error!("Couldn't send DM to @{user_name}: {why:?}"); | ||
} | ||
|
||
match guild_id.ban_with_reason(context, user_id, 0, &reason).await { | ||
Ok(_) => { | ||
modlog::insert_modlog( | ||
ModType::Ban, | ||
&guild_id, | ||
&user_id, | ||
&moderator_id, | ||
&reason, | ||
created_at, | ||
database, | ||
) | ||
.await?; | ||
|
||
user_mod_history += 1; | ||
|
||
modlog::update_users_set_modlog(&user_id, user_mod_history, database).await?; | ||
|
||
info!("@{moderator_name} banned @{user_name} from {guild_name}: {reason}"); | ||
Ok(format!("{user_mention} has been banned.")) | ||
} | ||
Err(why) => { | ||
error!("Couldn't ban @{user_name}: {why:?}"); | ||
Err(format!("Sorry, but I couldn't ban {user_mention}.")) | ||
} | ||
} | ||
}; | ||
|
||
if let Err(why) = result { | ||
let reply = messages::error_reply(&why, true); | ||
context.send(reply).await?; | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use poise::CreateReply; | ||
use serenity::builder::{ | ||
CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, | ||
}; | ||
|
||
use super::embeds; | ||
|
||
pub async fn error_response( | ||
message: impl Into<String>, | ||
ephemeral: bool, | ||
) -> CreateInteractionResponse { | ||
let embed = embeds::error_message_embed(&message.into()); | ||
|
||
let response_message = CreateInteractionResponseMessage::new() | ||
.embed(embed) | ||
.ephemeral(ephemeral); | ||
CreateInteractionResponse::Message(response_message) | ||
} | ||
|
||
pub fn info_message(message: impl Into<String>) -> CreateMessage { | ||
let embed = embeds::info_message_embed(&message.into()); | ||
|
||
CreateMessage::default().embed(embed) | ||
} | ||
|
||
pub fn error_reply(message: impl Into<String>, ephemeral: bool) -> CreateReply { | ||
let embed = embeds::error_message_embed(&message.into()); | ||
|
||
CreateReply::default().embed(embed).ephemeral(ephemeral) | ||
} | ||
|
||
pub fn info_reply(message: impl Into<String>, ephemeral: bool) -> CreateReply { | ||
let embed = embeds::info_message_embed(&message.into()); | ||
|
||
CreateReply::default().embed(embed).ephemeral(ephemeral) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
pub mod embeds; | ||
pub mod event_handler; | ||
pub mod git; | ||
pub mod messages; | ||
pub mod models; | ||
pub mod modlog; | ||
pub mod paginate; | ||
pub mod types; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use crate::Context; | ||
use poise::serenity_prelude::{model::ModelError, User, UserId}; | ||
use serenity::all::{Mention, Mentionable}; | ||
use tracing::error; | ||
|
||
pub fn author(context: Context<'_>) -> Result<&User, ModelError> { | ||
Ok(context.author()) | ||
} | ||
|
||
pub fn author_mention(context: Context<'_>) -> Result<Mention, ModelError> { | ||
let author = author(context)?; | ||
Ok(author.mention()) | ||
} | ||
|
||
pub async fn user(context: Context<'_>, user_id: UserId) -> Result<User, ModelError> { | ||
match user_id.to_user(context).await { | ||
Ok(user) => Ok(user), | ||
Err(why) => { | ||
error!("Couldn't get user: {why:?}"); | ||
return Err(ModelError::MemberNotFound); | ||
} | ||
} | ||
} | ||
|
||
pub async fn user_mention(context: Context<'_>, user_id: UserId) -> Result<Mention, ModelError> { | ||
Ok(user(context, user_id).await?.mention()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
use poise::serenity_prelude as serenity; | ||
use uuid::Uuid; | ||
|
||
use crate::Context; | ||
|
||
/// Paginates a list of embeds using UUID as custom ID to identify the buttons. | ||
pub async fn paginate<U, E>( | ||
ctx: Context<'_>, | ||
pages: Vec<serenity::all::CreateEmbed>, | ||
) -> Result<(), serenity::Error> { | ||
// Define some unique identifiers for the navigation buttons | ||
let ctx_id = Uuid::new_v4(); | ||
let prev_button_id = format!("{}prev", ctx_id); | ||
let next_button_id = format!("{}next", ctx_id); | ||
|
||
// Send the embed with the first page as content | ||
let reply = { | ||
let components = serenity::CreateActionRow::Buttons(vec![ | ||
serenity::CreateButton::new(&prev_button_id).emoji('◀'), | ||
serenity::CreateButton::new(&next_button_id).emoji('▶'), | ||
]); | ||
|
||
poise::CreateReply::default() | ||
.embed(pages[0].clone()) | ||
.components(vec![components]) | ||
}; | ||
|
||
ctx.send(reply).await?; | ||
|
||
// Loop through incoming interactions with the navigation buttons | ||
let mut current_page = 0; | ||
while let Some(press) = serenity::collector::ComponentInteractionCollector::new(ctx) | ||
// We defined our button IDs to start with `ctx_id`. If they don't, some other command's | ||
// button was pressed | ||
.filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string())) | ||
// Timeout when no navigation button has been pressed for 24 hours | ||
.timeout(std::time::Duration::from_secs(3600 * 24)) | ||
.await | ||
{ | ||
// Depending on which button was pressed, go to next or previous page | ||
if press.data.custom_id == next_button_id { | ||
current_page += 1; | ||
if current_page >= pages.len() { | ||
current_page = 0; | ||
} | ||
} else if press.data.custom_id == prev_button_id { | ||
current_page = current_page.checked_sub(1).unwrap_or(pages.len() - 1); | ||
} else { | ||
// This is an unrelated button interaction | ||
continue; | ||
} | ||
|
||
// Update the message with the new page contents | ||
press | ||
.create_response( | ||
ctx.serenity_context(), | ||
serenity::CreateInteractionResponse::UpdateMessage( | ||
serenity::CreateInteractionResponseMessage::new() | ||
.embed(pages[current_page].clone()), | ||
), | ||
) | ||
.await?; | ||
} | ||
|
||
Ok(()) | ||
} |