From 65420838fb4b17078d12e28fec5f3b307ce9934f Mon Sep 17 00:00:00 2001 From: Puechberty Arthur Date: Fri, 10 Apr 2026 19:26:31 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20la=20gestion=20des=20canaux=20de?= =?UTF-8?q?=20logs=20et=20am=C3=A9lioration=20de=20l'enregistrement=20des?= =?UTF-8?q?=20logs=20d'audit=20avec=20les=20identifiants=20de=20message.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/config/viewlogs.rs | 150 +++++++++++++++++++++++--------- src/utils/logs_service.rs | 55 +++++++++--- 2 files changed, 152 insertions(+), 53 deletions(-) diff --git a/src/commands/config/viewlogs.rs b/src/commands/config/viewlogs.rs index 210ec62..a38c8d9 100644 --- a/src/commands/config/viewlogs.rs +++ b/src/commands/config/viewlogs.rs @@ -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 { data.get::().cloned() } +async fn fetch_log_channels( + pool: &sqlx::PgPool, + bot_id: UserId, + guild_id: GuildId, +) -> HashMap { + let rows = sqlx::query_as::<_, (String, Option)>( + 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, +) -> Option { + 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, +) -> Option { + 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, + guild_id: GuildId, + channels: &HashMap, +) -> 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: \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!("{}", 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!("{}", 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 { diff --git a/src/utils/logs_service.rs b/src/utils/logs_service.rs index 7414b22..4098631 100644 --- a/src/utils/logs_service.rs +++ b/src/utils/logs_service.rs @@ -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, channel_id: Option, role_id: Option, + message_id: Option, action: &str, + details: Option, ) { 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) -> Option { + 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) {