Goodbye moderation

We'll probably keep zeppelin / move back to Lily -- however the reminders seem to currently be broken :/
This commit is contained in:
TheKodeToad 2024-01-08 17:17:21 +00:00 committed by seth
parent e0fea8d23e
commit f2979d4cde
No known key found for this signature in database
GPG key ID: D31BD0D494BBEE86
7 changed files with 1 additions and 559 deletions

View file

@ -4,7 +4,6 @@ use color_eyre::eyre::Report;
use poise::Command;
mod general;
pub mod moderation;
pub fn to_global_commands() -> Vec<Command<Data, Report>> {
vec![
@ -15,8 +14,5 @@ pub fn to_global_commands() -> Vec<Command<Data, Report>> {
general::say(),
general::stars(),
general::tag(),
moderation::ban_user(),
moderation::mass_ban(),
moderation::kick_user(),
]
}

View file

@ -1,238 +0,0 @@
use crate::{consts::COLORS, Context};
use async_trait::async_trait;
use color_eyre::eyre::{eyre, Context as _, Result};
use log::*;
use poise::serenity_prelude::{CacheHttp, GuildId, Http, Timestamp, User};
type Fields<'a> = Vec<(&'a str, String, bool)>;
#[async_trait]
pub trait ModActionInfo {
fn to_fields(&self) -> Option<Fields>;
fn description(&self) -> String;
async fn run_action(
&self,
http: impl CacheHttp + AsRef<Http>,
user: &User,
guild_id: &GuildId,
reason: String,
) -> Result<()>;
}
pub struct ModAction<T>
where
T: ModActionInfo,
{
pub reason: Option<String>,
pub data: T,
}
impl<T: ModActionInfo> ModAction<T> {
fn get_all_fields(&self) -> Fields {
let mut fields = vec![];
if let Some(reason) = self.reason.clone() {
fields.push(("Reason:", reason, false));
}
if let Some(mut action_fields) = self.data.to_fields() {
fields.append(&mut action_fields);
}
fields
}
/// internal mod logging
pub async fn log_action(&self, ctx: &Context<'_>) -> Result<()> {
let channel_id = ctx
.data()
.config
.discord
.channels
.say_log_channel_id
.ok_or_else(|| eyre!("Couldn't find say_log_channel_id! Unable to log mod action"))?;
let channel = ctx
.http()
.get_channel(channel_id.into())
.await
.wrap_err_with(|| "Couldn't resolve say_log_channel_id as a Channel! Are you sure you sure you used the right one?")?;
let channel = channel
.guild()
.ok_or_else(|| eyre!("Couldn't resolve say_log_channel_id as a GuildChannel! Are you sure you used the right one?"))?;
let fields = self.get_all_fields();
let title = format!("{} user!", self.data.description());
channel
.send_message(ctx, |m| {
m.embed(|e| e.title(title).fields(fields).color(COLORS["red"]))
})
.await?;
Ok(())
}
/// public facing message
pub async fn reply(&self, ctx: &Context<'_>, user: &User, dm_user: Option<bool>) -> Result<()> {
let mut resp = format!("{} {}!", self.data.description(), user.name);
if dm_user.unwrap_or_default() {
resp = format!("{resp} (user notified with direct message)");
}
ctx.reply(resp).await?;
Ok(())
}
pub async fn dm_user(&self, ctx: &Context<'_>, user: &User, guild_id: &GuildId) -> Result<()> {
let guild = ctx.http().get_guild(*guild_id.as_u64()).await?;
let title = format!("{} from {}!", self.data.description(), guild.name);
user.dm(ctx, |m| {
m.embed(|e| {
e.title(title).color(COLORS["red"]);
if let Some(reason) = &self.reason {
e.description(format!("Reason: {}", reason));
}
e
})
})
.await?;
Ok(())
}
pub async fn handle(
&self,
ctx: &Context<'_>,
user: &User,
quiet: Option<bool>,
dm_user: Option<bool>,
handle_reply: bool,
) -> Result<()> {
if quiet.unwrap_or_default() {
ctx.defer_ephemeral().await?;
} else {
ctx.defer().await?;
}
let guild_id = ctx
.guild_id()
.ok_or_else(|| eyre!("Couldn't get GuildId for context!"))?;
let actual_reason = self.reason.clone().unwrap_or("".to_string());
if dm_user.unwrap_or_default() {
self.dm_user(ctx, user, &guild_id).await?;
}
self.data
.run_action(ctx, user, &guild_id, actual_reason)
.await?;
self.log_action(ctx).await?;
if handle_reply {
self.reply(ctx, user, dm_user).await?;
}
Ok(())
}
}
pub struct Ban {
pub purge_messages_days: u8,
}
#[async_trait]
impl ModActionInfo for Ban {
fn to_fields(&self) -> Option<Fields> {
let fields = vec![(
"Purged messages:",
format!("Last {} day(s)", self.purge_messages_days),
false,
)];
Some(fields)
}
fn description(&self) -> String {
"Banned".to_string()
}
async fn run_action(
&self,
http: impl CacheHttp + AsRef<Http>,
user: &User,
guild_id: &GuildId,
reason: String,
) -> Result<()> {
debug!("Banning user {user} with reason: \"{reason}\"");
guild_id
.ban_with_reason(http, user, self.purge_messages_days, reason)
.await?;
Ok(())
}
}
pub struct Timeout {
pub time_until: Timestamp,
}
#[async_trait]
impl ModActionInfo for Timeout {
fn to_fields(&self) -> Option<Fields> {
let fields = vec![("Timed out until:", self.time_until.to_string(), false)];
Some(fields)
}
fn description(&self) -> String {
"Timed out".to_string()
}
#[allow(unused_variables)]
async fn run_action(
&self,
http: impl CacheHttp + AsRef<Http>,
user: &User,
guild_id: &GuildId,
reason: String,
) -> Result<()> {
todo!()
}
}
pub struct Kick {}
#[async_trait]
impl ModActionInfo for Kick {
fn to_fields(&self) -> Option<Fields> {
None
}
fn description(&self) -> String {
"Kicked".to_string()
}
async fn run_action(
&self,
http: impl CacheHttp + AsRef<Http>,
user: &User,
guild_id: &GuildId,
reason: String,
) -> Result<()> {
debug!("Kicked user {user} with reason: \"{reason}\"");
guild_id.kick_with_reason(http, user, &reason).await?;
Ok(())
}
}

