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::common::{send_embed, theme_color};
|
||||||
use crate::commands::logs_command_helpers::{pool, set_log_channel};
|
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_CATEGORY_NAME: &str = "📁 ➜ Espace Logs";
|
||||||
const LOG_CHANNEL_PREFIX: &str = "📁・";
|
const LOG_CHANNEL_PREFIX: &str = "📁・";
|
||||||
const AUTOCONFIGLOG_COMPONENT_PREFIX: &str = "autoconfiglog";
|
const AUTOCONFIGLOG_COMPONENT_PREFIX: &str = "autoconfiglog";
|
||||||
|
|||||||
@@ -125,7 +125,10 @@ pub async fn emit_log(
|
|||||||
|
|
||||||
embed = embed.timestamp(timestamp);
|
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 {
|
if let Some(log_channel_id) = get_log_channel(ctx, guild_id, log_type).await {
|
||||||
let _ = log_channel_id
|
let _ = log_channel_id
|
||||||
|
|||||||
@@ -13,16 +13,29 @@ pub mod alladmins;
|
|||||||
pub mod allbots;
|
pub mod allbots;
|
||||||
#[path = "permissions/allperms.rs"]
|
#[path = "permissions/allperms.rs"]
|
||||||
pub mod allperms;
|
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"]
|
#[path = "outils/autobackup.rs"]
|
||||||
pub mod autobackup;
|
pub mod autobackup;
|
||||||
#[path = "logs/autoconfiglog.rs"]
|
#[path = "logs/autoconfiglog.rs"]
|
||||||
pub mod autoconfiglog;
|
pub mod autoconfiglog;
|
||||||
|
pub mod automod_service;
|
||||||
#[path = "outils/autopublish.rs"]
|
#[path = "outils/autopublish.rs"]
|
||||||
pub mod autopublish;
|
pub mod autopublish;
|
||||||
#[path = "outils/autoreact.rs"]
|
#[path = "outils/autoreact.rs"]
|
||||||
pub mod autoreact;
|
pub mod autoreact;
|
||||||
#[path = "outils/backup.rs"]
|
#[path = "outils/backup.rs"]
|
||||||
pub mod backup;
|
pub mod backup;
|
||||||
|
#[path = "moderation/badwords.rs"]
|
||||||
|
pub mod badwords;
|
||||||
#[path = "moderation/ban.rs"]
|
#[path = "moderation/ban.rs"]
|
||||||
pub mod ban;
|
pub mod ban;
|
||||||
#[path = "moderation/banlist.rs"]
|
#[path = "moderation/banlist.rs"]
|
||||||
@@ -63,8 +76,12 @@ pub mod claim;
|
|||||||
pub mod cleanup;
|
pub mod cleanup;
|
||||||
#[path = "moderation/clear_all_sanctions.rs"]
|
#[path = "moderation/clear_all_sanctions.rs"]
|
||||||
pub mod clear_all_sanctions;
|
pub mod clear_all_sanctions;
|
||||||
|
#[path = "moderation/clear_badwords.rs"]
|
||||||
|
pub mod clear_badwords;
|
||||||
#[path = "administration/clear_bl.rs"]
|
#[path = "administration/clear_bl.rs"]
|
||||||
pub mod clear_bl;
|
pub mod clear_bl;
|
||||||
|
#[path = "moderation/clear_limit.rs"]
|
||||||
|
pub mod clear_limit;
|
||||||
#[path = "moderation/clear_messages.rs"]
|
#[path = "moderation/clear_messages.rs"]
|
||||||
pub mod clear_messages;
|
pub mod clear_messages;
|
||||||
#[path = "administration/clear_owners.rs"]
|
#[path = "administration/clear_owners.rs"]
|
||||||
@@ -125,6 +142,8 @@ pub mod kick;
|
|||||||
pub mod leave;
|
pub mod leave;
|
||||||
#[path = "logs/leave_settings.rs"]
|
#[path = "logs/leave_settings.rs"]
|
||||||
pub mod leave_settings;
|
pub mod leave_settings;
|
||||||
|
#[path = "moderation/link.rs"]
|
||||||
|
pub mod link;
|
||||||
#[path = "bot/listen.rs"]
|
#[path = "bot/listen.rs"]
|
||||||
pub mod listen;
|
pub mod listen;
|
||||||
#[path = "outils/loading.rs"]
|
#[path = "outils/loading.rs"]
|
||||||
@@ -154,8 +173,12 @@ pub mod mp;
|
|||||||
pub mod mute;
|
pub mod mute;
|
||||||
#[path = "moderation/mutelist.rs"]
|
#[path = "moderation/mutelist.rs"]
|
||||||
pub mod mutelist;
|
pub mod mutelist;
|
||||||
|
#[path = "moderation/muterole.rs"]
|
||||||
|
pub mod muterole;
|
||||||
#[path = "outils/newsticker.rs"]
|
#[path = "outils/newsticker.rs"]
|
||||||
pub mod newsticker;
|
pub mod newsticker;
|
||||||
|
#[path = "roles/noderank.rs"]
|
||||||
|
pub mod noderank;
|
||||||
#[path = "logs/nolog.rs"]
|
#[path = "logs/nolog.rs"]
|
||||||
pub mod nolog;
|
pub mod nolog;
|
||||||
#[path = "bot/online.rs"]
|
#[path = "bot/online.rs"]
|
||||||
@@ -168,12 +191,18 @@ pub mod perms_helpers;
|
|||||||
pub mod perms_service;
|
pub mod perms_service;
|
||||||
#[path = "infos/pic.rs"]
|
#[path = "infos/pic.rs"]
|
||||||
pub mod pic;
|
pub mod pic;
|
||||||
|
#[path = "outils/piconly.rs"]
|
||||||
|
pub mod piconly;
|
||||||
#[path = "infos/ping.rs"]
|
#[path = "infos/ping.rs"]
|
||||||
pub mod ping;
|
pub mod ping;
|
||||||
#[path = "bot/playto.rs"]
|
#[path = "bot/playto.rs"]
|
||||||
pub mod playto;
|
pub mod playto;
|
||||||
#[path = "administration/prefix.rs"]
|
#[path = "administration/prefix.rs"]
|
||||||
pub mod prefix;
|
pub mod prefix;
|
||||||
|
#[path = "moderation/public.rs"]
|
||||||
|
pub mod public;
|
||||||
|
#[path = "moderation/punish.rs"]
|
||||||
|
pub mod punish;
|
||||||
#[path = "logs/raidlog.rs"]
|
#[path = "logs/raidlog.rs"]
|
||||||
pub mod raidlog;
|
pub mod raidlog;
|
||||||
#[path = "bot/remove_activity.rs"]
|
#[path = "bot/remove_activity.rs"]
|
||||||
@@ -184,6 +213,8 @@ pub mod rename;
|
|||||||
pub mod renew;
|
pub mod renew;
|
||||||
#[path = "outils/reroll.rs"]
|
#[path = "outils/reroll.rs"]
|
||||||
pub mod reroll;
|
pub mod reroll;
|
||||||
|
#[path = "moderation/resetantiraide.rs"]
|
||||||
|
pub mod resetantiraide;
|
||||||
#[path = "infos/role.rs"]
|
#[path = "infos/role.rs"]
|
||||||
pub mod role;
|
pub mod role;
|
||||||
#[path = "logs/rolelog.rs"]
|
#[path = "logs/rolelog.rs"]
|
||||||
@@ -206,6 +237,8 @@ pub mod set;
|
|||||||
pub mod set_boostembed;
|
pub mod set_boostembed;
|
||||||
#[path = "logs/set_modlogs.rs"]
|
#[path = "logs/set_modlogs.rs"]
|
||||||
pub mod set_modlogs;
|
pub mod set_modlogs;
|
||||||
|
#[path = "moderation/set_muterole.rs"]
|
||||||
|
pub mod set_muterole;
|
||||||
#[path = "bot/shadowbot.rs"]
|
#[path = "bot/shadowbot.rs"]
|
||||||
pub mod shadowbot;
|
pub mod shadowbot;
|
||||||
#[path = "infos/showpics.rs"]
|
#[path = "infos/showpics.rs"]
|
||||||
@@ -214,8 +247,12 @@ pub mod showpics;
|
|||||||
pub mod slowmode;
|
pub mod slowmode;
|
||||||
#[path = "outils/snipe.rs"]
|
#[path = "outils/snipe.rs"]
|
||||||
pub mod snipe;
|
pub mod snipe;
|
||||||
|
#[path = "moderation/spam.rs"]
|
||||||
|
pub mod spam;
|
||||||
#[path = "bot/stream.rs"]
|
#[path = "bot/stream.rs"]
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
#[path = "moderation/strikes.rs"]
|
||||||
|
pub mod strikes;
|
||||||
#[path = "outils/suggestion.rs"]
|
#[path = "outils/suggestion.rs"]
|
||||||
pub mod suggestion;
|
pub mod suggestion;
|
||||||
#[path = "roles/sync.rs"]
|
#[path = "roles/sync.rs"]
|
||||||
@@ -240,6 +277,8 @@ pub mod ticket;
|
|||||||
pub mod ticket_member;
|
pub mod ticket_member;
|
||||||
#[path = "outils/tickets.rs"]
|
#[path = "outils/tickets.rs"]
|
||||||
pub mod tickets;
|
pub mod tickets;
|
||||||
|
#[path = "moderation/timeout.rs"]
|
||||||
|
pub mod timeout;
|
||||||
#[path = "moderation/unban.rs"]
|
#[path = "moderation/unban.rs"]
|
||||||
pub mod unban;
|
pub mod unban;
|
||||||
#[path = "moderation/unbanall.rs"]
|
#[path = "moderation/unbanall.rs"]
|
||||||
@@ -286,9 +325,12 @@ pub mod watch;
|
|||||||
pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
||||||
vec![
|
vec![
|
||||||
ping::COMMAND_DESCRIPTOR.metadata(),
|
ping::COMMAND_DESCRIPTOR.metadata(),
|
||||||
|
timeout::COMMAND_DESCRIPTOR.metadata(),
|
||||||
allbots::COMMAND_DESCRIPTOR.metadata(),
|
allbots::COMMAND_DESCRIPTOR.metadata(),
|
||||||
alladmins::COMMAND_DESCRIPTOR.metadata(),
|
alladmins::COMMAND_DESCRIPTOR.metadata(),
|
||||||
botadmins::COMMAND_DESCRIPTOR.metadata(),
|
botadmins::COMMAND_DESCRIPTOR.metadata(),
|
||||||
|
ancien::COMMAND_DESCRIPTOR.metadata(),
|
||||||
|
antiraideautoconfig::COMMAND_DESCRIPTOR.metadata(),
|
||||||
boosters::COMMAND_DESCRIPTOR.metadata(),
|
boosters::COMMAND_DESCRIPTOR.metadata(),
|
||||||
rolemembers::COMMAND_DESCRIPTOR.metadata(),
|
rolemembers::COMMAND_DESCRIPTOR.metadata(),
|
||||||
rolemenu::COMMAND_DESCRIPTOR.metadata(),
|
rolemenu::COMMAND_DESCRIPTOR.metadata(),
|
||||||
@@ -322,6 +364,20 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
|||||||
choose::COMMAND_DESCRIPTOR.metadata(),
|
choose::COMMAND_DESCRIPTOR.metadata(),
|
||||||
embed::COMMAND_DESCRIPTOR.metadata(),
|
embed::COMMAND_DESCRIPTOR.metadata(),
|
||||||
clear_messages::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(),
|
backup::COMMAND_DESCRIPTOR.metadata(),
|
||||||
ticket::COMMAND_DESCRIPTOR.metadata(),
|
ticket::COMMAND_DESCRIPTOR.metadata(),
|
||||||
claim::COMMAND_DESCRIPTOR.metadata(),
|
claim::COMMAND_DESCRIPTOR.metadata(),
|
||||||
@@ -330,6 +386,7 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
|||||||
close::COMMAND_DESCRIPTOR.metadata(),
|
close::COMMAND_DESCRIPTOR.metadata(),
|
||||||
tickets::COMMAND_DESCRIPTOR.metadata(),
|
tickets::COMMAND_DESCRIPTOR.metadata(),
|
||||||
showpics::COMMAND_DESCRIPTOR.metadata(),
|
showpics::COMMAND_DESCRIPTOR.metadata(),
|
||||||
|
piconly::COMMAND_DESCRIPTOR.metadata(),
|
||||||
suggestion::COMMAND_DESCRIPTOR.metadata(),
|
suggestion::COMMAND_DESCRIPTOR.metadata(),
|
||||||
autopublish::COMMAND_DESCRIPTOR.metadata(),
|
autopublish::COMMAND_DESCRIPTOR.metadata(),
|
||||||
tempvoc::COMMAND_DESCRIPTOR.metadata(),
|
tempvoc::COMMAND_DESCRIPTOR.metadata(),
|
||||||
@@ -372,6 +429,7 @@ pub fn all_command_metadata() -> Vec<CommandMetadata> {
|
|||||||
addrole::COMMAND_DESCRIPTOR.metadata(),
|
addrole::COMMAND_DESCRIPTOR.metadata(),
|
||||||
delrole::COMMAND_DESCRIPTOR.metadata(),
|
delrole::COMMAND_DESCRIPTOR.metadata(),
|
||||||
derank::COMMAND_DESCRIPTOR.metadata(),
|
derank::COMMAND_DESCRIPTOR.metadata(),
|
||||||
|
noderank::COMMAND_DESCRIPTOR.metadata(),
|
||||||
del_sanction::COMMAND_DESCRIPTOR.metadata(),
|
del_sanction::COMMAND_DESCRIPTOR.metadata(),
|
||||||
clear_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
clear_sanctions::COMMAND_DESCRIPTOR.metadata(),
|
||||||
clear_all_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::admin_common::parse_user_id;
|
||||||
use crate::commands::common::{send_embed, theme_color};
|
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]) {
|
pub async fn handle_clear_messages(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||||
let Ok(mut amount) = args.first().unwrap_or(&"0").parse::<u64>() else {
|
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 {
|
if amount == 0 {
|
||||||
return;
|
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));
|
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 serenity::prelude::*;
|
||||||
|
|
||||||
use crate::commands::admin_common::parse_user_id;
|
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> {
|
pub fn duration_from_input(input: &str) -> Option<Duration> {
|
||||||
let raw = input.trim().to_lowercase();
|
let raw = input.trim().to_lowercase();
|
||||||
@@ -95,9 +95,30 @@ pub async fn handle_timeout(
|
|||||||
users: &[UserId],
|
users: &[UserId],
|
||||||
expires: Option<chrono::DateTime<Utc>>,
|
expires: Option<chrono::DateTime<Utc>>,
|
||||||
) -> usize {
|
) -> 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;
|
let mut done = 0usize;
|
||||||
for user_id in users {
|
for user_id in users {
|
||||||
if let Ok(mut member) = guild_id.member(&ctx.http, *user_id).await {
|
if let Ok(mut member) = guild_id.member(&ctx.http, *user_id).await {
|
||||||
|
if use_timeout {
|
||||||
let mut builder = EditMember::new();
|
let mut builder = EditMember::new();
|
||||||
if let Some(ts) = expires {
|
if let Some(ts) = expires {
|
||||||
if let Ok(discord_ts) = Timestamp::from_unix_timestamp(ts.timestamp()) {
|
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() {
|
if member.edit(&ctx.http, builder).await.is_ok() {
|
||||||
done += 1;
|
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
|
done
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use std::sync::{Mutex, OnceLock};
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serenity::builder::EditMember;
|
|
||||||
use serenity::model::prelude::*;
|
use serenity::model::prelude::*;
|
||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
|
|
||||||
|
use crate::commands::moderation_sanction_helpers::{channel_mute_users, handle_timeout};
|
||||||
use crate::db::DbPoolKey;
|
use crate::db::DbPoolKey;
|
||||||
|
|
||||||
static MODERATION_TICK: OnceLock<Mutex<Instant>> = OnceLock::new();
|
static MODERATION_TICK: OnceLock<Mutex<Instant>> = OnceLock::new();
|
||||||
@@ -15,66 +15,6 @@ async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
|
|||||||
data.get::<DbPoolKey>().cloned()
|
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>) {
|
pub async fn maybe_run_maintenance(ctx: &Context, guild_id: Option<GuildId>) {
|
||||||
let Some(guild_id) = guild_id else {
|
let Some(guild_id) = guild_id else {
|
||||||
return;
|
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))
|
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) {
|
if let Some(key) = help_lookup_to_key(input) {
|
||||||
return Some(key.to_string());
|
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());
|
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("Shadow Bot est un bot de gestion de serveur.".to_string());
|
||||||
intro_lines.push(String::new());
|
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());
|
intro_lines.push("**Nombre de commandes par catégorie :**".to_string());
|
||||||
for (index, page) in HELP_PAGES.iter().enumerate() {
|
for (index, page) in HELP_PAGES.iter().enumerate() {
|
||||||
intro_lines.push(format!("• {} : {}", page.title, counts[index]));
|
intro_lines.push(format!("• {} : {}", page.title, counts[index]));
|
||||||
@@ -767,9 +773,8 @@ fn help_components(
|
|||||||
HelpLayout::Select | HelpLayout::Hybrid => {
|
HelpLayout::Select | HelpLayout::Hybrid => {
|
||||||
let mut options = Vec::with_capacity(HELP_PAGES.len() + 1);
|
let mut options = Vec::with_capacity(HELP_PAGES.len() + 1);
|
||||||
options.push(
|
options.push(
|
||||||
CreateSelectMenuOption::new("Présentation", "0").description(
|
CreateSelectMenuOption::new("Présentation", "0")
|
||||||
"Shadow Bot, total des commandes et répartition par catégorie.",
|
.description("Shadow Bot, total des commandes et répartition par catégorie."),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for (index, page) in HELP_PAGES.iter().enumerate() {
|
for (index, page) in HELP_PAGES.iter().enumerate() {
|
||||||
@@ -849,7 +854,10 @@ async fn build_command_help_embed(
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
let mut embed = CreateEmbed::new()
|
let mut embed = CreateEmbed::new()
|
||||||
.title(format!("Aide commande · +{}", doc.command.replace('_', " ")))
|
.title(format!(
|
||||||
|
"Aide commande · +{}",
|
||||||
|
doc.command.replace('_', " ")
|
||||||
|
))
|
||||||
.description(doc.description)
|
.description(doc.description)
|
||||||
.field(
|
.field(
|
||||||
"Commande",
|
"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::builder::CreateEmbed;
|
||||||
use serenity::model::prelude::*;
|
use serenity::model::prelude::*;
|
||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
|
|
||||||
|
use crate::commands::automod_service::pool;
|
||||||
use crate::commands::common::{send_embed, theme_color};
|
use crate::commands::common::{send_embed, theme_color};
|
||||||
use crate::commands::moderation_sanction_helpers::parse_targets;
|
use crate::commands::moderation_sanction_helpers::parse_targets;
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
pub async fn handle_derank(ctx: &Context, msg: &Message, args: &[&str]) {
|
pub async fn handle_derank(ctx: &Context, msg: &Message, args: &[&str]) {
|
||||||
let Some(guild_id) = msg.guild_id else {
|
let Some(guild_id) = msg.guild_id else {
|
||||||
@@ -18,12 +22,27 @@ pub async fn handle_derank(ctx: &Context, msg: &Message, args: &[&str]) {
|
|||||||
return;
|
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;
|
let mut done = 0usize;
|
||||||
for uid in &targets {
|
for uid in &targets {
|
||||||
if let Ok(member) = guild_id.member(&ctx.http, *uid).await {
|
if let Ok(member) = guild_id.member(&ctx.http, *uid).await {
|
||||||
let roles = member.roles.clone();
|
let roles = member.roles.clone();
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
for role_id in roles {
|
for role_id in roles {
|
||||||
|
if protected_roles.contains(&role_id.get()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if member.remove_role(&ctx.http, role_id).await.is_err() {
|
if member.remove_role(&ctx.http, role_id).await.is_err() {
|
||||||
ok = false;
|
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::model::prelude::*;
|
||||||
use serenity::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) {
|
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;
|
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) {
|
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,
|
new_member.premium_since,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
ancien::maybe_assign_ancien_role(ctx, new_member.guild_id, new_member.user.id).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use serenity::model::prelude::*;
|
|||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
|
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
advanced_tools, autoconfiglog, boostembed, help, helpsetting, mp, perms_service, rolemenu,
|
advanced_tools, ancien, autoconfiglog, boostembed, help, helpsetting, mp, perms_service,
|
||||||
suggestion, tempvoc, ticket, viewlogs,
|
rolemenu, suggestion, tempvoc, ticket, viewlogs,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) {
|
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 let Interaction::Component(component) = interaction {
|
||||||
|
if ancien::handle_component_interaction(ctx, component).await {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if autoconfiglog::handle_component_interaction(ctx, component).await {
|
if autoconfiglog::handle_component_interaction(ctx, component).await {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -63,6 +67,10 @@ pub async fn handle_interaction_create(ctx: &Context, interaction: &Interaction)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Interaction::Modal(modal) = interaction {
|
if let Interaction::Modal(modal) = interaction {
|
||||||
|
if ancien::handle_modal_interaction(ctx, modal).await {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ticket::handle_modal_interaction(ctx, modal).await {
|
if ticket::handle_modal_interaction(ctx, modal).await {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-15
@@ -6,19 +6,22 @@ use std::sync::{Mutex, OnceLock};
|
|||||||
use crate::commands::moderation_tools;
|
use crate::commands::moderation_tools;
|
||||||
use crate::commands::remove_activity;
|
use crate::commands::remove_activity;
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
addrole, alias, autobackup, autoconfiglog, autopublish, autoreact, backup, ban, banlist,
|
addrole, alias, ancien, antilink, antimassmention, antiraideautoconfig, antispam, autobackup,
|
||||||
banner, bl, blinfo, boostembed, boosters, boostlog, bringall, button, calc, change, changeall,
|
autoconfiglog, autopublish, autoreact, backup, badwords, ban, banlist, banner, bl, blinfo,
|
||||||
channel, choose, claim, cleanup, clear_all_sanctions, clear_bl, clear_messages, clear_owners,
|
boostembed, boosters, boostlog, bringall, button, calc, change, changeall, channel, choose,
|
||||||
clear_perms, clear_sanctions, close, cmute, compet, create, del, del_sanction, delrole, derank,
|
claim, cleanup, clear_all_sanctions, clear_badwords, clear_bl, clear_limit, clear_messages,
|
||||||
discussion, dnd, embed, emoji, end, giveaway, help, helpsetting, hide, hideall, idle,
|
clear_owners, clear_perms, clear_sanctions, close, cmute, compet, create, del, del_sanction,
|
||||||
invisible, invite, join, kick, leave, leave_settings, listen, loading, lock, lockall,
|
delrole, derank, discussion, dnd, embed, emoji, end, giveaway, help, helpsetting, hide,
|
||||||
mainprefix, massiverole, member, messagelog, modlog, mp, mute, mutelist, newsticker, nolog,
|
hideall, idle, invisible, invite, join, kick, leave, leave_settings, link, listen, loading,
|
||||||
online, owner, perms, pic, ping, playto, prefix, raidlog, rename, renew, reroll, role, rolelog,
|
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,
|
rolemembers, rolemenu, sanctions, say, server, serverinfo, set, set_boostembed, set_modlogs,
|
||||||
shadowbot, showpics, slowmode, snipe, stream, suggestion, sync, tempban, tempcmute, tempmute,
|
set_muterole, shadowbot, showpics, slowmode, snipe, spam, stream, strikes, suggestion, sync,
|
||||||
temprole, tempvoc, tempvoc_cmd, theme, ticket, ticket_member, tickets, unban, unbanall, unbl,
|
tempban, tempcmute, tempmute, temprole, tempvoc, tempvoc_cmd, theme, ticket, ticket_member,
|
||||||
uncmute, unhide, unhideall, unlock, unlockall, unmassiverole, unmute, unmuteall, unowner,
|
tickets, timeout, unban, unbanall, unbl, uncmute, unhide, unhideall, unlock, unlockall,
|
||||||
untemprole, user, viewlogs, vocinfo, voicekick, voicelog, voicemove, warn, watch,
|
unmassiverole, unmute, unmuteall, unowner, untemprole, user, viewlogs, vocinfo, voicekick,
|
||||||
|
voicelog, voicemove, warn, watch,
|
||||||
};
|
};
|
||||||
use crate::commands::{alladmins, allbots, allperms, botadmins};
|
use crate::commands::{alladmins, allbots, allperms, botadmins};
|
||||||
use crate::db::{DbPoolKey, upsert_message_observed};
|
use crate::db::{DbPoolKey, upsert_message_observed};
|
||||||
@@ -83,12 +86,24 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
|||||||
return;
|
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::apply_autoreacts(ctx, msg).await;
|
||||||
crate::commands::advanced_tools::maybe_run_maintenance(ctx, msg.guild_id).await;
|
crate::commands::advanced_tools::maybe_run_maintenance(ctx, msg.guild_id).await;
|
||||||
moderation_tools::maybe_run_maintenance(ctx, msg.guild_id).await;
|
moderation_tools::maybe_run_maintenance(ctx, msg.guild_id).await;
|
||||||
|
|
||||||
let content = msg.content.trim();
|
if crate::commands::automod_service::enforce_automod_message(ctx, msg).await {
|
||||||
let prefix_value = permissions::resolve_prefix(ctx, msg.guild_id).await;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if !content.starts_with(&prefix_value) {
|
if !content.starts_with(&prefix_value) {
|
||||||
return;
|
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;
|
let can_use = permissions::can_use_command(ctx, msg, &command_key).await;
|
||||||
if !can_use {
|
if !can_use {
|
||||||
let required = permissions::command_required_permission(ctx, &command_key).await;
|
|
||||||
permissions::deny_permission(ctx, msg, &command_key, required).await;
|
permissions::deny_permission(ctx, msg, &command_key, required).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -190,6 +211,7 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
|||||||
{
|
{
|
||||||
showpics::handle_show_pics(ctx, msg, &args[1..]).await
|
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,
|
"suggestion" => suggestion::handle_suggestion(ctx, msg, &args).await,
|
||||||
"autopublish" => autopublish::handle_autopublish(ctx, msg, &args).await,
|
"autopublish" => autopublish::handle_autopublish(ctx, msg, &args).await,
|
||||||
"tempvoc"
|
"tempvoc"
|
||||||
@@ -202,6 +224,21 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
|||||||
}
|
}
|
||||||
"tempvoc" => tempvoc::handle_tempvoc(ctx, msg, &args).await,
|
"tempvoc" => tempvoc::handle_tempvoc(ctx, msg, &args).await,
|
||||||
"ping" => ping::handle_ping(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,
|
"allbots" => allbots::handle_allbots(ctx, msg, &args).await,
|
||||||
"alladmins" => alladmins::handle_alladmins(ctx, msg, &args).await,
|
"alladmins" => alladmins::handle_alladmins(ctx, msg, &args).await,
|
||||||
"botadmins" => botadmins::handle_botadmins(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,
|
"vocinfo" => vocinfo::handle_vocinfo(ctx, msg, &args).await,
|
||||||
"role" => role::handle_role(ctx, msg, &args).await,
|
"role" => role::handle_role(ctx, msg, &args).await,
|
||||||
"rolemenu" => rolemenu::handle_rolemenu(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,
|
"channel" => channel::handle_channel(ctx, msg, &args).await,
|
||||||
"user" => user::handle_user(ctx, msg, &args).await,
|
"user" => user::handle_user(ctx, msg, &args).await,
|
||||||
"member" => member::handle_member(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;
|
derank::handle_derank(ctx, msg, &args).await;
|
||||||
crate::commands::logs_service::log_moderation_command(ctx, msg, "derank", &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,
|
"temprole" => temprole::handle_temprole(ctx, msg, &args).await,
|
||||||
"untemprole" => untemprole::handle_untemprole(ctx, msg, &args).await,
|
"untemprole" => untemprole::handle_untemprole(ctx, msg, &args).await,
|
||||||
"sync" => sync::handle_sync(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,
|
"autoreact" => autoreact::handle_autoreact(ctx, msg, &args).await,
|
||||||
"calc" => calc::handle_calc(ctx, msg, &args).await,
|
"calc" => calc::handle_calc(ctx, msg, &args).await,
|
||||||
"shadowbot" => shadowbot::handle_shadowbot(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"
|
"set"
|
||||||
if args
|
if args
|
||||||
.first()
|
.first()
|
||||||
@@ -453,6 +500,22 @@ pub async fn handle_message(ctx: &Context, msg: &Message) {
|
|||||||
{
|
{
|
||||||
clear_perms::handle_clear_perms(ctx, msg).await
|
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"
|
"clear"
|
||||||
if args
|
if args
|
||||||
.first()
|
.first()
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use serenity::model::prelude::*;
|
use serenity::model::prelude::*;
|
||||||
use serenity::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) {
|
pub async fn handle_ready(ctx: &Context, ready: &Ready) {
|
||||||
botconfig_service::restore_presence_from_db(ctx).await;
|
botconfig_service::restore_presence_from_db(ctx).await;
|
||||||
help::register_slash_help(ctx).await;
|
help::register_slash_help(ctx).await;
|
||||||
|
tempvoc::cleanup_stale_rooms_on_ready(ctx).await;
|
||||||
|
|
||||||
for guild_id in ctx.cache.guilds() {
|
for guild_id in ctx.cache.guilds() {
|
||||||
admin_service::enforce_blacklist_on_guild(ctx, guild_id).await;
|
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)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
"clear_bl".to_string()
|
"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
|
} else if args
|
||||||
.first()
|
.first()
|
||||||
.map(|s| s.eq_ignore_ascii_case("perms"))
|
.map(|s| s.eq_ignore_ascii_case("perms"))
|
||||||
@@ -132,6 +144,12 @@ pub fn command_key(command: &str, args: &[&str]) -> String {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
"set_perm".to_string()
|
"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
|
} else if args
|
||||||
.first()
|
.first()
|
||||||
.map(|s| s.eq_ignore_ascii_case("modlogs"))
|
.map(|s| s.eq_ignore_ascii_case("modlogs"))
|
||||||
@@ -233,6 +251,7 @@ pub fn all_command_keys() -> Vec<String> {
|
|||||||
"boosters",
|
"boosters",
|
||||||
"rolemembers",
|
"rolemembers",
|
||||||
"rolemenu",
|
"rolemenu",
|
||||||
|
"ancien",
|
||||||
"serverinfo",
|
"serverinfo",
|
||||||
"vocinfo",
|
"vocinfo",
|
||||||
"role",
|
"role",
|
||||||
@@ -252,6 +271,23 @@ pub fn all_command_keys() -> Vec<String> {
|
|||||||
"clear_sanctions",
|
"clear_sanctions",
|
||||||
"clear_all_sanctions",
|
"clear_all_sanctions",
|
||||||
"clear_messages",
|
"clear_messages",
|
||||||
|
"clear_limit",
|
||||||
|
"clear_badwords",
|
||||||
|
"timeout",
|
||||||
|
"muterole",
|
||||||
|
"set_muterole",
|
||||||
|
"antispam",
|
||||||
|
"antiraideautoconfig",
|
||||||
|
"antilink",
|
||||||
|
"antimassmention",
|
||||||
|
"badwords",
|
||||||
|
"spam",
|
||||||
|
"link",
|
||||||
|
"strikes",
|
||||||
|
"punish",
|
||||||
|
"noderank",
|
||||||
|
"public",
|
||||||
|
"resetantiraide",
|
||||||
"warn",
|
"warn",
|
||||||
"mute",
|
"mute",
|
||||||
"tempmute",
|
"tempmute",
|
||||||
@@ -290,6 +326,7 @@ pub fn all_command_keys() -> Vec<String> {
|
|||||||
"ticket_close",
|
"ticket_close",
|
||||||
"tickets",
|
"tickets",
|
||||||
"show_pics",
|
"show_pics",
|
||||||
|
"piconly",
|
||||||
"suggestion_create",
|
"suggestion_create",
|
||||||
"suggestion_settings",
|
"suggestion_settings",
|
||||||
"autopublish",
|
"autopublish",
|
||||||
@@ -374,14 +411,14 @@ pub fn all_command_keys() -> Vec<String> {
|
|||||||
|
|
||||||
pub fn default_permission(command_key: &str) -> u8 {
|
pub fn default_permission(command_key: &str) -> u8 {
|
||||||
match command_key {
|
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,
|
"claim" | "rename" | "ticket_add" | "ticket_remove" | "ticket_close" | "tickets" => 2,
|
||||||
"show_pics" | "suggestion_create" | "tempvoc_cmd" => 0,
|
"show_pics" | "suggestion_create" | "tempvoc_cmd" => 0,
|
||||||
"owner" | "unowner" | "clear_owners" => 9,
|
"owner" | "unowner" | "clear_owners" => 9,
|
||||||
"bl" | "unbl" | "blinfo" | "clear_bl" => 9,
|
"bl" | "unbl" | "blinfo" | "clear_bl" => 9,
|
||||||
"change" | "changeall" | "change_reset" | "mainprefix" | "set_perm" | "del_perm"
|
"change" | "changeall" | "change_reset" | "mainprefix" | "set_perm" | "del_perm"
|
||||||
| "clear_perms" => 9,
|
| "clear_perms" => 9,
|
||||||
"set_modlogs" | "set_boostembed" => 8,
|
"set_modlogs" | "set_boostembed" | "set_muterole" => 8,
|
||||||
"prefix" | "perms" | "allperms" => 8,
|
"prefix" | "perms" | "allperms" => 8,
|
||||||
"help" | "server_list" => 0,
|
"help" | "server_list" => 0,
|
||||||
"helpsetting" | "alias" | "leave" => 9,
|
"helpsetting" | "alias" | "leave" => 9,
|
||||||
@@ -428,6 +465,22 @@ pub fn default_permission(command_key: &str) -> u8 {
|
|||||||
| "clear_sanctions"
|
| "clear_sanctions"
|
||||||
| "clear_all_sanctions"
|
| "clear_all_sanctions"
|
||||||
| "clear_messages"
|
| "clear_messages"
|
||||||
|
| "clear_limit"
|
||||||
|
| "clear_badwords"
|
||||||
|
| "timeout"
|
||||||
|
| "muterole"
|
||||||
|
| "antispam"
|
||||||
|
| "antiraideautoconfig"
|
||||||
|
| "antilink"
|
||||||
|
| "antimassmention"
|
||||||
|
| "badwords"
|
||||||
|
| "spam"
|
||||||
|
| "link"
|
||||||
|
| "strikes"
|
||||||
|
| "punish"
|
||||||
|
| "noderank"
|
||||||
|
| "public"
|
||||||
|
| "resetantiraide"
|
||||||
| "warn"
|
| "warn"
|
||||||
| "mute"
|
| "mute"
|
||||||
| "tempmute"
|
| "tempmute"
|
||||||
@@ -455,6 +508,7 @@ pub fn default_permission(command_key: &str) -> u8 {
|
|||||||
| "delrole"
|
| "delrole"
|
||||||
| "derank"
|
| "derank"
|
||||||
| "rolemenu"
|
| "rolemenu"
|
||||||
|
| "ancien"
|
||||||
| "modlog"
|
| "modlog"
|
||||||
| "messagelog"
|
| "messagelog"
|
||||||
| "voicelog"
|
| "voicelog"
|
||||||
|
|||||||
Reference in New Issue
Block a user