Files
shadowbot/src/utils/moderation_sanction_helpers.rs
T

187 lines
5.1 KiB
Rust

use std::time::Duration;
use chrono::Utc;
use serenity::builder::EditMember;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::admin_common::parse_user_id;
use crate::db::{self, DbPoolKey};
pub fn duration_from_input(input: &str) -> Option<Duration> {
let raw = input.trim().to_lowercase();
if raw.is_empty() {
return None;
}
let mut number = String::new();
let mut suffix = String::new();
for ch in raw.chars() {
if ch.is_ascii_digit() {
if !suffix.is_empty() {
return None;
}
number.push(ch);
} else if !ch.is_whitespace() {
suffix.push(ch);
}
}
let value = number.parse::<u64>().ok()?;
let secs = match suffix.as_str() {
"s" | "sec" | "secs" | "seconde" | "secondes" => value,
"m" | "min" | "mins" | "minute" | "minutes" => value * 60,
"h" | "heure" | "heures" => value * 3600,
"j" | "d" | "jour" | "jours" => value * 86400,
_ => return None,
};
Some(Duration::from_secs(secs.max(1)))
}
pub async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}
pub async fn add_sanction(
ctx: &Context,
guild_id: GuildId,
user_id: UserId,
moderator_id: UserId,
kind: &str,
reason: &str,
channel_id: Option<ChannelId>,
expires_at: Option<chrono::DateTime<Utc>>,
) {
let Some(pool) = pool(ctx).await else {
return;
};
let bot_id = ctx.cache.current_user().id;
let _ = sqlx::query(
r#"
INSERT INTO bot_sanctions
(bot_id, guild_id, user_id, moderator_id, kind, reason, channel_id, expires_at, active)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, TRUE);
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(user_id.get() as i64)
.bind(moderator_id.get() as i64)
.bind(kind)
.bind(reason)
.bind(channel_id.map(|c| c.get() as i64))
.bind(expires_at)
.execute(&pool)
.await;
}
pub async fn parse_targets(raw: &str) -> Vec<UserId> {
let mut out = Vec::new();
for token in raw.split(',') {
if let Some(uid) = parse_user_id(token.trim()) {
out.push(uid);
}
}
out
}
pub async fn handle_timeout(
ctx: &Context,
guild_id: GuildId,
users: &[UserId],
expires: Option<chrono::DateTime<Utc>>,
) -> usize {
let settings = if let Some(pool) = pool(ctx).await {
let bot_id = ctx.cache.current_user().id.get() as i64;
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64)
.await
.ok()
} else {
None
};
let mute_role_id = settings
.as_ref()
.and_then(|s| s.mute_role_id)
.and_then(|raw| u64::try_from(raw).ok())
.map(RoleId::new);
let use_timeout = settings
.as_ref()
.map(|s| s.use_timeout || s.mute_role_id.is_none())
.unwrap_or(true);
let mut done = 0usize;
for user_id in users {
if let Ok(mut member) = guild_id.member(&ctx.http, *user_id).await {
if use_timeout {
let mut builder = EditMember::new();
if let Some(ts) = expires {
if let Ok(discord_ts) = Timestamp::from_unix_timestamp(ts.timestamp()) {
builder = builder.disable_communication_until_datetime(discord_ts);
}
} else {
builder = builder.enable_communication();
}
if member.edit(&ctx.http, builder).await.is_ok() {
done += 1;
}
} else {
let Some(role_id) = mute_role_id else {
continue;
};
let result = if expires.is_some() {
member.add_role(&ctx.http, role_id).await
} else {
member.remove_role(&ctx.http, role_id).await
};
if result.is_ok() {
done += 1;
}
}
}
}
done
}
pub async fn channel_mute_users(
ctx: &Context,
channel_id: ChannelId,
users: &[UserId],
mute: bool,
) -> usize {
let mut done = 0usize;
for user_id in users {
let result = if mute {
channel_id
.create_permission(
&ctx.http,
PermissionOverwrite {
allow: Permissions::empty(),
deny: Permissions::SEND_MESSAGES
| Permissions::ADD_REACTIONS
| Permissions::SPEAK,
kind: PermissionOverwriteType::Member(*user_id),
},
)
.await
} else {
channel_id
.delete_permission(&ctx.http, PermissionOverwriteType::Member(*user_id))
.await
};
if result.is_ok() {
done += 1;
}
}
done
}