View file

@ -1,166 +0,0 @@
use crate::Context;
use std::error::Error;
use color_eyre::eyre::{eyre, Result};
use poise::serenity_prelude::{ArgumentConvert, ChannelId, GuildId, User};
pub mod actions;
use actions::{Ban, Kick, ModAction};
async fn split_argument<T>(
ctx: &Context<'_>,
guild_id: Option<GuildId>,
channel_id: Option<ChannelId>,
list: String,
) -> Result<Vec<T>>
where
T: ArgumentConvert,
T::Err: Error + Send + Sync + 'static,
{
// yes i should be using something like `filter_map()` here. async closures
// are unstable though so woooooo
let mut res: Vec<T> = vec![];
for item in list.split(',') {
let item = T::convert(ctx.serenity_context(), guild_id, channel_id, item.trim()).await?;
res.push(item);
}
Ok(res)
}
/// Ban a user
#[poise::command(
slash_command,
prefix_command,
guild_only,
default_member_permissions = "BAN_MEMBERS",
required_permissions = "BAN_MEMBERS",
aliases("ban")
)]
pub async fn ban_user(
ctx: Context<'_>,
#[description = "User to ban"] user: User,
#[description = "Reason to ban"] reason: Option<String>,
#[description = "Number of days to purge their messages from (defaults to 0)"]
purge_messages_days: Option<u8>,
#[description = "If true, the reply from the bot will be ephemeral"] quiet: Option<bool>,
#[description = "If true, the affected user will be sent a DM"] dm_user: Option<bool>,
) -> Result<()> {
let dmd = purge_messages_days.unwrap_or(0);
let action = ModAction {
reason,
data: Ban {
purge_messages_days: dmd,
},
};
action.handle(&ctx, &user, quiet, dm_user, true).await?;
Ok(())
}
/// Ban multiple users
#[poise::command(
slash_command,
prefix_command,
guild_only,
default_member_permissions = "BAN_MEMBERS",
required_permissions = "BAN_MEMBERS",
aliases("ban_multi")
)]
pub async fn mass_ban(
ctx: Context<'_>,
#[description = "Comma separated list of users to ban"] users: String,
#[description = "Reason to ban"] reason: Option<String>,
#[description = "Number of days to purge their messages from (defaults to 0)"]
purge_messages_days: Option<u8>,
#[description = "If true, the reply from the bot will be ephemeral"] quiet: Option<bool>,
#[description = "If true, the affected user will be sent a DM"] dm_user: Option<bool>,
) -> Result<()> {
let gid = ctx
.guild_id()
.ok_or_else(|| eyre!("Couldn't get GuildId!"))?;
let dmd = purge_messages_days.unwrap_or(0);
let users: Vec<User> = split_argument(&ctx, Some(gid), None, users).await?;
for user in &users {
let action = ModAction {
reason: reason.clone(),
data: Ban {
purge_messages_days: dmd,
},
};
action.handle(&ctx, user, quiet, dm_user, false).await?;
}
let resp = format!("{} users banned!", users.len());
ctx.reply(resp).await?;
Ok(())
}
/// Kick a user
#[poise::command(
slash_command,
prefix_command,
guild_only,
default_member_permissions = "KICK_MEMBERS",
required_permissions = "KICK_MEMBERS",
aliases("kick")
)]
pub async fn kick_user(
ctx: Context<'_>,
#[description = "User to kick"] user: User,
#[description = "Reason to kick"] reason: Option<String>,
#[description = "If true, the reply from the bot will be ephemeral"] quiet: Option<bool>,
#[description = "If true, the affected user will be sent a DM"] dm_user: Option<bool>,
) -> Result<()> {
let action = ModAction {
reason,
data: Kick {},
};
action.handle(&ctx, &user, quiet, dm_user, true).await?;
Ok(())
}
/// Kick multiple users
#[poise::command(
slash_command,
prefix_command,
guild_only,
default_member_permissions = "KICK_MEMBERS",
required_permissions = "KICK_MEMBERS",
aliases("multi_kick")
)]
pub async fn mass_kick(
ctx: Context<'_>,
#[description = "Comma separated list of users to kick"] users: String,
#[description = "Reason to kick"] reason: Option<String>,
#[description = "If true, the reply from the bot will be ephemeral"] quiet: Option<bool>,
#[description = "If true, the affected user will be sent a DM"] dm_user: Option<bool>,
) -> Result<()> {
let gid = ctx
.guild_id()
.ok_or_else(|| eyre!("Couldn't get GuildId!"))?;
let users: Vec<User> = split_argument(&ctx, Some(gid), None, users).await?;
for user in &users {
let action = ModAction {
reason: reason.clone(),
data: Kick {},
};
action.handle(&ctx, user, quiet, dm_user, false).await?;
}
let resp = format!("{} users kicked!", users.len());
ctx.reply(resp).await?;
Ok(())
}