mirror of
https://github.com/arthur-pbty/shadowbot.git
synced 2026-06-03 15:07:37 +02:00
feat(moderation): add commands for anti-raid reset, mute role setting, spam overrides, strikes management, and timeout toggling
- Implemented `resetantiraide` command to reset anti-raid protections to default settings. - Added `set_muterole` command to define the mute role when timeout mode is disabled. - Created `spam` command to manage spam moderation channel overrides (allow, deny, reset). - Developed `strikes` command to display and modify strike rules for various triggers. - Introduced `timeout` command to toggle the use of Discord timeout for mutes. feat(outils): add piconly command to manage photo-only channels - Implemented `piconly` command to define or remove channels where only photos can be sent. - Added functionality to enforce photo-only rules in designated channels. feat(roles): add ancien and noderank commands for role management - Created `ancien` command to set up a role for members after a specified delay. - Implemented `noderank` command to manage protected roles that are not removed by derank actions.
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use chrono::Utc;
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::commands::moderation_sanction_helpers::{add_sanction, handle_timeout};
|
||||
use crate::db::{
|
||||
self, DbPoolKey, ModerationSettings, PunishRule, count_member_strikes_in_window,
|
||||
ensure_default_punish_rules, get_last_punish_triggered_at, upsert_last_punish_triggered_at,
|
||||
};
|
||||
use crate::permissions;
|
||||
|
||||
static SPAM_TRACKER: OnceLock<Mutex<HashMap<(u64, u64, u64), VecDeque<Instant>>>> = OnceLock::new();
|
||||
|
||||
pub async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
pub fn parse_on_off(input: &str) -> Option<bool> {
|
||||
match input.trim().to_lowercase().as_str() {
|
||||
"on" | "enable" | "enabled" | "true" | "1" => Some(true),
|
||||
"off" | "disable" | "disabled" | "false" | "0" => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_duration_to_seconds(input: &str) -> Option<i64> {
|
||||
let raw = input.trim().to_lowercase();
|
||||
if raw.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut digits = String::new();
|
||||
let mut suffix = String::new();
|
||||
|
||||
for ch in raw.chars() {
|
||||
if ch.is_ascii_digit() {
|
||||
if !suffix.is_empty() {
|
||||
return None;
|
||||
}
|
||||
digits.push(ch);
|
||||
} else if !ch.is_whitespace() {
|
||||
suffix.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
let value = digits.parse::<i64>().ok()?;
|
||||
if value <= 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let unit = if suffix.is_empty() { "s" } else { &suffix };
|
||||
let seconds = match unit {
|
||||
"s" | "sec" | "secs" | "seconde" | "secondes" => value,
|
||||
"m" | "min" | "mins" | "minute" | "minutes" => value.checked_mul(60)?,
|
||||
"h" | "heure" | "heures" => value.checked_mul(3_600)?,
|
||||
"j" | "d" | "jour" | "jours" => value.checked_mul(86_400)?,
|
||||
"w" | "sem" | "semaine" | "semaines" => value.checked_mul(604_800)?,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(seconds.max(1))
|
||||
}
|
||||
|
||||
pub fn format_duration(mut seconds: i64) -> String {
|
||||
seconds = seconds.max(1);
|
||||
let days = seconds / 86_400;
|
||||
seconds %= 86_400;
|
||||
let hours = seconds / 3_600;
|
||||
seconds %= 3_600;
|
||||
let minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
|
||||
let mut out = Vec::new();
|
||||
if days > 0 {
|
||||
out.push(format!("{}j", days));
|
||||
}
|
||||
if hours > 0 {
|
||||
out.push(format!("{}h", hours));
|
||||
}
|
||||
if minutes > 0 {
|
||||
out.push(format!("{}m", minutes));
|
||||
}
|
||||
if seconds > 0 || out.is_empty() {
|
||||
out.push(format!("{}s", seconds));
|
||||
}
|
||||
|
||||
out.join(" ")
|
||||
}
|
||||
|
||||
pub fn parse_rate_limit(input: &str) -> Option<(i32, i32)> {
|
||||
let mut parts = input.splitn(2, '/');
|
||||
let limit = parts.next()?.trim().parse::<i32>().ok()?.max(1);
|
||||
let duration = parse_duration_to_seconds(parts.next()?.trim())?;
|
||||
if duration > i32::MAX as i64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((limit, duration as i32))
|
||||
}
|
||||
|
||||
pub fn parse_trigger(input: &str) -> Option<&'static str> {
|
||||
match input.trim().to_lowercase().as_str() {
|
||||
"spam" | "antispam" => Some("spam"),
|
||||
"link" | "antilink" => Some("link"),
|
||||
"massmention" | "antimassmention" | "mention" | "mentions" => Some("massmention"),
|
||||
"badword" | "badwords" | "mauvaismot" | "motinterdit" => Some("badword"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_profile(input: Option<&str>) -> Option<&'static str> {
|
||||
let raw = input?.trim().to_lowercase();
|
||||
match raw.as_str() {
|
||||
"ancien" | "old" => Some("old"),
|
||||
"nouveau" | "new" => Some("new"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sanction(input: &str) -> Option<&'static str> {
|
||||
match input.trim().to_lowercase().as_str() {
|
||||
"warn" | "avert" => Some("warn"),
|
||||
"mute" | "timeout" => Some("mute"),
|
||||
"kick" => Some("kick"),
|
||||
"ban" => Some("ban"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_channel_override(global_enabled: bool, override_mode: Option<&str>) -> bool {
|
||||
match override_mode {
|
||||
Some(mode) if mode.eq_ignore_ascii_case("allow") => false,
|
||||
Some(mode) if mode.eq_ignore_ascii_case("deny") => true,
|
||||
_ => global_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_invite_link(content: &str) -> bool {
|
||||
let lower = content.to_lowercase();
|
||||
lower.contains("discord.gg/")
|
||||
|| lower.contains("discord.com/invite/")
|
||||
|| lower.contains("discordapp.com/invite/")
|
||||
}
|
||||
|
||||
fn contains_any_link(content: &str) -> bool {
|
||||
let lower = content.to_lowercase();
|
||||
lower.contains("http://")
|
||||
|| lower.contains("https://")
|
||||
|| lower.contains("www.")
|
||||
|| lower.contains("discord.gg/")
|
||||
}
|
||||
|
||||
fn spam_hit(bot_id: u64, guild_id: u64, user_id: u64, limit: i32, window_seconds: i32) -> bool {
|
||||
let lock = SPAM_TRACKER.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
let mut tracker = lock.lock().expect("spam tracker lock poisoned");
|
||||
|
||||
let key = (bot_id, guild_id, user_id);
|
||||
let now = Instant::now();
|
||||
let window = Duration::from_secs(window_seconds.max(1) as u64);
|
||||
let queue = tracker.entry(key).or_insert_with(VecDeque::new);
|
||||
|
||||
queue.push_back(now);
|
||||
|
||||
while let Some(oldest) = queue.front() {
|
||||
if now.duration_since(*oldest) > window {
|
||||
let _ = queue.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queue.len() > limit.max(1) as usize
|
||||
}
|
||||
|
||||
async fn user_profile(
|
||||
ctx: &Context,
|
||||
pool: &sqlx::PgPool,
|
||||
bot_id: i64,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
) -> &'static str {
|
||||
let Ok(old_settings) =
|
||||
db::get_or_create_old_member_settings(pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return "new";
|
||||
};
|
||||
|
||||
if !old_settings.enabled {
|
||||
return "new";
|
||||
}
|
||||
|
||||
let Some(role_id_raw) = old_settings.role_id else {
|
||||
return "new";
|
||||
};
|
||||
|
||||
let Ok(member) = guild_id.member(&ctx.http, user_id).await else {
|
||||
return "new";
|
||||
};
|
||||
|
||||
if member
|
||||
.roles
|
||||
.iter()
|
||||
.any(|role_id| role_id.get() as i64 == role_id_raw)
|
||||
{
|
||||
"old"
|
||||
} else {
|
||||
"new"
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_rule(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
user_id: UserId,
|
||||
rule: &PunishRule,
|
||||
settings: &ModerationSettings,
|
||||
) -> String {
|
||||
let sanction = rule.sanction.to_lowercase();
|
||||
let bot_user_id = ctx.cache.current_user().id;
|
||||
|
||||
if sanction == "warn" {
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"warn",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return "warn".to_string();
|
||||
}
|
||||
|
||||
if sanction == "mute" || sanction == "timeout" {
|
||||
let duration = rule
|
||||
.sanction_seconds
|
||||
.unwrap_or(3_600)
|
||||
.clamp(1, 28 * 24 * 3_600);
|
||||
let expires = Some(Utc::now() + chrono::Duration::seconds(duration));
|
||||
let _ = handle_timeout(ctx, guild_id, &[user_id], expires).await;
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"tempmute",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
expires,
|
||||
)
|
||||
.await;
|
||||
if settings.use_timeout {
|
||||
return format!("timeout {}", format_duration(duration));
|
||||
}
|
||||
return format!("mute role {}", format_duration(duration));
|
||||
}
|
||||
|
||||
if sanction == "kick" {
|
||||
let result = guild_id
|
||||
.kick_with_reason(&ctx.http, user_id, "AutoMod: seuil de strikes atteint")
|
||||
.await;
|
||||
|
||||
if result.is_ok() {
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"kick",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return "kick".to_string();
|
||||
}
|
||||
|
||||
return "kick (echec)".to_string();
|
||||
}
|
||||
|
||||
if sanction == "ban" {
|
||||
let result = guild_id
|
||||
.ban_with_reason(&ctx.http, user_id, 0, "AutoMod: seuil de strikes atteint")
|
||||
.await;
|
||||
|
||||
if result.is_ok() {
|
||||
add_sanction(
|
||||
ctx,
|
||||
guild_id,
|
||||
user_id,
|
||||
bot_user_id,
|
||||
"ban",
|
||||
"AutoMod: seuil de strikes atteint.",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
return "ban".to_string();
|
||||
}
|
||||
|
||||
return "ban (echec)".to_string();
|
||||
}
|
||||
|
||||
"aucune".to_string()
|
||||
}
|
||||
|
||||
async fn apply_violation(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
pool: &sqlx::PgPool,
|
||||
settings: &ModerationSettings,
|
||||
trigger: &str,
|
||||
reason: &str,
|
||||
) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = msg.delete(&ctx.http).await;
|
||||
|
||||
let bot_id = settings.bot_id;
|
||||
let guild_id_raw = settings.guild_id;
|
||||
let user_id = msg.author.id;
|
||||
let profile = user_profile(ctx, pool, bot_id, guild_id, user_id).await;
|
||||
|
||||
let strikes = db::get_strike_rule(pool, bot_id, guild_id_raw, trigger, profile)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(1)
|
||||
.max(0);
|
||||
|
||||
if strikes > 0 {
|
||||
let _ = db::add_member_strike_event(
|
||||
pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
user_id.get() as i64,
|
||||
trigger,
|
||||
strikes,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let _ = ensure_default_punish_rules(pool, bot_id, guild_id_raw).await;
|
||||
let rules = db::list_punish_rules(pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut action = String::from("aucune");
|
||||
for rule in rules.iter().rev() {
|
||||
let Ok(total) = count_member_strikes_in_window(
|
||||
pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
user_id.get() as i64,
|
||||
rule.window_seconds,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if total < rule.threshold as i64 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let recent_trigger =
|
||||
get_last_punish_triggered_at(pool, bot_id, guild_id_raw, user_id.get() as i64, rule.id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|at| Utc::now() - at < chrono::Duration::seconds(rule.window_seconds))
|
||||
.unwrap_or(false);
|
||||
|
||||
if recent_trigger {
|
||||
continue;
|
||||
}
|
||||
|
||||
action = execute_rule(ctx, guild_id, user_id, rule, settings).await;
|
||||
let _ = upsert_last_punish_triggered_at(
|
||||
pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
user_id.get() as i64,
|
||||
rule.id,
|
||||
)
|
||||
.await;
|
||||
break;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("AutoMod")
|
||||
.description(format!(
|
||||
"{}\nMembre: <@{}>\nTrigger: `{}` · Profil: `{}` · Strikes: `+{}`\nAction: `{}`",
|
||||
reason,
|
||||
user_id.get(),
|
||||
trigger,
|
||||
profile,
|
||||
strikes,
|
||||
action
|
||||
))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub async fn enforce_automod_message(ctx: &Context, msg: &Message) -> bool {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings =
|
||||
match db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await {
|
||||
Ok(settings) => settings,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let channel_id = msg.channel_id.get() as i64;
|
||||
let spam_override = db::get_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
channel_id,
|
||||
"spam",
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let link_override = db::get_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
channel_id,
|
||||
"link",
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let antispam_enabled =
|
||||
apply_channel_override(settings.antispam_enabled, spam_override.as_deref());
|
||||
let antilink_enabled =
|
||||
apply_channel_override(settings.antilink_enabled, link_override.as_deref());
|
||||
|
||||
if settings.badwords_enabled {
|
||||
let content = msg.content.to_lowercase();
|
||||
let badwords = db::list_badwords(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if badwords
|
||||
.iter()
|
||||
.any(|word| !word.is_empty() && content.contains(word))
|
||||
{
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"badword",
|
||||
"Message supprime: mot interdit detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if settings.antimassmention_enabled {
|
||||
let mention_count = msg.mentions.len() + msg.mention_roles.len();
|
||||
if mention_count >= settings.antimassmention_limit.max(1) as usize {
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"massmention",
|
||||
"Message supprime: spam de mentions detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if antilink_enabled {
|
||||
let link_hit = if settings.antilink_mode.eq_ignore_ascii_case("all") {
|
||||
contains_any_link(&msg.content)
|
||||
} else {
|
||||
contains_invite_link(&msg.content)
|
||||
};
|
||||
|
||||
if link_hit {
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"link",
|
||||
"Message supprime: lien interdit detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if antispam_enabled {
|
||||
let hit = spam_hit(
|
||||
ctx.cache.current_user().id.get(),
|
||||
guild_id.get(),
|
||||
msg.author.id.get(),
|
||||
settings.antispam_limit.max(1),
|
||||
settings.antispam_window_seconds.max(1),
|
||||
);
|
||||
|
||||
if hit {
|
||||
apply_violation(
|
||||
ctx,
|
||||
msg,
|
||||
&pool,
|
||||
&settings,
|
||||
"spam",
|
||||
"Message supprime: spam detecte.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn public_command_allowed(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
command_key: &str,
|
||||
required_permission: u8,
|
||||
) -> bool {
|
||||
if permissions::is_owner_user(ctx, msg.author.id).await {
|
||||
return true;
|
||||
}
|
||||
|
||||
if required_permission > 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings =
|
||||
match db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await {
|
||||
Ok(settings) => settings,
|
||||
Err(_) => return true,
|
||||
};
|
||||
|
||||
let override_mode = db::get_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
msg.channel_id.get() as i64,
|
||||
"public",
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let allowed = match override_mode.as_deref() {
|
||||
Some(mode) if mode.eq_ignore_ascii_case("allow") => true,
|
||||
Some(mode) if mode.eq_ignore_ascii_case("deny") => false,
|
||||
_ => settings.public_commands_enabled,
|
||||
};
|
||||
if allowed {
|
||||
return true;
|
||||
}
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Commandes publiques desactivees")
|
||||
.description(format!(
|
||||
"La commande `{}` est desactivee dans ce salon.",
|
||||
command_key.replace('_', " ")
|
||||
))
|
||||
.color(0xED4245);
|
||||
send_embed(ctx, msg, embed).await;
|
||||
false
|
||||
}
|
||||
@@ -11,7 +11,15 @@ use serenity::prelude::*;
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::commands::logs_command_helpers::{pool, set_log_channel};
|
||||
|
||||
const LOG_TYPES: &[&str] = &["moderation", "message", "voice", "boost", "role", "raid", "channel"];
|
||||
const LOG_TYPES: &[&str] = &[
|
||||
"moderation",
|
||||
"message",
|
||||
"voice",
|
||||
"boost",
|
||||
"role",
|
||||
"raid",
|
||||
"channel",
|
||||
];
|
||||
const LOG_CATEGORY_NAME: &str = "📁 ➜ Espace Logs";
|
||||
const LOG_CHANNEL_PREFIX: &str = "📁・";
|
||||
const AUTOCONFIGLOG_COMPONENT_PREFIX: &str = "autoconfiglog";
|
||||
|
||||
@@ -125,7 +125,10 @@ pub async fn emit_log(
|
||||
|
||||
embed = embed.timestamp(timestamp);
|
||||
|
||||
record_audit_log(ctx, guild_id, log_type, user_id, channel_id, role_id, action).await;
|
||||
record_audit_log(
|
||||
ctx, guild_id, log_type, user_id, channel_id, role_id, action,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(log_channel_id) = get_log_channel(ctx, guild_id, log_type).await {
|
||||
let _ = log_channel_id
|
||||
|
||||
@@ -13,16 +13,29 @@ pub mod alladmins;
|
||||
pub mod allbots;
|
||||
#[path = "permissions/allperms.rs"]
|
||||
pub mod allperms;
|
||||
#[path = "roles/ancien.rs"]
|
||||
pub mod ancien;
|
||||
#[path = "moderation/antilink.rs"]
|
||||
pub mod antilink;
|
||||
#[path = "moderation/antimassmention.rs"]
|
||||
pub mod antimassmention;
|
||||
#[path = "moderation/antiraideautoconfig.rs"]
|
||||
pub mod antiraideautoconfig;
|
||||
#[path = "moderation/antispam.rs"]
|
||||
pub mod antispam;
|
||||
#[path = "outils/autobackup.rs"]
|
||||
pub mod autobackup;
|
||||
#[path = "logs/autoconfiglog.rs"]
|
||||
pub mod autoconfiglog;
|
||||
pub mod automod_service;
|
||||
#[path = "outils/autopublish.rs"]
|
||||
pub mod autopublish;
|
||||
#[path = "outils/autoreact.rs"]
|
||||
pub mod autoreact;
|
||||
#[path = "outils/backup.rs"]
|
||||
pub mod backup;
|
||||
#[path = "moderation/badwords.rs"]
|
||||
pub mod badwords;
|
||||
#[path = "moderation/ban.rs"]
|
||||
pub mod ban;
|
||||
#[path = "moderation/banlist.rs"]
|
||||
@@ -63,8 +76,12 @@ pub mod claim;
|
||||
pub mod cleanup;
|
||||
#[path = "moderation/clear_all_sanctions.rs"]
|
||||
pub mod clear_all_sanctions;
|
||||
#[path = "moderation/clear_badwords.rs"]
|
||||
pub mod clear_badwords;
|
||||
#[path = "administration/clear_bl.rs"]
|
||||
pub mod clear_bl;
|
||||
#[path = "moderation/clear_limit.rs"]
|
||||
pub mod clear_limit;
|
||||
#[path = "moderation/clear_messages.rs"]
|
||||
pub mod clear_messages;
|
||||
#[path = "administration/clear_owners.rs"]
|
||||
@@ -125,6 +142,8 @@ pub mod kick;
|
||||
pub mod leave;
|
||||
#[path = "logs/leave_settings.rs"]
|
||||
pub mod leave_settings;
|
||||
#[path = "moderation/link.rs"]
|
||||
pub mod link;
|
||||
#[path = "bot/listen.rs"]
|
||||
pub mod listen;
|
||||
#[path = "outils/loading.rs"]
|
||||
@@ -154,8 +173,12 @@ pub mod mp;
|
||||
pub mod mute;
|
||||
#[path = "moderation/mutelist.rs"]
|
||||
pub mod mutelist;
|
||||
#[path = "moderation/muterole.rs"]
|
||||
pub mod muterole;
|
||||
#[path = "outils/newsticker.rs"]
|
||||
pub mod newsticker;
|
||||
#[path = "roles/noderank.rs"]
|
||||
pub mod noderank;
|
||||
#[path = "logs/nolog.rs"]
|
||||
pub mod nolog;
|
||||
#[path = "bot/online.rs"]
|
||||
@@ -168,12 +191,18 @@ pub mod perms_helpers;
|
||||
pub mod perms_service;
|
||||
#[path = "infos/pic.rs"]
|
||||
pub mod pic;
|
||||
#[path = "outils/piconly.rs"]
|
||||
pub mod piconly;
|
||||
#[path = "infos/ping.rs"]
|
||||
pub mod ping;
|
||||
#[path = "bot/playto.rs"]
|
||||
pub mod playto;
|
||||
#[path = "administration/prefix.rs"]
|
||||
pub mod prefix;
|
||||
#[path = "moderation/public.rs"]
|
||||
pub mod public;
|
||||
#[path = "moderation/punish.rs"]
|
||||
pub mod punish;
|
||||
#[path = "logs/raidlog.rs"]
|
||||
pub mod raidlog;
|
||||
#[path = "bot/remove_activity.rs"]
|
||||
@@ -184,6 +213,8 @@ pub mod rename;
|
||||
pub mod renew;
|
||||
#[path = "outils/reroll.rs"]
|
||||
pub mod reroll;
|
||||
#[path = "moderation/resetantiraide.rs"]
|
||||
pub mod resetantiraide;
|
||||
#[path = "infos/role.rs"]
|
||||
pub mod role;
|
||||
#[path = "logs/rolelog.rs"]
|
||||
@@ -206,6 +237,8 @@ pub mod set;
|
||||
pub mod set_boostembed;
|
||||
#[path = "logs/set_modlogs.rs"]
|
||||
pub mod set_modlogs;
|
||||
#[path = "moderation/set_muterole.rs"]
|
||||
pub mod set_muterole;
|
||||
#[path = "bot/shadowbot.rs"]
|
||||
pub mod shadowbot;
|
||||
#[path = "infos/showpics.rs"]
|
||||
@@ -214,8 +247,12 @@ pub mod showpics;
|
||||
pub mod slowmode;
|
||||
#[path = "outils/snipe.rs"]
|
||||
pub mod snipe;
|
||||
#[path = "moderation/spam.rs"]
|
||||
pub mod spam;
|
||||
#[path = "bot/stream.rs"]
|
||||
pub mod stream;
|
||||
#[path = "moderation/strikes.rs"]
|
||||
pub mod strikes;
|
||||
#[path = "outils/suggestion.rs"]
|
||||
pub mod suggestion;
|
||||
#[path = "roles/sync.rs"]
|
||||
@@ -240,6 +277,8 @@ pub mod ticket;
|
||||
pub mod ticket_member;
|
||||
#[path = "outils/tickets.rs"]
|
||||
pub mod tickets;
|
||||
#[path = "moderation/timeout.rs"]
|
||||
pub mod timeout;
|
||||
#[path = "moderation/unban.rs"]
|
||||
pub mod unban;
|
||||
#[path = "moderation/unbanall.rs"]
|
||||
@@ -286,9 +325,12 @@ pub mod watch;
|
||||
pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
vec![
|
||||
ping::COMMAND_DESCRIPTOR.metadata(),
|
||||
timeout::COMMAND_DESCRIPTOR.metadata(),
|
||||
allbots::COMMAND_DESCRIPTOR.metadata(),
|
||||
alladmins::COMMAND_DESCRIPTOR.metadata(),
|
||||
botadmins::COMMAND_DESCRIPTOR.metadata(),
|
||||
ancien::COMMAND_DESCRIPTOR.metadata(),
|
||||
antiraideautoconfig::COMMAND_DESCRIPTOR.metadata(),
|
||||
boosters::COMMAND_DESCRIPTOR.metadata(),
|
||||
rolemembers::COMMAND_DESCRIPTOR.metadata(),
|
||||
rolemenu::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -322,6 +364,20 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
choose::COMMAND_DESCRIPTOR.metadata(),
|
||||
embed::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_messages::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_limit::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_badwords::COMMAND_DESCRIPTOR.metadata(),
|
||||
muterole::COMMAND_DESCRIPTOR.metadata(),
|
||||
set_muterole::COMMAND_DESCRIPTOR.metadata(),
|
||||
antispam::COMMAND_DESCRIPTOR.metadata(),
|
||||
antilink::COMMAND_DESCRIPTOR.metadata(),
|
||||
antimassmention::COMMAND_DESCRIPTOR.metadata(),
|
||||
badwords::COMMAND_DESCRIPTOR.metadata(),
|
||||
spam::COMMAND_DESCRIPTOR.metadata(),
|
||||
link::COMMAND_DESCRIPTOR.metadata(),
|
||||
strikes::COMMAND_DESCRIPTOR.metadata(),
|
||||
punish::COMMAND_DESCRIPTOR.metadata(),
|
||||
public::COMMAND_DESCRIPTOR.metadata(),
|
||||
resetantiraide::COMMAND_DESCRIPTOR.metadata(),
|
||||
backup::COMMAND_DESCRIPTOR.metadata(),
|
||||
ticket::COMMAND_DESCRIPTOR.metadata(),
|
||||
claim::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -330,6 +386,7 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
close::COMMAND_DESCRIPTOR.metadata(),
|
||||
tickets::COMMAND_DESCRIPTOR.metadata(),
|
||||
showpics::COMMAND_DESCRIPTOR.metadata(),
|
||||
piconly::COMMAND_DESCRIPTOR.metadata(),
|
||||
suggestion::COMMAND_DESCRIPTOR.metadata(),
|
||||
autopublish::COMMAND_DESCRIPTOR.metadata(),
|
||||
tempvoc::COMMAND_DESCRIPTOR.metadata(),
|
||||
@@ -372,6 +429,7 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||
addrole::COMMAND_DESCRIPTOR.metadata(),
|
||||
delrole::COMMAND_DESCRIPTOR.metadata(),
|
||||
derank::COMMAND_DESCRIPTOR.metadata(),
|
||||
noderank::COMMAND_DESCRIPTOR.metadata(),
|
||||
del_sanction::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
clear_all_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{parse_on_off, pool};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_antilink(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(first) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiLink")
|
||||
.description("Usage: +antilink <on/off> | +antilink <invite/all>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let Ok(current) =
|
||||
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let updated = if let Some(value) = parse_on_off(first) {
|
||||
db::set_antilink_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
value,
|
||||
¤t.antilink_mode,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
} else if first.eq_ignore_ascii_case("invite") || first.eq_ignore_ascii_case("all") {
|
||||
db::set_antilink_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
current.antilink_enabled,
|
||||
&first.to_lowercase(),
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Some(updated) = updated else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiLink")
|
||||
.description(format!(
|
||||
"Etat: **{}**\nMode: **{}**",
|
||||
if updated.antilink_enabled {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
},
|
||||
updated.antilink_mode
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct AntilinkCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AntilinkCommand = AntilinkCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AntilinkCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "antilink",
|
||||
category: "moderation",
|
||||
params: "<on/off> | <invite/all>",
|
||||
description: "Active ou configure la protection anti liens.",
|
||||
examples: &["+antilink on", "+antilink invite", "+help antilink"],
|
||||
default_aliases: &["alink"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{parse_on_off, pool};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_antimassmention(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(first) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiMassMention")
|
||||
.description("Usage: +antimassmention <on/off> | +antimassmention <nombre>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let Ok(current) =
|
||||
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let updated = if let Some(value) = parse_on_off(first) {
|
||||
db::set_antimassmention_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
value,
|
||||
current.antimassmention_limit,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
} else if let Ok(limit) = first.parse::<i32>() {
|
||||
db::set_antimassmention_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
current.antimassmention_enabled,
|
||||
limit.clamp(1, 50),
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Some(updated) = updated else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiMassMention")
|
||||
.description(format!(
|
||||
"Etat: **{}**\nSeuil: **{} mention(s)**",
|
||||
if updated.antimassmention_enabled {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
},
|
||||
updated.antimassmention_limit
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct AntimassmentionCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AntimassmentionCommand = AntimassmentionCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AntimassmentionCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "antimassmention",
|
||||
category: "moderation",
|
||||
params: "<on/off> | <nombre>",
|
||||
description: "Active ou configure la protection anti spam de mentions.",
|
||||
examples: &[
|
||||
"+antimassmention on",
|
||||
"+antimassmention 6",
|
||||
"+help antimassmention",
|
||||
],
|
||||
default_aliases: &["amm"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::db;
|
||||
|
||||
const DEFAULT_STRIKE_RULES: &[(&str, &str, i32)] = &[
|
||||
("spam", "new", 2),
|
||||
("spam", "old", 1),
|
||||
("link", "new", 2),
|
||||
("link", "old", 1),
|
||||
("massmention", "new", 3),
|
||||
("massmention", "old", 2),
|
||||
("badword", "new", 2),
|
||||
("badword", "old", 1),
|
||||
];
|
||||
|
||||
pub async fn handle_antiraideautoconfig(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
let mut failed = Vec::new();
|
||||
|
||||
if db::set_antispam_settings(&pool, bot_id, guild_id_raw, true, 6, 5)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("antispam");
|
||||
}
|
||||
|
||||
if db::set_antilink_settings(&pool, bot_id, guild_id_raw, true, "invite")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("antilink");
|
||||
}
|
||||
|
||||
if db::set_antimassmention_settings(&pool, bot_id, guild_id_raw, true, 5)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("antimassmention");
|
||||
}
|
||||
|
||||
if db::set_badwords_enabled(&pool, bot_id, guild_id_raw, true)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("badwords");
|
||||
}
|
||||
|
||||
if db::clear_moderation_channel_overrides_by_kind(&pool, bot_id, guild_id_raw, "spam")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("spam overrides");
|
||||
}
|
||||
|
||||
if db::clear_moderation_channel_overrides_by_kind(&pool, bot_id, guild_id_raw, "link")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("link overrides");
|
||||
}
|
||||
|
||||
for (trigger, profile, strike_count) in DEFAULT_STRIKE_RULES {
|
||||
if db::upsert_strike_rule(&pool, bot_id, guild_id_raw, trigger, profile, *strike_count)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("strikes");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if db::setup_default_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("punish");
|
||||
}
|
||||
|
||||
let mut description = String::from(
|
||||
"Configuration anti raid appliquee.\n\n- Antispam: ON (6/5s)\n- AntiLink: ON (invite)\n- AntiMassMention: ON (5)\n- BadWords: ON\n- Strikes: profils par defaut\n- Punish: regles par defaut",
|
||||
);
|
||||
|
||||
if !failed.is_empty() {
|
||||
description.push_str("\n\nErreurs detectees: ");
|
||||
description.push_str(&failed.join(", "));
|
||||
}
|
||||
|
||||
let color = if failed.is_empty() {
|
||||
theme_color(ctx).await
|
||||
} else {
|
||||
0xFEE75C
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiRaid AutoConfig")
|
||||
.description(description)
|
||||
.color(color),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct AntiraideautoconfigCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AntiraideautoconfigCommand = AntiraideautoconfigCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AntiraideautoconfigCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "antiraideautoconfig",
|
||||
category: "moderation",
|
||||
params: "aucun",
|
||||
description: "Configure automatiquement les protections anti raid du serveur.",
|
||||
examples: &["+antiraideautoconfig", "+help antiraideautoconfig"],
|
||||
default_aliases: &["arcfg"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{format_duration, parse_on_off, parse_rate_limit, pool};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_antispam(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(first) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiSpam")
|
||||
.description("Usage: +antispam <on/off> | +antispam <nombre>/<duree>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
|
||||
let Ok(current) =
|
||||
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let updated = if let Some(value) = parse_on_off(first) {
|
||||
db::set_antispam_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
value,
|
||||
current.antispam_limit,
|
||||
current.antispam_window_seconds,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
} else if let Some((limit, window)) = parse_rate_limit(first) {
|
||||
db::set_antispam_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
current.antispam_enabled,
|
||||
limit,
|
||||
window,
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Some(updated) = updated else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("AntiSpam")
|
||||
.description(format!(
|
||||
"Etat: **{}**\nSensibilite: **{}/{}**",
|
||||
if updated.antispam_enabled {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
},
|
||||
updated.antispam_limit,
|
||||
format_duration(updated.antispam_window_seconds as i64)
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct AntispamCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AntispamCommand = AntispamCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AntispamCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "antispam",
|
||||
category: "moderation",
|
||||
params: "<on/off> | <nombre>/<duree>",
|
||||
description: "Active ou configure la protection antispam du serveur.",
|
||||
examples: &["+antispam on", "+antispam 6/5s", "+help antispam"],
|
||||
default_aliases: &["aspam"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{parse_on_off, pool};
|
||||
use crate::commands::common::{add_list_fields, send_embed};
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_badwords(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let Ok(settings) =
|
||||
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("BadWords")
|
||||
.description(format!(
|
||||
"Etat: **{}**\nUsage: +badwords <on/off|add/del/list>",
|
||||
if settings.badwords_enabled {
|
||||
"ON"
|
||||
} else {
|
||||
"OFF"
|
||||
}
|
||||
))
|
||||
.color(0x5865F2),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let action = args[0].to_lowercase();
|
||||
|
||||
if action == "list" {
|
||||
let words = db::list_badwords(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let lines = words
|
||||
.into_iter()
|
||||
.map(|word| format!("- {}", word))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut embed = CreateEmbed::new().title("BadWords list").color(0x5865F2);
|
||||
embed = add_list_fields(embed, &lines, "Mots interdits");
|
||||
send_embed(ctx, msg, embed).await;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(value) = parse_on_off(&action) {
|
||||
let _ = db::set_badwords_enabled(&pool, bot_id, guild_id.get() as i64, value).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("BadWords")
|
||||
.description(format!(
|
||||
"Protection badwords: **{}**",
|
||||
if value { "ON" } else { "OFF" }
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if action == "add" {
|
||||
let Some(word) = args.get(1) else {
|
||||
return;
|
||||
};
|
||||
let _ = db::add_badword(&pool, bot_id, guild_id.get() as i64, word).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("BadWords")
|
||||
.description(format!("Mot ajoute: **{}**", word))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if action == "del" || action == "remove" {
|
||||
let Some(word) = args.get(1) else {
|
||||
return;
|
||||
};
|
||||
let removed = db::remove_badword(&pool, bot_id, guild_id.get() as i64, word)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("BadWords")
|
||||
.description(format!("Mot supprime: **{}** ({}).", word, removed))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("BadWords")
|
||||
.description("Usage: +badwords <on/off|add <mot>|del <mot>|list>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct BadwordsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: BadwordsCommand = BadwordsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for BadwordsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "badwords",
|
||||
category: "moderation",
|
||||
params: "<on/off|add <mot>|del <mot>|list>",
|
||||
description: "Active la protection badwords et gere la liste des mots interdits.",
|
||||
examples: &["+badwords on", "+badwords add insulte", "+badwords list"],
|
||||
default_aliases: &["bw"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_clear_badwords(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let cleared = db::clear_badwords(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or(0);
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Clear BadWords")
|
||||
.description(format!("{} mot(s) interdit(s) supprime(s).", cleared))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct ClearBadwordsCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ClearBadwordsCommand = ClearBadwordsCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ClearBadwordsCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "clear_badwords",
|
||||
category: "moderation",
|
||||
params: "badwords",
|
||||
description: "Supprime l ensemble des mots interdits enregistres.",
|
||||
examples: &["+clear badwords", "+help clear badwords"],
|
||||
default_aliases: &["cbw"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_clear_limit(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(raw_value) = args.get(1) else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Clear Limit")
|
||||
.description("Usage: +clear limit <nombre>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(value) = raw_value.parse::<i32>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let clamped = value.clamp(1, 1_000);
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
if db::set_clear_limit(&pool, bot_id, guild_id.get() as i64, clamped)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Clear Limit")
|
||||
.description(format!(
|
||||
"Limite de suppression definie a **{}** message(s) par commande clear.",
|
||||
clamped
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct ClearLimitCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ClearLimitCommand = ClearLimitCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ClearLimitCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "clear_limit",
|
||||
category: "moderation",
|
||||
params: "limit <nombre>",
|
||||
description: "Definit la limite max de messages supprimables avec +clear.",
|
||||
examples: &["+clear limit 100", "+help clear limit"],
|
||||
default_aliases: &["climit"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use serenity::prelude::*;
|
||||
|
||||
use crate::commands::admin_common::parse_user_id;
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::db::{self, DbPoolKey};
|
||||
|
||||
pub async fn handle_clear_messages(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Ok(mut amount) = args.first().unwrap_or(&"0").parse::<u64>() else {
|
||||
@@ -12,7 +13,28 @@ pub async fn handle_clear_messages(ctx: &Context, msg: &Message, args: &[&str])
|
||||
if amount == 0 {
|
||||
return;
|
||||
}
|
||||
amount = amount.clamp(1, 100);
|
||||
|
||||
let max_limit = if let Some(guild_id) = msg.guild_id {
|
||||
let pool = {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
};
|
||||
|
||||
if let Some(pool) = pool {
|
||||
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()
|
||||
.map(|settings| settings.clear_limit.max(1) as u64)
|
||||
.unwrap_or(100)
|
||||
} else {
|
||||
100
|
||||
}
|
||||
} else {
|
||||
100
|
||||
};
|
||||
|
||||
amount = amount.clamp(1, max_limit);
|
||||
|
||||
let filter_user = args.get(1).and_then(|raw| parse_user_id(raw));
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::{parse_channel_id, send_embed};
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_link_override(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(action) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Link")
|
||||
.description("Usage: +link <allow/deny/reset> [#salon]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let channel_id = args
|
||||
.get(1)
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
let channel_id_raw = channel_id.get() as i64;
|
||||
|
||||
let description = if action.eq_ignore_ascii_case("allow") {
|
||||
let _ = db::set_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id_raw,
|
||||
"link",
|
||||
"allow",
|
||||
)
|
||||
.await;
|
||||
format!("AntiLink desactive dans <#{}>.", channel_id.get())
|
||||
} else if action.eq_ignore_ascii_case("deny") {
|
||||
let _ = db::set_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id_raw,
|
||||
"link",
|
||||
"deny",
|
||||
)
|
||||
.await;
|
||||
format!("AntiLink force dans <#{}>.", channel_id.get())
|
||||
} else if action.eq_ignore_ascii_case("reset") {
|
||||
let _ = db::remove_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id_raw,
|
||||
"link",
|
||||
)
|
||||
.await;
|
||||
format!("Override antilink supprime dans <#{}>.", channel_id.get())
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Link Override")
|
||||
.description(description)
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct LinkCommand;
|
||||
pub static COMMAND_DESCRIPTOR: LinkCommand = LinkCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for LinkCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "link",
|
||||
category: "moderation",
|
||||
params: "<allow/deny/reset> [#salon]",
|
||||
description: "Definit l override antilink pour un salon (allow, deny, reset).",
|
||||
examples: &["+link allow #general", "+link deny #regles", "+link reset"],
|
||||
default_aliases: &["linkch"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use serenity::builder::{CreateEmbed, EditRole};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
fn mute_permissions() -> Permissions {
|
||||
Permissions::SEND_MESSAGES | Permissions::ADD_REACTIONS | Permissions::SPEAK
|
||||
}
|
||||
|
||||
pub async fn handle_muterole(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let Ok(settings) =
|
||||
db::get_or_create_moderation_settings(&pool, bot_id, guild_id.get() as i64).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut role_id = settings
|
||||
.mute_role_id
|
||||
.and_then(|raw| u64::try_from(raw).ok())
|
||||
.map(RoleId::new);
|
||||
|
||||
let Ok(partial_guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
if role_id.is_none() {
|
||||
role_id = partial_guild
|
||||
.roles
|
||||
.values()
|
||||
.find(|role| role.name.eq_ignore_ascii_case("Muted"))
|
||||
.map(|role| role.id);
|
||||
}
|
||||
|
||||
if role_id.is_none() {
|
||||
let created = guild_id
|
||||
.create_role(
|
||||
&ctx.http,
|
||||
EditRole::new()
|
||||
.name("Muted")
|
||||
.permissions(Permissions::empty()),
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
role_id = created.map(|role| role.id);
|
||||
}
|
||||
|
||||
let Some(role_id) = role_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut failed_channels = Vec::new();
|
||||
if let Ok(channels) = guild_id.channels(&ctx.http).await {
|
||||
for channel in channels.values() {
|
||||
let result = channel
|
||||
.create_permission(
|
||||
&ctx.http,
|
||||
PermissionOverwrite {
|
||||
allow: Permissions::empty(),
|
||||
deny: mute_permissions(),
|
||||
kind: PermissionOverwriteType::Role(role_id),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if result.is_err() {
|
||||
failed_channels.push(channel.id.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = db::set_mute_role(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
Some(role_id.get() as i64),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut description = format!(
|
||||
"Role muet configure: <@&{}>.\nPermissions appliquees sur les salons du serveur.",
|
||||
role_id.get()
|
||||
);
|
||||
|
||||
if !failed_channels.is_empty() {
|
||||
let list = failed_channels
|
||||
.iter()
|
||||
.take(10)
|
||||
.map(|id| format!("<#{}>", id))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
description.push_str(&format!(
|
||||
"\nErreurs permissions: {} salon(s). {}",
|
||||
failed_channels.len(),
|
||||
list
|
||||
));
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("MuteRole")
|
||||
.description(description)
|
||||
.color(if failed_channels.is_empty() {
|
||||
0x57F287
|
||||
} else {
|
||||
0xFEE75C
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct MuteRoleCommand;
|
||||
pub static COMMAND_DESCRIPTOR: MuteRoleCommand = MuteRoleCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for MuteRoleCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "muterole",
|
||||
category: "moderation",
|
||||
params: "aucun",
|
||||
description: "Cree ou met a jour le role muet et tente de corriger les permissions des salons.",
|
||||
examples: &["+muterole", "+help muterole"],
|
||||
default_aliases: &["mr"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{parse_on_off, pool};
|
||||
use crate::commands::common::{parse_channel_id, send_embed};
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_public(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(first) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Public")
|
||||
.description("Usage: +public <on/off> | +public <allow/deny/reset> [#salon]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if let Some(enabled) = parse_on_off(first) {
|
||||
let _ = db::set_public_commands_enabled(&pool, bot_id, guild_id_raw, enabled).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Public")
|
||||
.description(format!(
|
||||
"Commandes publiques sur le serveur: **{}**",
|
||||
if enabled { "ON" } else { "OFF" }
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let channel_id = args
|
||||
.get(1)
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let description = if first.eq_ignore_ascii_case("allow") {
|
||||
let _ = db::set_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id.get() as i64,
|
||||
"public",
|
||||
"allow",
|
||||
)
|
||||
.await;
|
||||
format!("Commandes publiques forcees dans <#{}>.", channel_id.get())
|
||||
} else if first.eq_ignore_ascii_case("deny") {
|
||||
let _ = db::set_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id.get() as i64,
|
||||
"public",
|
||||
"deny",
|
||||
)
|
||||
.await;
|
||||
format!(
|
||||
"Commandes publiques desactivees dans <#{}>.",
|
||||
channel_id.get()
|
||||
)
|
||||
} else if first.eq_ignore_ascii_case("reset") {
|
||||
let _ = db::remove_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id.get() as i64,
|
||||
"public",
|
||||
)
|
||||
.await;
|
||||
format!("Override public supprime dans <#{}>.", channel_id.get())
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Public")
|
||||
.description(description)
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct PublicCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PublicCommand = PublicCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PublicCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "public",
|
||||
category: "moderation",
|
||||
params: "<on/off> | <allow/deny/reset> [#salon]",
|
||||
description: "Active/desactive les commandes publiques globalement ou par salon.",
|
||||
examples: &[
|
||||
"+public on",
|
||||
"+public deny #annonces",
|
||||
"+public reset #annonces",
|
||||
],
|
||||
default_aliases: &["pubc"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{
|
||||
format_duration, parse_duration_to_seconds, parse_sanction, pool,
|
||||
};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
fn describe_rule(index: usize, rule: &db::PunishRule) -> String {
|
||||
let sanction = if let Some(duration) = rule.sanction_seconds {
|
||||
format!("{} {}", rule.sanction, format_duration(duration))
|
||||
} else {
|
||||
rule.sanction.clone()
|
||||
};
|
||||
|
||||
format!(
|
||||
"{}. {} strikes / {} -> {}",
|
||||
index,
|
||||
rule.threshold,
|
||||
format_duration(rule.window_seconds),
|
||||
sanction
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn handle_punish(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let rules = db::list_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let description = if rules.is_empty() {
|
||||
"Aucune regle.".to_string()
|
||||
} else {
|
||||
rules
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, rule)| describe_rule(idx + 1, rule))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description(description)
|
||||
.color(0x5865F2),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("setup") {
|
||||
let _ = db::setup_default_punish_rules(&pool, bot_id, guild_id_raw).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Regles par defaut restaurees.")
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("add") {
|
||||
if args.len() < 4 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(threshold) = args[1].parse::<i32>() else {
|
||||
return;
|
||||
};
|
||||
let Some(window_seconds) = parse_duration_to_seconds(args[2]) else {
|
||||
return;
|
||||
};
|
||||
let Some(sanction) = parse_sanction(args[3]) else {
|
||||
return;
|
||||
};
|
||||
let sanction_seconds = args.get(4).and_then(|raw| parse_duration_to_seconds(raw));
|
||||
|
||||
let _ = db::upsert_punish_rule(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
threshold.clamp(1, 200),
|
||||
window_seconds,
|
||||
sanction,
|
||||
sanction_seconds,
|
||||
)
|
||||
.await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Regle ajoutee ou mise a jour.")
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("del") {
|
||||
let Some(raw_index) = args.get(1) else {
|
||||
return;
|
||||
};
|
||||
let Ok(index) = raw_index.parse::<usize>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let rules = db::list_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if index == 0 || index > rules.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = &rules[index - 1];
|
||||
let _ = db::delete_punish_rule_by_id(&pool, bot_id, guild_id_raw, rule.id).await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description(format!("Regle {} supprimee.", index))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Punish")
|
||||
.description("Usage: +punish | +punish add <nombre> <duree> <sanction> [duree] | +punish del <numero> | +punish setup")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct PunishCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PunishCommand = PunishCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PunishCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "punish",
|
||||
category: "moderation",
|
||||
params: "[add <nombre> <duree> <sanction> [duree] | del <numero> | setup]",
|
||||
description: "Affiche et gere les sanctions automatiques appliquees selon les strikes.",
|
||||
examples: &["+punish", "+punish add 8 1h mute 30m", "+punish setup"],
|
||||
default_aliases: &["pn"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::db;
|
||||
|
||||
const DEFAULT_STRIKE_RULES: &[(&str, &str, i32)] = &[
|
||||
("spam", "new", 2),
|
||||
("spam", "old", 1),
|
||||
("link", "new", 2),
|
||||
("link", "old", 1),
|
||||
("massmention", "new", 3),
|
||||
("massmention", "old", 2),
|
||||
("badword", "new", 2),
|
||||
("badword", "old", 1),
|
||||
];
|
||||
|
||||
pub async fn handle_resetantiraide(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
let mut failed = Vec::new();
|
||||
|
||||
if db::set_antispam_settings(&pool, bot_id, guild_id_raw, false, 6, 5)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("antispam");
|
||||
}
|
||||
|
||||
if db::set_antilink_settings(&pool, bot_id, guild_id_raw, false, "invite")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("antilink");
|
||||
}
|
||||
|
||||
if db::set_antimassmention_settings(&pool, bot_id, guild_id_raw, false, 5)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("antimassmention");
|
||||
}
|
||||
|
||||
if db::set_badwords_enabled(&pool, bot_id, guild_id_raw, false)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("badwords");
|
||||
}
|
||||
|
||||
if db::clear_badwords(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("clear badwords");
|
||||
}
|
||||
|
||||
if db::clear_moderation_channel_overrides_by_kind(&pool, bot_id, guild_id_raw, "spam")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("spam overrides");
|
||||
}
|
||||
|
||||
if db::clear_moderation_channel_overrides_by_kind(&pool, bot_id, guild_id_raw, "link")
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("link overrides");
|
||||
}
|
||||
|
||||
for (trigger, profile, strike_count) in DEFAULT_STRIKE_RULES {
|
||||
if db::upsert_strike_rule(&pool, bot_id, guild_id_raw, trigger, profile, *strike_count)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("strikes");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if db::setup_default_punish_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
failed.push("punish");
|
||||
}
|
||||
|
||||
let mut description = String::from(
|
||||
"Les protections anti raid ont ete remises a leur etat par defaut.\n\n- Antispam: OFF (6/5s)\n- AntiLink: OFF (invite)\n- AntiMassMention: OFF (5)\n- BadWords: OFF et liste vide\n- Overrides spam/link: supprimes\n- Strikes: profils par defaut\n- Punish: regles par defaut",
|
||||
);
|
||||
|
||||
if !failed.is_empty() {
|
||||
description.push_str("\n\nErreurs detectees: ");
|
||||
description.push_str(&failed.join(", "));
|
||||
}
|
||||
|
||||
let color = if failed.is_empty() {
|
||||
theme_color(ctx).await
|
||||
} else {
|
||||
0xFEE75C
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Reset AntiRaid")
|
||||
.description(description)
|
||||
.color(color),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct ResetantiraideCommand;
|
||||
pub static COMMAND_DESCRIPTOR: ResetantiraideCommand = ResetantiraideCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for ResetantiraideCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "resetantiraide",
|
||||
category: "moderation",
|
||||
params: "aucun",
|
||||
description: "Arrete et reinitialise les protections anti raid avec les valeurs par defaut.",
|
||||
examples: &["+resetantiraide", "+help resetantiraide"],
|
||||
default_aliases: &["rra"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::{parse_role, send_embed};
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_set_muterole(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(raw_role) = args.get(1) else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Set MuteRole")
|
||||
.description("Usage: +set muterole <@role/ID/nom>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(role) = parse_role(&guild, raw_role) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
if db::set_mute_role(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
Some(role.id.get() as i64),
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("MuteRole")
|
||||
.description(format!("Role muet defini sur <@&{}>.", role.id.get()))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct SetMuteRoleCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SetMuteRoleCommand = SetMuteRoleCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SetMuteRoleCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "set_muterole",
|
||||
category: "moderation",
|
||||
params: "muterole <@role/ID/nom>",
|
||||
description: "Definit le role utilise pour le mute lorsque le mode timeout est desactive.",
|
||||
examples: &["+set muterole @Muted", "+help set muterole"],
|
||||
default_aliases: &["smr"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::{parse_channel_id, send_embed};
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_spam_override(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(action) = args.first() else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Spam")
|
||||
.description("Usage: +spam <allow/deny/reset> [#salon]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let channel_id = args
|
||||
.get(1)
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
let channel_id_raw = channel_id.get() as i64;
|
||||
|
||||
let description = if action.eq_ignore_ascii_case("allow") {
|
||||
let _ = db::set_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id_raw,
|
||||
"spam",
|
||||
"allow",
|
||||
)
|
||||
.await;
|
||||
format!("Antispam desactive dans <#{}>.", channel_id.get())
|
||||
} else if action.eq_ignore_ascii_case("deny") {
|
||||
let _ = db::set_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id_raw,
|
||||
"spam",
|
||||
"deny",
|
||||
)
|
||||
.await;
|
||||
format!("Antispam force dans <#{}>.", channel_id.get())
|
||||
} else if action.eq_ignore_ascii_case("reset") {
|
||||
let _ = db::remove_moderation_channel_override(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id_raw,
|
||||
channel_id_raw,
|
||||
"spam",
|
||||
)
|
||||
.await;
|
||||
format!("Override antispam supprime dans <#{}>.", channel_id.get())
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Spam Override")
|
||||
.description(description)
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct SpamCommand;
|
||||
pub static COMMAND_DESCRIPTOR: SpamCommand = SpamCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for SpamCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "spam",
|
||||
category: "moderation",
|
||||
params: "<allow/deny/reset> [#salon]",
|
||||
description: "Definit l override antispam pour un salon (allow, deny, reset).",
|
||||
examples: &["+spam allow #general", "+spam deny #flood", "+spam reset"],
|
||||
default_aliases: &["spamch"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{parse_profile, parse_trigger, pool};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_strikes(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let rules = db::list_strike_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut lines = Vec::new();
|
||||
for trigger in ["spam", "link", "massmention", "badword"] {
|
||||
let new_count = rules
|
||||
.iter()
|
||||
.find(|r| r.trigger == trigger && r.profile == "new")
|
||||
.map(|r| r.strike_count)
|
||||
.unwrap_or(0);
|
||||
let old_count = rules
|
||||
.iter()
|
||||
.find(|r| r.trigger == trigger && r.profile == "old")
|
||||
.map(|r| r.strike_count)
|
||||
.unwrap_or(0);
|
||||
|
||||
lines.push(format!(
|
||||
"`{}` -> nouveau: `{}` | ancien: `{}`",
|
||||
trigger, new_count, old_count
|
||||
));
|
||||
}
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Strikes")
|
||||
.description(lines.join("\n"))
|
||||
.color(0x5865F2),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(trigger) = parse_trigger(args[0]) else {
|
||||
return;
|
||||
};
|
||||
let Ok(count) = args[1].parse::<i32>() else {
|
||||
return;
|
||||
};
|
||||
let count = count.clamp(0, 20);
|
||||
|
||||
if let Some(profile) = parse_profile(args.get(2).copied()) {
|
||||
let _ = db::upsert_strike_rule(&pool, bot_id, guild_id_raw, trigger, profile, count).await;
|
||||
} else {
|
||||
let _ = db::upsert_strike_rule(&pool, bot_id, guild_id_raw, trigger, "new", count).await;
|
||||
let _ = db::upsert_strike_rule(&pool, bot_id, guild_id_raw, trigger, "old", count).await;
|
||||
}
|
||||
|
||||
let rules = db::list_strike_rules(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let new_count = rules
|
||||
.iter()
|
||||
.find(|r| r.trigger == trigger && r.profile == "new")
|
||||
.map(|r| r.strike_count)
|
||||
.unwrap_or(0);
|
||||
let old_count = rules
|
||||
.iter()
|
||||
.find(|r| r.trigger == trigger && r.profile == "old")
|
||||
.map(|r| r.strike_count)
|
||||
.unwrap_or(0);
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Strikes")
|
||||
.description(format!(
|
||||
"Regle mise a jour pour `{}`\nNouveau: `{}`\nAncien: `{}`",
|
||||
trigger, new_count, old_count
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct StrikesCommand;
|
||||
pub static COMMAND_DESCRIPTOR: StrikesCommand = StrikesCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for StrikesCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "strikes",
|
||||
category: "moderation",
|
||||
params: "[<trigger> <nombre> [ancien/nouveau]]",
|
||||
description: "Affiche ou modifie les strikes attribues pour chaque trigger automod.",
|
||||
examples: &["+strikes", "+strikes spam 2", "+strikes link 1 ancien"],
|
||||
default_aliases: &["stk"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::{parse_on_off, pool};
|
||||
use crate::commands::common::send_embed;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_timeout_toggle(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(value) = args.first().and_then(|raw| parse_on_off(raw)) else {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Timeout")
|
||||
.description("Usage: +timeout <on/off>")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let Ok(settings) =
|
||||
db::set_use_timeout_for_mute(&pool, bot_id, guild_id.get() as i64, value).await
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mode = if settings.use_timeout {
|
||||
"Timeout Discord"
|
||||
} else {
|
||||
"Role mute"
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Timeout")
|
||||
.description(format!(
|
||||
"Mode mute mis a jour: **{}**.\nNote: les timeouts Discord sont limites a 28 jours.",
|
||||
mode
|
||||
))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct TimeoutCommand;
|
||||
pub static COMMAND_DESCRIPTOR: TimeoutCommand = TimeoutCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for TimeoutCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "timeout",
|
||||
category: "moderation",
|
||||
params: "<on/off>",
|
||||
description: "Active ou desactive l utilisation du timeout Discord pour les mutes.",
|
||||
examples: &["+timeout on", "+timeout off", "+help timeout"],
|
||||
default_aliases: &["to"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::admin_common::parse_user_id;
|
||||
use crate::db::DbPoolKey;
|
||||
use crate::db::{self, DbPoolKey};
|
||||
|
||||
pub fn duration_from_input(input: &str) -> Option<Duration> {
|
||||
let raw = input.trim().to_lowercase();
|
||||
@@ -95,9 +95,30 @@ pub async fn handle_timeout(
|
||||
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()) {
|
||||
@@ -110,6 +131,21 @@ pub async fn handle_timeout(
|
||||
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
|
||||
|
||||
@@ -2,10 +2,10 @@ use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use chrono::Utc;
|
||||
use serenity::builder::EditMember;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::moderation_sanction_helpers::{channel_mute_users, handle_timeout};
|
||||
use crate::db::DbPoolKey;
|
||||
|
||||
static MODERATION_TICK: OnceLock<Mutex<Instant>> = OnceLock::new();
|
||||
@@ -15,66 +15,6 @@ async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
data.get::<DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
async fn handle_timeout(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
users: &[UserId],
|
||||
expires: Option<chrono::DateTime<Utc>>,
|
||||
) -> usize {
|
||||
let mut done = 0usize;
|
||||
for user_id in users {
|
||||
if let Ok(mut member) = guild_id.member(&ctx.http, *user_id).await {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub async fn maybe_run_maintenance(ctx: &Context, guild_id: Option<GuildId>) {
|
||||
let Some(guild_id) = guild_id else {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
use chrono::Utc;
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::{parse_channel_id, send_embed};
|
||||
use crate::db;
|
||||
|
||||
fn is_image_filename(filename: &str) -> bool {
|
||||
let extension = filename
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
|
||||
matches!(
|
||||
extension.as_str(),
|
||||
"jpg" | "jpeg" | "png" | "gif" | "webp" | "bmp" | "heic" | "heif"
|
||||
)
|
||||
}
|
||||
|
||||
fn has_only_photo_attachments(msg: &Message) -> bool {
|
||||
!msg.attachments.is_empty()
|
||||
&& msg
|
||||
.attachments
|
||||
.iter()
|
||||
.all(|attachment| is_image_filename(&attachment.filename))
|
||||
}
|
||||
|
||||
fn is_piconly_command_message(content: &str, prefix: &str) -> bool {
|
||||
if !content.starts_with(prefix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let without_prefix = content.trim_start_matches(prefix).trim();
|
||||
without_prefix
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.map(|command| command.eq_ignore_ascii_case("piconly"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub async fn enforce_piconly_message(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
content: &str,
|
||||
prefix: &str,
|
||||
) -> bool {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let is_selfie_channel = db::is_piconly_channel(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
msg.channel_id.get() as i64,
|
||||
)
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_selfie_channel || is_piconly_command_message(content, prefix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if has_only_photo_attachments(msg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let _ = msg.delete(&ctx.http).await;
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("Salon selfie")
|
||||
.description("Seules les photos sont autorisees dans ce salon.")
|
||||
.color(0xED4245)
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn handle_piconly(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = ({
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_i64 = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let channels = db::get_piconly_channels(&pool, bot_id, guild_id_i64)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let description = if channels.is_empty() {
|
||||
"Aucun salon selfie configure.".to_string()
|
||||
} else {
|
||||
channels
|
||||
.into_iter()
|
||||
.map(|channel| format!("<#{}>", channel.channel_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description(description)
|
||||
.timestamp(Utc::now()),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let adding = args[0].eq_ignore_ascii_case("add");
|
||||
let deleting = args[0].eq_ignore_ascii_case("del")
|
||||
|| args[0].eq_ignore_ascii_case("remove")
|
||||
|| args[0].eq_ignore_ascii_case("delete");
|
||||
|
||||
if !adding && !deleting {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description("Utilisation: +piconly <add/del> [#salon]")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let channel_id = args
|
||||
.get(1)
|
||||
.and_then(|raw| parse_channel_id(raw))
|
||||
.unwrap_or(msg.channel_id);
|
||||
|
||||
let result = if adding {
|
||||
db::add_piconly_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await
|
||||
} else {
|
||||
db::remove_piconly_channel(&pool, bot_id, guild_id_i64, channel_id.get() as i64).await
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("PicOnly")
|
||||
.description("Impossible de mettre a jour le salon selfie.")
|
||||
.color(0xED4245),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let embed = if adding {
|
||||
CreateEmbed::new()
|
||||
.title("Salon selfie ajoute")
|
||||
.description(format!("Salon: <#{}>", channel_id.get()))
|
||||
.timestamp(Utc::now())
|
||||
} else {
|
||||
CreateEmbed::new()
|
||||
.title("Salon selfie retire")
|
||||
.description(format!("Salon: <#{}>", channel_id.get()))
|
||||
.timestamp(Utc::now())
|
||||
};
|
||||
|
||||
send_embed(ctx, msg, embed).await;
|
||||
}
|
||||
|
||||
pub struct PiconlyCommand;
|
||||
pub static COMMAND_DESCRIPTOR: PiconlyCommand = PiconlyCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for PiconlyCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "piconly",
|
||||
category: "outils",
|
||||
params: "<add/del> [salon]",
|
||||
description: "Definit ou supprime un salon selfie, ou les membres ne peuvent envoyer que des photos.",
|
||||
examples: &["+piconly", "+piconly add #selfie", "+piconly del #selfie"],
|
||||
default_aliases: &["selfieonly"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,7 +448,10 @@ fn help_lookup_to_key(input: &str) -> Option<&'static str> {
|
||||
matched.or_else(|| help_metadata_lookup_key(input))
|
||||
}
|
||||
|
||||
fn resolve_help_command_key(input: &str, alias_map: &BTreeMap<String, Vec<String>>) -> Option<String> {
|
||||
fn resolve_help_command_key(
|
||||
input: &str,
|
||||
alias_map: &BTreeMap<String, Vec<String>>,
|
||||
) -> Option<String> {
|
||||
if let Some(key) = help_lookup_to_key(input) {
|
||||
return Some(key.to_string());
|
||||
}
|
||||
@@ -628,7 +631,10 @@ fn build_help_view_pages(
|
||||
let mut intro_lines = Vec::with_capacity(4 + HELP_PAGES.len());
|
||||
intro_lines.push("Shadow Bot est un bot de gestion de serveur.".to_string());
|
||||
intro_lines.push(String::new());
|
||||
intro_lines.push(format!("**Nombre total de commandes :** {}", total_commands));
|
||||
intro_lines.push(format!(
|
||||
"**Nombre total de commandes :** {}",
|
||||
total_commands
|
||||
));
|
||||
intro_lines.push("**Nombre de commandes par catégorie :**".to_string());
|
||||
for (index, page) in HELP_PAGES.iter().enumerate() {
|
||||
intro_lines.push(format!("• {} : {}", page.title, counts[index]));
|
||||
@@ -767,9 +773,8 @@ fn help_components(
|
||||
HelpLayout::Select | HelpLayout::Hybrid => {
|
||||
let mut options = Vec::with_capacity(HELP_PAGES.len() + 1);
|
||||
options.push(
|
||||
CreateSelectMenuOption::new("Présentation", "0").description(
|
||||
"Shadow Bot, total des commandes et répartition par catégorie.",
|
||||
),
|
||||
CreateSelectMenuOption::new("Présentation", "0")
|
||||
.description("Shadow Bot, total des commandes et répartition par catégorie."),
|
||||
);
|
||||
|
||||
for (index, page) in HELP_PAGES.iter().enumerate() {
|
||||
@@ -849,7 +854,10 @@ async fn build_command_help_embed(
|
||||
.join("\n");
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title(format!("Aide commande · +{}", doc.command.replace('_', " ")))
|
||||
.title(format!(
|
||||
"Aide commande · +{}",
|
||||
doc.command.replace('_', " ")
|
||||
))
|
||||
.description(doc.description)
|
||||
.field(
|
||||
"Commande",
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
use chrono::Utc;
|
||||
use serenity::builder::{
|
||||
CreateActionRow, CreateButton, CreateEmbed, CreateInputText, CreateInteractionResponse,
|
||||
CreateInteractionResponseMessage, CreateMessage, CreateModal,
|
||||
};
|
||||
use serenity::model::application::{
|
||||
ActionRowComponent, ButtonStyle, ComponentInteraction, InputTextStyle, ModalInteraction,
|
||||
};
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::common::theme_color;
|
||||
use crate::db;
|
||||
|
||||
const ANCIEN_MENU: &str = "ancien:settings";
|
||||
const ANCIEN_ROLE_INPUT_ID: &str = "role_id";
|
||||
const ANCIEN_DELAY_INPUT_ID: &str = "delay";
|
||||
|
||||
fn parse_owner_id(custom_id: &str) -> Option<(String, u64)> {
|
||||
let mut parts = custom_id.rsplitn(2, ':');
|
||||
let owner = parts.next()?.parse::<u64>().ok()?;
|
||||
let action = parts.next()?.to_string();
|
||||
Some((action, owner))
|
||||
}
|
||||
|
||||
fn modal_value(modal: &ModalInteraction, wanted_id: &str) -> Option<String> {
|
||||
for row in &modal.data.components {
|
||||
for component in &row.components {
|
||||
if let ActionRowComponent::InputText(input) = component {
|
||||
if input.custom_id == wanted_id {
|
||||
return input.value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_role_id_input(raw: &str) -> Option<RoleId> {
|
||||
let cleaned = raw.trim().trim_start_matches("<@&").trim_end_matches('>');
|
||||
cleaned.parse::<u64>().ok().map(RoleId::new)
|
||||
}
|
||||
|
||||
fn parse_delay_seconds(input: &str) -> Option<i64> {
|
||||
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::<i64>().ok()?;
|
||||
if value <= 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let unit = if suffix.is_empty() { "j" } else { &suffix };
|
||||
|
||||
let seconds = match unit {
|
||||
"s" | "sec" | "secs" | "seconde" | "secondes" => value,
|
||||
"m" | "min" | "mins" | "minute" | "minutes" => value.checked_mul(60)?,
|
||||
"h" | "heure" | "heures" => value.checked_mul(3_600)?,
|
||||
"j" | "d" | "jour" | "jours" => value.checked_mul(86_400)?,
|
||||
"w" | "sem" | "semaine" | "semaines" => value.checked_mul(604_800)?,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(seconds.max(1))
|
||||
}
|
||||
|
||||
fn format_delay(seconds: i64) -> String {
|
||||
let mut remaining = seconds.max(1);
|
||||
let days = remaining / 86_400;
|
||||
remaining %= 86_400;
|
||||
let hours = remaining / 3_600;
|
||||
remaining %= 3_600;
|
||||
let minutes = remaining / 60;
|
||||
|
||||
let mut parts = Vec::new();
|
||||
if days > 0 {
|
||||
parts.push(format!("{}j", days));
|
||||
}
|
||||
if hours > 0 {
|
||||
parts.push(format!("{}h", hours));
|
||||
}
|
||||
if minutes > 0 {
|
||||
parts.push(format!("{}m", minutes));
|
||||
}
|
||||
if parts.is_empty() {
|
||||
parts.push(format!("{}s", seconds.max(1)));
|
||||
}
|
||||
|
||||
parts.join(" ")
|
||||
}
|
||||
|
||||
fn ancien_embed(settings: &db::OldMemberSettings) -> CreateEmbed {
|
||||
let role_label = settings
|
||||
.role_id
|
||||
.and_then(|id| u64::try_from(id).ok())
|
||||
.map(|id| format!("<@&{}>", id))
|
||||
.unwrap_or_else(|| "Non configure".to_string());
|
||||
|
||||
CreateEmbed::new()
|
||||
.title("Ancien")
|
||||
.description("Definit au bout de combien de temps un membre devient ancien sur le serveur.")
|
||||
.field(
|
||||
"Statut",
|
||||
if settings.enabled { "Actif" } else { "Inactif" },
|
||||
true,
|
||||
)
|
||||
.field("Role ancien", role_label, true)
|
||||
.field("Delai", format_delay(settings.delay_seconds), true)
|
||||
.field(
|
||||
"Configuration",
|
||||
"Utilise le bouton Configurer pour definir l'ID du role et le delai.",
|
||||
false,
|
||||
)
|
||||
.timestamp(Utc::now())
|
||||
}
|
||||
|
||||
fn ancien_components(owner_id: UserId, settings: &db::OldMemberSettings) -> Vec<CreateActionRow> {
|
||||
let toggle_label = if settings.enabled {
|
||||
"Desactiver"
|
||||
} else {
|
||||
"Activer"
|
||||
};
|
||||
|
||||
let toggle_style = if settings.enabled {
|
||||
ButtonStyle::Danger
|
||||
} else {
|
||||
ButtonStyle::Success
|
||||
};
|
||||
|
||||
vec![CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(format!("{}:toggle:{}", ANCIEN_MENU, owner_id.get()))
|
||||
.label(toggle_label)
|
||||
.style(toggle_style),
|
||||
CreateButton::new(format!("{}:configure:{}", ANCIEN_MENU, owner_id.get()))
|
||||
.label("Configurer")
|
||||
.style(ButtonStyle::Primary),
|
||||
CreateButton::new(format!("{}:refresh:{}", ANCIEN_MENU, owner_id.get()))
|
||||
.label("Rafraichir")
|
||||
.style(ButtonStyle::Secondary),
|
||||
])]
|
||||
}
|
||||
|
||||
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
||||
let data = ctx.data.read().await;
|
||||
data.get::<db::DbPoolKey>().cloned()
|
||||
}
|
||||
|
||||
async fn show_menu(ctx: &Context, msg: &Message) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings = db::get_or_create_old_member_settings(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or(db::OldMemberSettings {
|
||||
bot_id,
|
||||
guild_id: guild_id.get() as i64,
|
||||
role_id: None,
|
||||
delay_seconds: 2_592_000,
|
||||
enabled: false,
|
||||
updated_at: Utc::now(),
|
||||
});
|
||||
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.send_message(
|
||||
&ctx.http,
|
||||
CreateMessage::new()
|
||||
.embed(ancien_embed(&settings).color(theme_color(ctx).await))
|
||||
.components(ancien_components(msg.author.id, &settings)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn respond_ephemeral_component(
|
||||
ctx: &Context,
|
||||
component: &ComponentInteraction,
|
||||
content: &str,
|
||||
) {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content(content)
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn respond_ephemeral_modal(ctx: &Context, modal: &ModalInteraction, content: &str) {
|
||||
let _ = modal
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content(content)
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_ancien(ctx: &Context, msg: &Message, _args: &[&str]) {
|
||||
show_menu(ctx, msg).await;
|
||||
}
|
||||
|
||||
pub async fn handle_component_interaction(ctx: &Context, component: &ComponentInteraction) -> bool {
|
||||
if !component.data.custom_id.starts_with(ANCIEN_MENU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some((action, owner_id)) = parse_owner_id(&component.data.custom_id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if component.user.id.get() != owner_id {
|
||||
respond_ephemeral_component(
|
||||
ctx,
|
||||
component,
|
||||
"Seul l'auteur du menu peut utiliser ces boutons.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(guild_id) = component.guild_id else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings = db::get_or_create_old_member_settings(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let Some(settings) = settings else {
|
||||
return true;
|
||||
};
|
||||
|
||||
if action.ends_with(":configure") {
|
||||
let modal = CreateModal::new(component.data.custom_id.clone(), "Configurer Ancien")
|
||||
.components(vec![
|
||||
CreateActionRow::InputText(
|
||||
CreateInputText::new(
|
||||
InputTextStyle::Short,
|
||||
"ID du role ancien (ou mention)",
|
||||
ANCIEN_ROLE_INPUT_ID,
|
||||
)
|
||||
.required(true),
|
||||
),
|
||||
CreateActionRow::InputText(
|
||||
CreateInputText::new(
|
||||
InputTextStyle::Short,
|
||||
"Delai (ex: 30j, 72h, 90m)",
|
||||
ANCIEN_DELAY_INPUT_ID,
|
||||
)
|
||||
.required(true),
|
||||
),
|
||||
]);
|
||||
|
||||
let _ = component
|
||||
.create_response(&ctx.http, CreateInteractionResponse::Modal(modal))
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
if action.ends_with(":toggle") {
|
||||
if !settings.enabled && settings.role_id.is_none() {
|
||||
respond_ephemeral_component(
|
||||
ctx,
|
||||
component,
|
||||
"Configure d'abord le role et le delai avant d'activer.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let updated = db::update_old_member_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
settings.role_id,
|
||||
settings.delay_seconds,
|
||||
!settings.enabled,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if let Some(updated) = updated {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(ancien_embed(&updated).color(theme_color(ctx).await))
|
||||
.components(ancien_components(component.user.id, &updated)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if action.ends_with(":refresh") {
|
||||
let _ = component
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(ancien_embed(&settings).color(theme_color(ctx).await))
|
||||
.components(ancien_components(component.user.id, &settings)),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn handle_modal_interaction(ctx: &Context, modal: &ModalInteraction) -> bool {
|
||||
if !modal.data.custom_id.starts_with(ANCIEN_MENU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some((action, owner_id)) = parse_owner_id(&modal.data.custom_id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if modal.user.id.get() != owner_id {
|
||||
respond_ephemeral_modal(
|
||||
ctx,
|
||||
modal,
|
||||
"Seul l'auteur du menu peut soumettre ce formulaire.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
}
|
||||
|
||||
if !action.contains(":configure") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(guild_id) = modal.guild_id else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let role_raw = modal_value(modal, ANCIEN_ROLE_INPUT_ID).unwrap_or_default();
|
||||
let Some(role_id) = parse_role_id_input(&role_raw) else {
|
||||
respond_ephemeral_modal(ctx, modal, "Role invalide. Fournis un ID ou une mention.").await;
|
||||
return true;
|
||||
};
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
respond_ephemeral_modal(ctx, modal, "Impossible de verifier le role sur ce serveur.").await;
|
||||
return true;
|
||||
};
|
||||
|
||||
if !guild.roles.contains_key(&role_id) {
|
||||
respond_ephemeral_modal(ctx, modal, "Le role indique n'existe pas sur ce serveur.").await;
|
||||
return true;
|
||||
}
|
||||
|
||||
let delay_raw = modal_value(modal, ANCIEN_DELAY_INPUT_ID).unwrap_or_default();
|
||||
let Some(delay_seconds) = parse_delay_seconds(&delay_raw) else {
|
||||
respond_ephemeral_modal(
|
||||
ctx,
|
||||
modal,
|
||||
"Delai invalide. Exemples valides: 30j, 72h, 90m.",
|
||||
)
|
||||
.await;
|
||||
return true;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let current = db::get_or_create_old_member_settings(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let Some(current) = current else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let updated = db::update_old_member_settings(
|
||||
&pool,
|
||||
bot_id,
|
||||
guild_id.get() as i64,
|
||||
Some(role_id.get() as i64),
|
||||
delay_seconds,
|
||||
current.enabled,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if let Some(updated) = updated {
|
||||
let _ = modal
|
||||
.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(ancien_embed(&updated).color(theme_color(ctx).await))
|
||||
.components(ancien_components(modal.user.id, &updated))
|
||||
.ephemeral(true),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn maybe_assign_ancien_role(ctx: &Context, guild_id: GuildId, user_id: UserId) {
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let settings = db::get_or_create_old_member_settings(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let Some(settings) = settings else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !settings.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(role_raw) = settings.role_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(role_id_u64) = u64::try_from(role_raw) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let role_id = RoleId::new(role_id_u64);
|
||||
|
||||
let Ok(member) = guild_id.member(&ctx.http, user_id).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
if member.user.bot || member.roles.contains(&role_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let joined_at = member.joined_at.unwrap_or_else(|| member.user.created_at());
|
||||
let elapsed = Utc::now()
|
||||
.timestamp()
|
||||
.saturating_sub(joined_at.unix_timestamp());
|
||||
|
||||
if elapsed < settings.delay_seconds.max(1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = member.add_role(&ctx.http, role_id).await;
|
||||
}
|
||||
|
||||
pub struct AncienCommand;
|
||||
pub static COMMAND_DESCRIPTOR: AncienCommand = AncienCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for AncienCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "ancien",
|
||||
category: "roles",
|
||||
params: "aucun",
|
||||
description: "Definit au bout de combien de temps un membre est considere comme ancien et recoit le role configure.",
|
||||
examples: &["+ancien", "+help ancien"],
|
||||
default_aliases: &[],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::{send_embed, theme_color};
|
||||
use crate::commands::moderation_sanction_helpers::parse_targets;
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_derank(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
@@ -18,12 +22,27 @@ pub async fn handle_derank(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let protected_roles: HashSet<u64> = if let Some(pool) = pool(ctx).await {
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
db::list_noderank_roles(&pool, bot_id, guild_id.get() as i64)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|id| u64::try_from(id).ok())
|
||||
.collect()
|
||||
} else {
|
||||
HashSet::new()
|
||||
};
|
||||
|
||||
let mut done = 0usize;
|
||||
for uid in &targets {
|
||||
if let Ok(member) = guild_id.member(&ctx.http, *uid).await {
|
||||
let roles = member.roles.clone();
|
||||
let mut ok = true;
|
||||
for role_id in roles {
|
||||
if protected_roles.contains(&role_id.get()) {
|
||||
continue;
|
||||
}
|
||||
if member.remove_role(&ctx.http, role_id).await.is_err() {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::automod_service::pool;
|
||||
use crate::commands::common::{parse_role, send_embed};
|
||||
use crate::db;
|
||||
|
||||
pub async fn handle_noderank(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||
let Some(guild_id) = msg.guild_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(pool) = pool(ctx).await else {
|
||||
return;
|
||||
};
|
||||
let bot_id = ctx.cache.current_user().id.get() as i64;
|
||||
let guild_id_raw = guild_id.get() as i64;
|
||||
|
||||
if args.is_empty() {
|
||||
let roles = db::list_noderank_roles(&pool, bot_id, guild_id_raw)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let description = if roles.is_empty() {
|
||||
"Aucun role protege.".to_string()
|
||||
} else {
|
||||
roles
|
||||
.iter()
|
||||
.map(|role_id| format!("<@&{}>", role_id))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
};
|
||||
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(description)
|
||||
.color(0x5865F2),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(guild) = guild_id.to_partial_guild(&ctx.http).await else {
|
||||
return;
|
||||
};
|
||||
let Some(role) = parse_role(&guild, args[1]) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if args[0].eq_ignore_ascii_case("add") {
|
||||
let _ = db::add_noderank_role(&pool, bot_id, guild_id_raw, role.id.get() as i64).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(format!("Role protege ajoute: <@&{}>", role.id.get()))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if args[0].eq_ignore_ascii_case("del") || args[0].eq_ignore_ascii_case("remove") {
|
||||
let _ = db::remove_noderank_role(&pool, bot_id, guild_id_raw, role.id.get() as i64).await;
|
||||
send_embed(
|
||||
ctx,
|
||||
msg,
|
||||
CreateEmbed::new()
|
||||
.title("NoDeRank")
|
||||
.description(format!("Role protege retire: <@&{}>", role.id.get()))
|
||||
.color(0x57F287),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoderankCommand;
|
||||
pub static COMMAND_DESCRIPTOR: NoderankCommand = NoderankCommand;
|
||||
|
||||
impl crate::commands::command_contract::CommandSpec for NoderankCommand {
|
||||
fn metadata(&self) -> crate::commands::command_contract::CommandMetadata {
|
||||
crate::commands::command_contract::CommandMetadata {
|
||||
name: "noderank",
|
||||
category: "roles",
|
||||
params: "[add/del <@role/ID/nom>]",
|
||||
description: "Definit des roles proteges qui ne sont pas retires par +derank.",
|
||||
examples: &["+noderank", "+noderank add @VIP", "+noderank del @VIP"],
|
||||
default_aliases: &["ndr"],
|
||||
allow_in_dm: false,
|
||||
default_permission: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
+1167
-111
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::logs_service;
|
||||
use crate::commands::{ancien, logs_service};
|
||||
|
||||
pub async fn handle_member_addition(ctx: &Context, new_member: &Member) {
|
||||
logs_service::on_member_join(ctx, new_member.guild_id, &new_member.user).await;
|
||||
ancien::maybe_assign_ancien_role(ctx, new_member.guild_id, new_member.user.id).await;
|
||||
}
|
||||
|
||||
pub async fn handle_member_removal(ctx: &Context, guild_id: GuildId, user: &User) {
|
||||
@@ -35,6 +36,8 @@ pub async fn handle_member_update(
|
||||
new_member.premium_since,
|
||||
)
|
||||
.await;
|
||||
|
||||
ancien::maybe_assign_ancien_role(ctx, new_member.guild_id, new_member.user.id).await;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::{
|
||||
advanced_tools, autoconfiglog, boostembed, help, helpsetting, mp, perms_service, rolemenu,
|
||||
suggestion, tempvoc, ticket, viewlogs,
|
||||
advanced_tools, ancien, autoconfiglog, boostembed, help, helpsetting, mp, perms_service,
|
||||
rolemenu, suggestion, tempvoc, ticket, viewlogs,
|
||||
};
|
||||
|
||||
pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) {
|
||||
@@ -14,6 +14,10 @@ pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction)
|
||||
}
|
||||
|
||||
if let Interaction::Component(component) = interaction {
|
||||
if ancien::handle_component_interaction(ctx, component).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if autoconfiglog::handle_component_interaction(ctx, component).await {
|
||||
return;
|
||||
}
|
||||
@@ -63,6 +67,10 @@ pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction)
|
||||
}
|
||||
|
||||
if let Interaction::Modal(modal) = interaction {
|
||||
if ancien::handle_modal_interaction(ctx, modal).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if ticket::handle_modal_interaction(ctx, modal).await {
|
||||
return;
|
||||
}
|
||||
|
||||
+78
-15
@@ -6,19 +6,22 @@ use std::sync::{Mutex, OnceLock};
|
||||
use crate::commands::moderation_tools;
|
||||
use crate::commands::remove_activity;
|
||||
use crate::commands::{
|
||||
addrole, alias, autobackup, autoconfiglog, autopublish, autoreact, backup, ban, banlist,
|
||||
banner, bl, blinfo, boostembed, boosters, boostlog, bringall, button, calc, change, changeall,
|
||||
channel, choose, claim, cleanup, clear_all_sanctions, clear_bl, clear_messages, clear_owners,
|
||||
clear_perms, clear_sanctions, close, cmute, compet, create, del, del_sanction, delrole, derank,
|
||||
discussion, dnd, embed, emoji, end, giveaway, help, helpsetting, hide, hideall, idle,
|
||||
invisible, invite, join, kick, leave, leave_settings, listen, loading, lock, lockall,
|
||||
mainprefix, massiverole, member, messagelog, modlog, mp, mute, mutelist, newsticker, nolog,
|
||||
online, owner, perms, pic, ping, playto, prefix, raidlog, rename, renew, reroll, role, rolelog,
|
||||
addrole, alias, ancien, antilink, antimassmention, antiraideautoconfig, antispam, autobackup,
|
||||
autoconfiglog, autopublish, autoreact, backup, badwords, ban, banlist, banner, bl, blinfo,
|
||||
boostembed, boosters, boostlog, bringall, button, calc, change, changeall, channel, choose,
|
||||
claim, cleanup, clear_all_sanctions, clear_badwords, clear_bl, clear_limit, clear_messages,
|
||||
clear_owners, clear_perms, clear_sanctions, close, cmute, compet, create, del, del_sanction,
|
||||
delrole, derank, discussion, dnd, embed, emoji, end, giveaway, help, helpsetting, hide,
|
||||
hideall, idle, invisible, invite, join, kick, leave, leave_settings, link, listen, loading,
|
||||
lock, lockall, mainprefix, massiverole, member, messagelog, modlog, mp, mute, mutelist,
|
||||
muterole, newsticker, noderank, nolog, online, owner, perms, pic, piconly, ping, playto,
|
||||
prefix, public, punish, raidlog, rename, renew, reroll, resetantiraide, role, rolelog,
|
||||
rolemembers, rolemenu, sanctions, say, server, serverinfo, set, set_boostembed, set_modlogs,
|
||||
shadowbot, showpics, slowmode, snipe, stream, suggestion, sync, tempban, tempcmute, tempmute,
|
||||
temprole, tempvoc, tempvoc_cmd, theme, ticket, ticket_member, tickets, unban, unbanall, unbl,
|
||||
uncmute, unhide, unhideall, unlock, unlockall, unmassiverole, unmute, unmuteall, unowner,
|
||||
untemprole, user, viewlogs, vocinfo, voicekick, voicelog, voicemove, warn, watch,
|
||||
set_muterole, shadowbot, showpics, slowmode, snipe, spam, stream, strikes, suggestion, sync,
|
||||
tempban, tempcmute, tempmute, temprole, tempvoc, tempvoc_cmd, theme, ticket, ticket_member,
|
||||
tickets, timeout, unban, unbanall, unbl, uncmute, unhide, unhideall, unlock, unlockall,
|
||||
unmassiverole, unmute, unmuteall, unowner, untemprole, user, viewlogs, vocinfo, voicekick,
|
||||
voicelog, voicemove, warn, watch,
|
||||
};
|
||||
use crate::commands::{alladmins, allbots, allperms, botadmins};
|
||||
use crate::db::{DbPoolKey, upsert_message_observed};
|
||||
@@ -83,12 +86,24 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(guild_id) = msg.guild_id {
|
||||
ancien::maybe_assign_ancien_role(ctx, guild_id, msg.author.id).await;
|
||||
}
|
||||
|
||||
let content = msg.content.trim();
|
||||
let prefix_value = permissions::resolve_prefix(ctx, msg.guild_id).await;
|
||||
if piconly::enforce_piconly_message(ctx, msg, content, &prefix_value).await {
|
||||
return;
|
||||
}
|
||||
|
||||
crate::commands::advanced_tools::apply_autoreacts(ctx, msg).await;
|
||||
crate::commands::advanced_tools::maybe_run_maintenance(ctx, msg.guild_id).await;
|
||||
moderation_tools::maybe_run_maintenance(ctx, msg.guild_id).await;
|
||||
|
||||
let content = msg.content.trim();
|
||||
let prefix_value = permissions::resolve_prefix(ctx, msg.guild_id).await;
|
||||
if crate::commands::automod_service::enforce_automod_message(ctx, msg).await {
|
||||
return;
|
||||
}
|
||||
|
||||
if !content.starts_with(&prefix_value) {
|
||||
return;
|
||||
}
|
||||
@@ -151,9 +166,15 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
}
|
||||
}
|
||||
|
||||
let required = permissions::command_required_permission(ctx, &command_key).await;
|
||||
if !crate::commands::automod_service::public_command_allowed(ctx, msg, &command_key, required)
|
||||
.await
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let can_use = permissions::can_use_command(ctx, msg, &command_key).await;
|
||||
if !can_use {
|
||||
let required = permissions::command_required_permission(ctx, &command_key).await;
|
||||
permissions::deny_permission(ctx, msg, &command_key, required).await;
|
||||
return;
|
||||
}
|
||||
@@ -190,6 +211,7 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
{
|
||||
showpics::handle_show_pics(ctx, msg, &args[1..]).await
|
||||
}
|
||||
"piconly" => piconly::handle_piconly(ctx, msg, &args).await,
|
||||
"suggestion" => suggestion::handle_suggestion(ctx, msg, &args).await,
|
||||
"autopublish" => autopublish::handle_autopublish(ctx, msg, &args).await,
|
||||
"tempvoc"
|
||||
@@ -202,6 +224,21 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
}
|
||||
"tempvoc" => tempvoc::handle_tempvoc(ctx, msg, &args).await,
|
||||
"ping" => ping::handle_ping(ctx, msg, &args).await,
|
||||
"timeout" => timeout::handle_timeout_toggle(ctx, msg, &args).await,
|
||||
"muterole" => muterole::handle_muterole(ctx, msg, &args).await,
|
||||
"antispam" => antispam::handle_antispam(ctx, msg, &args).await,
|
||||
"antiraideautoconfig" => {
|
||||
antiraideautoconfig::handle_antiraideautoconfig(ctx, msg, &args).await
|
||||
}
|
||||
"antilink" => antilink::handle_antilink(ctx, msg, &args).await,
|
||||
"antimassmention" => antimassmention::handle_antimassmention(ctx, msg, &args).await,
|
||||
"badwords" => badwords::handle_badwords(ctx, msg, &args).await,
|
||||
"spam" => spam::handle_spam_override(ctx, msg, &args).await,
|
||||
"link" => link::handle_link_override(ctx, msg, &args).await,
|
||||
"strikes" => strikes::handle_strikes(ctx, msg, &args).await,
|
||||
"punish" => punish::handle_punish(ctx, msg, &args).await,
|
||||
"public" => public::handle_public(ctx, msg, &args).await,
|
||||
"resetantiraide" => resetantiraide::handle_resetantiraide(ctx, msg, &args).await,
|
||||
"allbots" => allbots::handle_allbots(ctx, msg, &args).await,
|
||||
"alladmins" => alladmins::handle_alladmins(ctx, msg, &args).await,
|
||||
"botadmins" => botadmins::handle_botadmins(ctx, msg, &args).await,
|
||||
@@ -211,6 +248,7 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
"vocinfo" => vocinfo::handle_vocinfo(ctx, msg, &args).await,
|
||||
"role" => role::handle_role(ctx, msg, &args).await,
|
||||
"rolemenu" => rolemenu::handle_rolemenu(ctx, msg, &args).await,
|
||||
"ancien" => ancien::handle_ancien(ctx, msg, &args).await,
|
||||
"channel" => channel::handle_channel(ctx, msg, &args).await,
|
||||
"user" => user::handle_user(ctx, msg, &args).await,
|
||||
"member" => member::handle_member(ctx, msg, &args).await,
|
||||
@@ -359,6 +397,7 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
derank::handle_derank(ctx, msg, &args).await;
|
||||
crate::commands::logs_service::log_moderation_command(ctx, msg, "derank", &args).await;
|
||||
}
|
||||
"noderank" => noderank::handle_noderank(ctx, msg, &args).await,
|
||||
"temprole" => temprole::handle_temprole(ctx, msg, &args).await,
|
||||
"untemprole" => untemprole::handle_untemprole(ctx, msg, &args).await,
|
||||
"sync" => sync::handle_sync(ctx, msg, &args).await,
|
||||
@@ -366,6 +405,14 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
"autoreact" => autoreact::handle_autoreact(ctx, msg, &args).await,
|
||||
"calc" => calc::handle_calc(ctx, msg, &args).await,
|
||||
"shadowbot" => shadowbot::handle_shadowbot(ctx, msg, &args).await,
|
||||
"set"
|
||||
if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("muterole"))
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
set_muterole::handle_set_muterole(ctx, msg, &args).await
|
||||
}
|
||||
"set"
|
||||
if args
|
||||
.first()
|
||||
@@ -453,6 +500,22 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
||||
{
|
||||
clear_perms::handle_clear_perms(ctx, msg).await
|
||||
}
|
||||
"clear"
|
||||
if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("limit"))
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
clear_limit::handle_clear_limit(ctx, msg, &args).await
|
||||
}
|
||||
"clear"
|
||||
if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("badwords"))
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
clear_badwords::handle_clear_badwords(ctx, msg, &args).await
|
||||
}
|
||||
"clear"
|
||||
if args
|
||||
.first()
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use serenity::model::prelude::*;
|
||||
use serenity::prelude::*;
|
||||
|
||||
use crate::commands::{admin_service, botconfig_service, help};
|
||||
use crate::commands::{admin_service, botconfig_service, help, tempvoc};
|
||||
|
||||
pub async fn handle_ready(ctx: &Context, ready: &Ready) {
|
||||
botconfig_service::restore_presence_from_db(ctx).await;
|
||||
help::register_slash_help(ctx).await;
|
||||
tempvoc::cleanup_stale_rooms_on_ready(ctx).await;
|
||||
|
||||
for guild_id in ctx.cache.guilds() {
|
||||
admin_service::enforce_blacklist_on_guild(ctx, guild_id).await;
|
||||
|
||||
+56
-2
@@ -82,6 +82,18 @@ pub fn command_key(command: &str, args: &[&str]) -> String {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"clear_bl".to_string()
|
||||
} else if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("limit"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"clear_limit".to_string()
|
||||
} else if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("badwords"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"clear_badwords".to_string()
|
||||
} else if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("perms"))
|
||||
@@ -132,6 +144,12 @@ pub fn command_key(command: &str, args: &[&str]) -> String {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"set_perm".to_string()
|
||||
} else if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("muterole"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
"set_muterole".to_string()
|
||||
} else if args
|
||||
.first()
|
||||
.map(|s| s.eq_ignore_ascii_case("modlogs"))
|
||||
@@ -233,6 +251,7 @@ pub fn all_command_keys() -> Vec<String> {
|
||||
"boosters",
|
||||
"rolemembers",
|
||||
"rolemenu",
|
||||
"ancien",
|
||||
"serverinfo",
|
||||
"vocinfo",
|
||||
"role",
|
||||
@@ -252,6 +271,23 @@ pub fn all_command_keys() -> Vec<String> {
|
||||
"clear_sanctions",
|
||||
"clear_all_sanctions",
|
||||
"clear_messages",
|
||||
"clear_limit",
|
||||
"clear_badwords",
|
||||
"timeout",
|
||||
"muterole",
|
||||
"set_muterole",
|
||||
"antispam",
|
||||
"antiraideautoconfig",
|
||||
"antilink",
|
||||
"antimassmention",
|
||||
"badwords",
|
||||
"spam",
|
||||
"link",
|
||||
"strikes",
|
||||
"punish",
|
||||
"noderank",
|
||||
"public",
|
||||
"resetantiraide",
|
||||
"warn",
|
||||
"mute",
|
||||
"tempmute",
|
||||
@@ -290,6 +326,7 @@ pub fn all_command_keys() -> Vec<String> {
|
||||
"ticket_close",
|
||||
"tickets",
|
||||
"show_pics",
|
||||
"piconly",
|
||||
"suggestion_create",
|
||||
"suggestion_settings",
|
||||
"autopublish",
|
||||
@@ -374,14 +411,14 @@ pub fn all_command_keys() -> Vec<String> {
|
||||
|
||||
pub fn default_permission(command_key: &str) -> u8 {
|
||||
match command_key {
|
||||
"ticket_settings" | "suggestion_settings" | "autopublish" | "tempvoc" => 8,
|
||||
"ticket_settings" | "suggestion_settings" | "autopublish" | "piconly" | "tempvoc" => 8,
|
||||
"claim" | "rename" | "ticket_add" | "ticket_remove" | "ticket_close" | "tickets" => 2,
|
||||
"show_pics" | "suggestion_create" | "tempvoc_cmd" => 0,
|
||||
"owner" | "unowner" | "clear_owners" => 9,
|
||||
"bl" | "unbl" | "blinfo" | "clear_bl" => 9,
|
||||
"change" | "changeall" | "change_reset" | "mainprefix" | "set_perm" | "del_perm"
|
||||
| "clear_perms" => 9,
|
||||
"set_modlogs" | "set_boostembed" => 8,
|
||||
"set_modlogs" | "set_boostembed" | "set_muterole" => 8,
|
||||
"prefix" | "perms" | "allperms" => 8,
|
||||
"help" | "server_list" => 0,
|
||||
"helpsetting" | "alias" | "leave" => 9,
|
||||
@@ -428,6 +465,22 @@ pub fn default_permission(command_key: &str) -> u8 {
|
||||
| "clear_sanctions"
|
||||
| "clear_all_sanctions"
|
||||
| "clear_messages"
|
||||
| "clear_limit"
|
||||
| "clear_badwords"
|
||||
| "timeout"
|
||||
| "muterole"
|
||||
| "antispam"
|
||||
| "antiraideautoconfig"
|
||||
| "antilink"
|
||||
| "antimassmention"
|
||||
| "badwords"
|
||||
| "spam"
|
||||
| "link"
|
||||
| "strikes"
|
||||
| "punish"
|
||||
| "noderank"
|
||||
| "public"
|
||||
| "resetantiraide"
|
||||
| "warn"
|
||||
| "mute"
|
||||
| "tempmute"
|
||||
@@ -455,6 +508,7 @@ pub fn default_permission(command_key: &str) -> u8 {
|
||||
| "delrole"
|
||||
| "derank"
|
||||
| "rolemenu"
|
||||
| "ancien"
|
||||
| "modlog"
|
||||
| "messagelog"
|
||||
| "voicelog"
|
||||
|
||||
Reference in New Issue
Block a user