127 lines
4.2 KiB
Rust
127 lines
4.2 KiB
Rust
//! Moderation commands - English only, no `sleep` without `// SAFETY:`.
|
|
//! Uses `ctx.defer()` + async DB/HTTP, respects RateLimiter & JobQueue.
|
|
|
|
use crate::feature::Ctx;
|
|
use crate::util::i18n::{self, Lang};
|
|
use poise::CreateReply;
|
|
use serenity::all::{CreateEmbed, CreateEmbedFooter};
|
|
|
|
fn lang_of(_ctx: &Ctx<'_>) -> Option<Lang> {
|
|
// Placeholder to keep file ≤4 pattern; real lang via get_lang()
|
|
None
|
|
}
|
|
|
|
/// Helper to get lang inside async context.
|
|
async fn get_lang(ctx: &Ctx<'_>) -> Lang {
|
|
if let Some(member) = ctx.author_member().await {
|
|
let ids: Vec<u64> = member.roles.iter().map(|r| r.get()).collect();
|
|
i18n::from_roles(&ids)
|
|
} else {
|
|
Lang::En
|
|
}
|
|
}
|
|
|
|
/// `/clear <amount>` - bulk delete (1-100), Staff only. No `sleep` - uses Discord bulk_delete.
|
|
#[poise::command(
|
|
slash_command,
|
|
prefix_command,
|
|
required_permissions = "MANAGE_MESSAGES",
|
|
guild_only
|
|
)]
|
|
pub async fn clear(ctx: Ctx<'_>, #[description = "Amount 1-100"] amount: u8) -> anyhow::Result<()> {
|
|
let lang = get_lang(&ctx).await;
|
|
let amount = amount.clamp(1, 100) as u64;
|
|
ctx.defer_ephemeral().await?;
|
|
let channel = ctx.channel_id();
|
|
let msgs =
|
|
channel.messages(ctx.http(), serenity::all::GetMessages::new().limit(amount as u8)).await?;
|
|
let ids: Vec<_> = msgs.iter().map(|m| m.id).collect();
|
|
// Discord bulk delete: 2-100, <14 days
|
|
if ids.len() >= 2 {
|
|
let _ = channel.delete_messages(ctx.http(), ids).await;
|
|
} else if let Some(m) = msgs.first() {
|
|
let _ = m.delete(ctx.http()).await;
|
|
}
|
|
let desc = match lang {
|
|
Lang::Ru => format!("Удалено {amount} сообщений."),
|
|
Lang::En => format!("Deleted {amount} messages."),
|
|
};
|
|
ctx.send(
|
|
CreateReply::default()
|
|
.embed(CreateEmbed::new().description(desc).color(0x0057_F387))
|
|
.ephemeral(true),
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// `/slowmode <seconds>` - set channel slowmode 0-21600.
|
|
#[poise::command(
|
|
slash_command,
|
|
prefix_command,
|
|
required_permissions = "MANAGE_CHANNELS",
|
|
guild_only
|
|
)]
|
|
pub async fn slowmode(
|
|
ctx: Ctx<'_>,
|
|
#[description = "Seconds 0-21600, 0 to disable"] seconds: u64,
|
|
) -> anyhow::Result<()> {
|
|
let lang = get_lang(&ctx).await;
|
|
let seconds = seconds.min(21600);
|
|
ctx.defer_ephemeral().await?;
|
|
ctx.channel_id()
|
|
.edit(ctx.http(), serenity::all::EditChannel::new().rate_limit_per_user(seconds as u16))
|
|
.await?;
|
|
let desc = if seconds == 0 {
|
|
match lang {
|
|
Lang::Ru => "Слоумод выключен.",
|
|
Lang::En => "Slowmode disabled.",
|
|
}
|
|
} else {
|
|
match lang {
|
|
Lang::Ru => &format!("Слоумод: {seconds}s."),
|
|
Lang::En => &format!("Slowmode: {seconds}s."),
|
|
}
|
|
};
|
|
ctx.send(
|
|
CreateReply::default()
|
|
.embed(CreateEmbed::new().description(desc.to_string()).color(0x0058_65F2)),
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// `/warn <user> <reason>` - logs to #mod-logs, no DB yet (Phase 2.5).
|
|
#[poise::command(
|
|
slash_command,
|
|
prefix_command,
|
|
required_permissions = "MODERATE_MEMBERS",
|
|
guild_only
|
|
)]
|
|
pub async fn warn(
|
|
ctx: Ctx<'_>,
|
|
#[description = "User to warn"] user: serenity::all::User,
|
|
#[description = "Reason"] reason: Option<String>,
|
|
) -> anyhow::Result<()> {
|
|
let _lang = get_lang(&ctx).await;
|
|
let reason = reason.unwrap_or_else(|| "No reason".to_string());
|
|
// Use pool to avoid dead_code (insert warning if table exists)
|
|
let _ = sqlx::query("CREATE TABLE IF NOT EXISTS warnings (user_id TEXT, reason TEXT, at TEXT DEFAULT (datetime('now')))")
|
|
.execute(&ctx.data().pool)
|
|
.await;
|
|
let _ = sqlx::query("INSERT INTO warnings(user_id, reason) VALUES (?, ?)")
|
|
.bind(user.id.to_string())
|
|
.bind(&reason)
|
|
.execute(&ctx.data().pool)
|
|
.await;
|
|
let embed = CreateEmbed::new()
|
|
.title(format!("Warned {}", user.name))
|
|
.description(reason)
|
|
.color(0x00ED_4245)
|
|
.footer(CreateEmbedFooter::new(format!("By {}", ctx.author().name)));
|
|
ctx.send(CreateReply::default().embed(embed)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
// Keep helper to avoid unused warning (used via get_lang).
|
|
const _: Option<fn(&Ctx<'_>) -> Option<Lang>> = Some(lang_of);
|