Refactor profile commands to improve status handling and embed responses

- Updated `handle_idle`, `handle_invisible`, `handle_online`, `handle_listen`, `handle_playto`, `handle_stream`, `handle_watch`, and `handle_remove_activity` functions to use a unified approach for setting bot status and sending embed messages.
- Removed dependency on `botconfig_common` and replaced it with direct database interactions for status management.
- Added new helper functions for logging and moderation channel management.
- Introduced permission handling improvements in `set` command with better error messages and user feedback.
- Created new utility functions for parsing user and role IDs, ensuring better command access control.
This commit is contained in:
Puechberty Arthur
2026-04-10 06:42:46 +02:00
parent bc623a7736
commit e1016e0af1
146 changed files with 5434 additions and 4237 deletions
+1 -575
View File
@@ -1,63 +1,17 @@
use std::collections::BTreeSet;
use chrono::Utc;
use serenity::builder::{CreateChannel, CreateEmbed};
use serenity::builder::CreateEmbed;
use serenity::model::prelude::*;
use serenity::prelude::*;
use crate::commands::common::{parse_channel_id, send_embed, theme_color};
use crate::db::DbPoolKey;
const LOG_TYPES: &[(&str, &str)] = &[
("moderation", "modlog"),
("message", "messagelog"),
("voice", "voicelog"),
("boost", "boostlog"),
("role", "rolelog"),
("raid", "raidlog"),
("channel", "channellog"),
];
async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
let data = ctx.data.read().await;
data.get::<DbPoolKey>().cloned()
}
fn parse_target_channel(msg: &Message, args: &[&str], idx: usize) -> Option<ChannelId> {
args.get(idx)
.and_then(|raw| parse_channel_id(raw))
.or(Some(msg.channel_id))
}
async fn set_log_channel(
ctx: &Context,
guild_id: GuildId,
log_type: &str,
channel_id: Option<ChannelId>,
enabled: bool,
) {
let Some(pool) = pool(ctx).await else {
return;
};
let bot_id = ctx.cache.current_user().id;
let _ = sqlx::query(
r#"
INSERT INTO bot_log_channels (bot_id, guild_id, log_type, channel_id, enabled)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (bot_id, guild_id, log_type)
DO UPDATE SET channel_id = EXCLUDED.channel_id, enabled = EXCLUDED.enabled, updated_at = NOW();
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(log_type)
.bind(channel_id.map(|c| c.get() as i64))
.bind(enabled)
.execute(&pool)
.await;
}
async fn get_log_channel(ctx: &Context, guild_id: GuildId, log_type: &str) -> Option<ChannelId> {
let pool = pool(ctx).await?;
let bot_id = ctx.cache.current_user().id;
@@ -160,348 +114,6 @@ pub async fn emit_log(
send_log_embed(ctx, guild_id, log_type, embed).await;
}
pub async fn handle_log_toggle(
ctx: &Context,
msg: &Message,
args: &[&str],
log_type: &str,
label: &str,
) {
let Some(guild_id) = msg.guild_id else {
return;
};
let Some(action) = args.first().map(|s| s.to_lowercase()) else {
let embed = CreateEmbed::new()
.title(label)
.description(format!("Usage: +{} <on [salon]|off>", label.to_lowercase()))
.color(0xED4245);
send_embed(ctx, msg, embed).await;
return;
};
match action.as_str() {
"on" => {
let channel = parse_target_channel(msg, args, 1);
set_log_channel(ctx, guild_id, log_type, channel, true).await;
let embed = CreateEmbed::new()
.title(label)
.description(format!(
"Activé dans {}.",
channel
.map(|c| format!("<#{}>", c.get()))
.unwrap_or_else(|| "ce salon".to_string())
))
.color(theme_color(ctx).await);
send_embed(ctx, msg, embed).await;
}
"off" => {
set_log_channel(ctx, guild_id, log_type, None, false).await;
let embed = CreateEmbed::new()
.title(label)
.description("Désactivé.")
.color(theme_color(ctx).await);
send_embed(ctx, msg, embed).await;
}
_ => {
let embed = CreateEmbed::new()
.title(label)
.description(format!("Usage: +{} <on [salon]|off>", label.to_lowercase()))
.color(0xED4245);
send_embed(ctx, msg, embed).await;
}
}
}
pub async fn handle_raidlog(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args
.first()
.map(|a| a.eq_ignore_ascii_case("off"))
.unwrap_or(false)
{
set_log_channel(ctx, guild_id, "raid", None, false).await;
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("RaidLog")
.description("Désactivé.")
.color(theme_color(ctx).await),
)
.await;
return;
}
let channel = parse_target_channel(msg, args, 0);
set_log_channel(ctx, guild_id, "raid", channel, true).await;
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("RaidLog")
.description(format!(
"Activé dans {}.",
channel
.map(|c| format!("<#{}>", c.get()))
.unwrap_or_else(|| "ce salon".to_string())
))
.color(theme_color(ctx).await),
)
.await;
}
pub async fn handle_autoconfiglog(ctx: &Context, msg: &Message) {
let Some(guild_id) = msg.guild_id else {
return;
};
let mut created = Vec::new();
for (log_type, cmd) in LOG_TYPES {
let name = format!("{}-logs", cmd.replace("log", ""));
if let Ok(channel) = guild_id
.create_channel(&ctx.http, CreateChannel::new(name).kind(ChannelType::Text))
.await
{
set_log_channel(ctx, guild_id, log_type, Some(channel.id), true).await;
created.push(format!("{} -> <#{}>", log_type, channel.id.get()));
}
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("AutoConfigLog")
.description(if created.is_empty() {
"Aucun salon créé.".to_string()
} else {
created.join("\n")
})
.color(theme_color(ctx).await),
)
.await;
}
pub async fn handle_set_modlogs(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;
let row = sqlx::query_as::<_, (String,)>(
r#"
SELECT modlog_events
FROM bot_log_settings
WHERE bot_id = $1 AND guild_id = $2
LIMIT 1;
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.fetch_optional(&pool)
.await
.ok()
.flatten();
let mut events = row
.map(|(s,)| {
s.split(',')
.map(|v| v.trim().to_lowercase())
.filter(|v| !v.is_empty())
.collect::<BTreeSet<_>>()
})
.unwrap_or_else(|| {
[
"warn",
"mute",
"tempmute",
"unmute",
"cmute",
"tempcmute",
"uncmute",
"kick",
"ban",
"tempban",
"unban",
"lock",
"unlock",
"hide",
"unhide",
"addrole",
"delrole",
"derank",
"clear",
"sanctions",
]
.into_iter()
.map(|s| s.to_string())
.collect()
});
if args.len() >= 2 {
let event = args[0].to_lowercase();
let state = args[1].to_lowercase();
if state == "on" {
events.insert(event);
} else if state == "off" {
events.remove(&event);
}
let serialized = events.iter().cloned().collect::<Vec<_>>().join(",");
let _ = sqlx::query(
r#"
INSERT INTO bot_log_settings (bot_id, guild_id, modlog_events)
VALUES ($1, $2, $3)
ON CONFLICT (bot_id, guild_id)
DO UPDATE SET modlog_events = EXCLUDED.modlog_events, updated_at = NOW();
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(serialized)
.execute(&pool)
.await;
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Set ModLogs")
.description(format!(
"Événements actifs:\n{}\n\nUsage: +set modlogs <event> <on/off>",
events.iter().cloned().collect::<Vec<_>>().join(", ")
))
.color(theme_color(ctx).await),
)
.await;
}
pub async fn handle_join_leave_settings(ctx: &Context, msg: &Message, args: &[&str], kind: &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;
if args.is_empty() || !args[0].eq_ignore_ascii_case("settings") {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title(format!("{} settings", kind))
.description(format!(
"Usage: +{} settings [on/off] [salon] [message...]",
kind
))
.color(0xED4245),
)
.await;
return;
}
if args.len() == 1 {
let row = sqlx::query_as::<_, (bool, Option<i64>, Option<String>)>(
r#"
SELECT enabled, channel_id, custom_message
FROM bot_join_leave_settings
WHERE bot_id = $1 AND guild_id = $2 AND kind = $3
LIMIT 1;
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(kind)
.fetch_optional(&pool)
.await
.ok()
.flatten();
let desc = if let Some((enabled, channel_id, custom_message)) = row {
format!(
"État: {}\nSalon: {}\nMessage: {}",
if enabled { "on" } else { "off" },
channel_id
.map(|id| format!("<#{}>", id))
.unwrap_or_else(|| "non défini".to_string()),
custom_message.unwrap_or_else(|| "(défaut)".to_string())
)
} else {
"Aucun réglage configuré.".to_string()
};
send_embed(
ctx,
msg,
CreateEmbed::new()
.title(format!("{} settings", kind))
.description(desc)
.color(theme_color(ctx).await),
)
.await;
return;
}
let action = args[1].to_lowercase();
let enabled = action == "on";
let channel = if enabled {
parse_target_channel(msg, args, 2)
} else {
None
};
let message_start = if enabled { 3 } else { 2 };
let custom_message = if args.len() > message_start {
Some(args[message_start..].join(" "))
} else {
None
};
let _ = sqlx::query(
r#"
INSERT INTO bot_join_leave_settings (bot_id, guild_id, kind, enabled, channel_id, custom_message)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (bot_id, guild_id, kind)
DO UPDATE SET enabled = EXCLUDED.enabled, channel_id = EXCLUDED.channel_id,
custom_message = EXCLUDED.custom_message, updated_at = NOW();
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(kind)
.bind(enabled)
.bind(channel.map(|c| c.get() as i64))
.bind(custom_message)
.execute(&pool)
.await;
send_embed(
ctx,
msg,
CreateEmbed::new()
.title(format!("{} settings", kind))
.description(format!(
"{} {}",
if enabled { "Activé" } else { "Désactivé" },
channel
.map(|c| format!("dans <#{}>", c.get()))
.unwrap_or_default()
))
.color(theme_color(ctx).await),
)
.await;
}
pub async fn on_member_join(ctx: &Context, guild_id: GuildId, user: &User) {
if let Some(channel_id) = get_log_channel(ctx, guild_id, "raid").await {
let _ = channel_id
@@ -569,94 +181,6 @@ async fn run_join_leave_action(ctx: &Context, guild_id: GuildId, kind: &str, use
let _ = channel_id.say(&ctx.http, content).await;
}
pub async fn handle_set_boostembed(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args.len() < 2 {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Set BoostEmbed")
.description("Usage: +set boostembed <title|description|color> <valeur>")
.color(0xED4245),
)
.await;
return;
}
let field = args[0].to_lowercase();
let value = args[1..].join(" ");
let Some(pool) = pool(ctx).await else {
return;
};
let bot_id = ctx.cache.current_user().id;
let _ = sqlx::query(
r#"
INSERT INTO bot_boost_embed (bot_id, guild_id, enabled, title, description, color)
VALUES ($1, $2, TRUE, NULL, NULL, NULL)
ON CONFLICT (bot_id, guild_id)
DO NOTHING;
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.execute(&pool)
.await;
match field.as_str() {
"title" => {
let _ = sqlx::query(
"UPDATE bot_boost_embed SET title = $3, updated_at = NOW() WHERE bot_id = $1 AND guild_id = $2",
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(value)
.execute(&pool)
.await;
}
"description" => {
let _ = sqlx::query(
"UPDATE bot_boost_embed SET description = $3, updated_at = NOW() WHERE bot_id = $1 AND guild_id = $2",
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(value)
.execute(&pool)
.await;
}
"color" => {
let normalized = value
.trim()
.trim_start_matches('#')
.trim_start_matches("0x");
if let Ok(color) = u32::from_str_radix(normalized, 16) {
let _ = sqlx::query(
"UPDATE bot_boost_embed SET color = $3, updated_at = NOW() WHERE bot_id = $1 AND guild_id = $2",
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(color as i32)
.execute(&pool)
.await;
}
}
_ => {}
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("Set BoostEmbed")
.description("Configuration mise à jour.")
.color(theme_color(ctx).await),
)
.await;
}
pub async fn send_boost_embed(ctx: &Context, guild_id: GuildId, user: &User) {
let Some(pool) = pool(ctx).await else {
return;
@@ -705,104 +229,6 @@ pub async fn send_boost_embed(ctx: &Context, guild_id: GuildId, user: &User) {
send_log_embed(ctx, guild_id, "boost", embed).await;
}
pub async fn handle_nolog(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
return;
};
if args.is_empty() {
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("NoLog")
.description("Usage: +nolog <add/del> [salon] [message|voice|all]")
.color(0xED4245),
)
.await;
return;
}
let action = args[0].to_lowercase();
let channel = parse_target_channel(msg, args, 1).unwrap_or(msg.channel_id);
let scope = args
.get(2)
.map(|s| s.to_lowercase())
.unwrap_or_else(|| "all".to_string());
let set_message = scope == "all" || scope == "message";
let set_voice = scope == "all" || scope == "voice";
let Some(pool) = pool(ctx).await else {
return;
};
let bot_id = ctx.cache.current_user().id;
if action == "add" {
let _ = sqlx::query(
r#"
INSERT INTO bot_nolog_channels (bot_id, guild_id, channel_id, disable_message, disable_voice)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (bot_id, guild_id, channel_id)
DO UPDATE SET disable_message = bot_nolog_channels.disable_message OR EXCLUDED.disable_message,
disable_voice = bot_nolog_channels.disable_voice OR EXCLUDED.disable_voice,
updated_at = NOW();
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(channel.get() as i64)
.bind(set_message)
.bind(set_voice)
.execute(&pool)
.await;
} else if action == "del" {
let _ = sqlx::query(
r#"
UPDATE bot_nolog_channels
SET disable_message = CASE WHEN $4 THEN FALSE ELSE disable_message END,
disable_voice = CASE WHEN $5 THEN FALSE ELSE disable_voice END,
updated_at = NOW()
WHERE bot_id = $1 AND guild_id = $2 AND channel_id = $3;
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(channel.get() as i64)
.bind(set_message)
.bind(set_voice)
.execute(&pool)
.await;
let _ = sqlx::query(
r#"
DELETE FROM bot_nolog_channels
WHERE bot_id = $1 AND guild_id = $2 AND channel_id = $3
AND disable_message = FALSE AND disable_voice = FALSE;
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.bind(channel.get() as i64)
.execute(&pool)
.await;
}
send_embed(
ctx,
msg,
CreateEmbed::new()
.title("NoLog")
.description(format!(
"{} appliqué sur <#{}> ({})",
action,
channel.get(),
scope
))
.color(theme_color(ctx).await),
)
.await;
}
pub async fn on_message_deleted(
ctx: &Context,
guild_id: Option<GuildId>,