Ajout de la gestion des canaux de logs et amélioration de l'enregistrement des logs d'audit avec les identifiants de message.

This commit is contained in:
Puechberty Arthur
2026-04-10 19:26:31 +02:00
parent 6a22ea0631
commit 65420838fb
2 changed files with 152 additions and 53 deletions
+107 -43
View File
@@ -1,3 +1,5 @@
use std::collections::HashMap;
use serenity::builder::{CreateActionRow, CreateButton, CreateEmbed, CreateMessage};
use serenity::model::application::ButtonStyle;
use serenity::model::prelude::*;
@@ -17,6 +19,101 @@ pub async fn pool(ctx: &Context) -> Option<sqlx::PgPool> {
data.get::<DbPoolKey>().cloned()
}
async fn fetch_log_channels(
pool: &sqlx::PgPool,
bot_id: UserId,
guild_id: GuildId,
) -> HashMap<String, u64> {
let rows = sqlx::query_as::<_, (String, Option<i64>)>(
r#"
SELECT log_type, channel_id
FROM bot_log_channels
WHERE bot_id = $1 AND guild_id = $2 AND enabled = TRUE;
"#,
)
.bind(bot_id.get() as i64)
.bind(guild_id.get() as i64)
.fetch_all(pool)
.await
.unwrap_or_default();
rows.into_iter()
.filter_map(|(log_type, channel_id)| {
channel_id
.and_then(|id| u64::try_from(id).ok())
.map(|id| (log_type, id))
})
.collect()
}
fn extract_log_channel_id(
log: &crate::db::AuditLog,
channels: &HashMap<String, u64>,
) -> Option<u64> {
let from_details = log
.details
.as_ref()
.and_then(|details| details.get("log_channel_id"))
.and_then(|value| value.as_i64())
.and_then(|id| u64::try_from(id).ok());
from_details.or_else(|| channels.get(&log.log_type).copied())
}
fn build_log_link(
log: &crate::db::AuditLog,
guild_id: GuildId,
channels: &HashMap<String, u64>,
) -> Option<String> {
let message_id = log.message_id.and_then(|id| u64::try_from(id).ok())?;
let log_channel_id = extract_log_channel_id(log, channels)?;
Some(format!(
"https://discord.com/channels/{}/{}/{}",
guild_id.get(),
log_channel_id,
message_id
))
}
fn append_log_fields(
mut embed: CreateEmbed,
logs: Vec<crate::db::AuditLog>,
guild_id: GuildId,
channels: &HashMap<String, u64>,
) -> CreateEmbed {
for log in logs {
let actor = log
.user_id
.map(|id| format!("<@{}>", id))
.unwrap_or_else(|| "Systeme".to_string());
let target = match (log.channel_id, log.role_id) {
(Some(ch_id), _) => format!("<#{}>", ch_id),
(_, Some(role_id)) => format!("<@&{}>", role_id),
_ => "-".to_string(),
};
let link = build_log_link(&log, guild_id, channels)
.map(|url| format!("[Voir]({})", url))
.unwrap_or_else(|| "indisponible".to_string());
embed = embed.field(
format!("[{}] {}", log.log_type.to_uppercase(), log.action),
format!(
"Acteur: {}\nQuand: <t:{}:R>\nCible: {}\nLien: {}",
actor,
log.created_at.timestamp(),
target,
link
),
false,
);
}
embed
}
pub async fn handle_viewlogs(ctx: &Context, msg: &Message, args: &[&str]) {
let Some(guild_id) = msg.guild_id else {
send_embed(
@@ -75,32 +172,14 @@ pub async fn handle_viewlogs(ctx: &Context, msg: &Message, args: &[&str]) {
.await
.unwrap_or_default();
let mut embed = CreateEmbed::new()
let log_channels = fetch_log_channels(&pool, bot_id, guild_id).await;
let embed = CreateEmbed::new()
.title("Logs d'audit")
.description(format!(
"Page {}/{} ({} logs total)",
page, total_pages, total
))
.description(format!("Page {}/{}{} logs", page, total_pages, total))
.color(theme_color(ctx).await);
for log in logs {
let user_mention = log
.user_id
.map(|id| format!("<@{}>", id))
.unwrap_or_else(|| "Système".to_string());
let extra = match (log.channel_id, log.role_id) {
(Some(ch_id), _) => format!(" · <#{}>", ch_id),
(_, Some(role_id)) => format!(" · <@&{}>", role_id),
_ => String::new(),
};
embed = embed.field(
format!("[{}] {} {}", &log.log_type, user_mention, log.action),
format!("<t:{}:R>{}", log.created_at.timestamp(), extra),
false,
);
}
let embed = append_log_fields(embed, logs, guild_id, &log_channels);
let mut components = Vec::new();
@@ -187,32 +266,17 @@ pub async fn handle_viewlogs_button(ctx: &Context, component: &ComponentInteract
.await
.unwrap_or_default();
let mut embed = CreateEmbed::new()
let log_channels = fetch_log_channels(&pool, bot_id, guild_id).await;
let embed = CreateEmbed::new()
.title("Logs d'audit")
.description(format!(
"Page {}/{} ({} logs total)",
"Page {}/{} {} logs",
new_page, total_pages, total
))
.color(theme_color(ctx).await);
for log in logs {
let user_mention = log
.user_id
.map(|id| format!("<@{}>", id))
.unwrap_or_else(|| "Système".to_string());
let extra = match (log.channel_id, log.role_id) {
(Some(ch_id), _) => format!(" · <#{}>", ch_id),
(_, Some(role_id)) => format!(" · <@&{}>", role_id),
_ => String::new(),
};
embed = embed.field(
format!("[{}] {} {}", &log.log_type, user_mention, log.action),
format!("<t:{}:R>{}", log.created_at.timestamp(), extra),
false,
);
}
let embed = append_log_fields(embed, logs, guild_id, &log_channels);
let mut components = Vec::new();
if total_pages > 1 {
+45 -10
View File
@@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use chrono::Utc;
use serde_json::json;
use serenity::builder::{CreateEmbed, CreateEmbedFooter};
use serenity::model::prelude::*;
use serenity::prelude::*;
@@ -85,7 +86,9 @@ async fn record_audit_log(
user_id: Option<UserId>,
channel_id: Option<ChannelId>,
role_id: Option<RoleId>,
message_id: Option<MessageId>,
action: &str,
details: Option<sqlx::types::JsonValue>,
) {
let Some(pool) = pool(ctx).await else {
return;
@@ -93,11 +96,16 @@ async fn record_audit_log(
let bot_id = ctx.cache.current_user().id;
let _ = crate::db::insert_audit_log(
&pool, bot_id, guild_id, log_type, user_id, channel_id, role_id, None, action, None,
&pool, bot_id, guild_id, log_type, user_id, channel_id, role_id, message_id, action,
details,
)
.await;
}
fn build_audit_details(log_channel_id: Option<ChannelId>) -> Option<sqlx::types::JsonValue> {
log_channel_id.map(|id| json!({ "log_channel_id": id.get() as i64 }))
}
fn color_for_log_type(log_type: &str) -> u32 {
match log_type {
"message" => 0xF4C430,
@@ -199,9 +207,8 @@ fn enrich_log_embed(
}
pub async fn send_log_embed(ctx: &Context, guild_id: GuildId, log_type: &str, embed: CreateEmbed) {
record_audit_log(ctx, guild_id, log_type, None, None, None, log_type).await;
let log_channel_id = get_log_channel(ctx, guild_id, log_type).await;
let mut sent_log_message_id = None;
if let Some(channel_id) = log_channel_id {
let embed = enrich_log_embed(
@@ -216,13 +223,28 @@ pub async fn send_log_embed(ctx: &Context, guild_id: GuildId, log_type: &str, em
embed,
);
let _ = channel_id
let sent = channel_id
.send_message(
&ctx.http,
serenity::builder::CreateMessage::new().embed(embed),
)
.await;
sent_log_message_id = sent.ok().map(|message| message.id);
}
record_audit_log(
ctx,
guild_id,
log_type,
None,
None,
None,
sent_log_message_id,
log_type,
build_audit_details(log_channel_id),
)
.await;
}
pub async fn emit_log(
@@ -235,12 +257,10 @@ pub async fn emit_log(
action: &str,
embed: CreateEmbed,
) {
record_audit_log(
ctx, guild_id, log_type, user_id, channel_id, role_id, action,
)
.await;
let log_channel_id = get_log_channel(ctx, guild_id, log_type).await;
let mut sent_log_message_id = None;
if let Some(log_channel_id) = get_log_channel(ctx, guild_id, log_type).await {
if let Some(log_channel_id) = log_channel_id {
let embed = enrich_log_embed(
ctx,
guild_id,
@@ -253,13 +273,28 @@ pub async fn emit_log(
embed,
);
let _ = log_channel_id
let sent = log_channel_id
.send_message(
&ctx.http,
serenity::builder::CreateMessage::new().embed(embed),
)
.await;
sent_log_message_id = sent.ok().map(|message| message.id);
}
record_audit_log(
ctx,
guild_id,
log_type,
user_id,
channel_id,
role_id,
sent_log_message_id,
action,
build_audit_details(log_channel_id),
)
.await;
}
pub async fn on_member_join(ctx: &Context, guild_id: GuildId, user: &User) {