From 0a7df6f25d40a29627933b1b274c4d30e02da086 Mon Sep 17 00:00:00 2001 From: Haruka Date: Sun, 11 May 2025 20:18:09 +0300 Subject: [PATCH] feat: improve logging --- src/modules/logging.js | 299 +++++++++++++++++++++++++++++++++++++++-- src/util/dconstants.js | 6 + 2 files changed, 297 insertions(+), 8 deletions(-) diff --git a/src/modules/logging.js b/src/modules/logging.js index 0710346..9b09e2d 100644 --- a/src/modules/logging.js +++ b/src/modules/logging.js @@ -4,10 +4,17 @@ const logger = require("#lib/logger.js"); const {getGuildData} = require("#lib/guildData.js"); const events = require("#lib/events.js"); -const {AuditLogActions, APIEndpoints, CDNEndpoints, GuildIntegrationTypes} = require("#util/dconstants.js"); -const {JoinSourceTypeNames} = require("#util/constants.js"); +const { + AuditLogActions, + APIEndpoints, + CDNEndpoints, + GuildIntegrationTypes, + GuildProfileVisibility, +} = require("#util/dconstants.js"); +const {JoinSourceTypeNames, ChannelTypeNames} = require("#util/constants.js"); const {formatUsername, getDefaultAvatar} = require("#util/misc.js"); const {snowflakeToTimestamp} = require("#util/time.js"); +const {GUILD_ICON, BANNER, CDN_URL} = require("@projectdysnomia/dysnomia/lib/rest/Endpoints.js"); const COLOR_ADDED = 0x399d53; const COLOR_REMOVED = 0xe55152; @@ -18,6 +25,13 @@ async function getLoggingChannel(guild) { return guild.channels.get(channelId) ?? guild.threads.get(channelId); } +function formatNameChange(before, after) { + const beforeFormatted = before != null ? before : ""; + const afterFormatted = after != null ? after : ""; + + return `\`${beforeFormatted}\` > \`${afterFormatted}\``; +} + const WHITELISTED_EVENTS = new Set([ "GUILD_UPDATE", "CHANNEL_CREATE", @@ -73,18 +87,17 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { if (!channel) return; try { + const before = entry.before; + const after = entry.after; + switch (entry.actionType) { - case AuditLogActions.GUILD_UPDATE: - case AuditLogActions.CHANNEL_CREATE: case AuditLogActions.CHANNEL_UPDATE: - case AuditLogActions.CHANNEL_DELETE: case AuditLogActions.CHANNEL_OVERWRITE_CREATE: case AuditLogActions.CHANNEL_OVERWRITE_UPDATE: case AuditLogActions.CHANNEL_OVERWRITE_DELETE: case AuditLogActions.MEMBER_KICK: case AuditLogActions.MEMBER_BAN_ADD: case AuditLogActions.MEMBER_BAN_REMOVE: - case AuditLogActions.MEMBER_UPDATE: case AuditLogActions.BOT_ADD: case AuditLogActions.ROLE_CREATE: case AuditLogActions.ROLE_UPDATE: @@ -114,8 +127,7 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { case AuditLogActions.CLYDE_AI_PROFILE_UPDATE: case AuditLogActions.GUILD_SCHEDULED_EVENT_EXCEPTION_CREATE: case AuditLogActions.GUILD_SCHEDULED_EVENT_EXCEPTION_UPDATE: - case AuditLogActions.GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE: - case AuditLogActions.GUILD_PROFILE_UPDATE: { + case AuditLogActions.GUILD_SCHEDULED_EVENT_EXCEPTION_DELETE: { channel .createMessage({ content: Object.entries(AuditLogActions).find(([name, val]) => val === entry.actionType)[0], @@ -129,6 +141,277 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { .catch(() => {}); break; } + case AuditLogActions.CHANNEL_DELETE: + case AuditLogActions.CHANNEL_CREATE: { + const fields = []; + const hasCreated = entry.actionType == AuditLogActions.CHANNEL_CREATE; + + for (const [key, value] of Object.entries(hasCreated ? after : before)) { + switch (key) { + case "name": { + fields.push({ + name: "Name", + value: `#${value}`, + inline: true, + }); + break; + } + case "type": { + const typeName = ChannelTypeNames[value]; + fields.push({ + name: "Type", + value: typeName, + inline: true, + }); + break; + } + case "nsfw": { + fields.push({ + name: "NSFW", + value: value == true ? "Yes" : "No", + inline: true, + }); + break; + } + } + } + + channel.createMessage({ + embeds: [ + { + color: hasCreated ? COLOR_ADDED : COLOR_REMOVED, + title: `Channel ${hasCreated ? "created" : "deleted"}`, + description: `<@${entry.user.id}> (${formatUsername(entry.user)}) ${ + hasCreated ? `created channel <#${entry.targetID}>` : `deleted channel` + }`, + fields, + footer: { + text: `Channel ID: ${entry.targetID}`, + }, + timestamp: new Date().toISOString(), + }, + ], + }); + + break; + } + case AuditLogActions.GUILD_UPDATE: { + const fields = []; + + let thumbnail; + let image; + + for (const [key, newValue] of Object.entries(after)) { + const oldValue = before[key]; + + switch (key) { + case "name": + fields.push({ + name: "Name", + value: formatNameChange(oldValue, newValue), + inline: true, + }); + break; + case "description": + fields.push({ + name: "Description", + value: formatNameChange(oldValue, newValue), + inline: true, + }); + break; + case "icon_hash": { + const oldIconURL = new URL(GUILD_ICON(entry.targetID, oldValue), CDN_URL); + const newIconURL = new URL(GUILD_ICON(entry.targetID, newValue), CDN_URL); + + const oldIconFormatted = `[Old](${oldIconURL})`; + const newIconFormatted = `[New](${newIconURL})`; + + fields.push({ + name: "Icon", + value: `${oldIconFormatted} > ${newIconFormatted}`, + inline: true, + }); + + thumbnail = newIconURL; + break; + } + case "banner_hash": { + const oldBannerURL = new URL(BANNER(entry.targetID, oldValue), CDN_URL); + const newBannerURL = new URL(BANNER(entry.targetID, newValue), CDN_URL); + + const oldBannerFormatted = `[Old](${oldBannerURL})`; + const newBannerFormated = `[Old](${newBannerURL})`; + + fields.push({ + name: "Banner", + value: `${oldBannerFormatted} > ${newBannerFormated}`, + inline: true, + }); + + image = newBannerFormated; + break; + } + case "system_channel_id": { + // system_channel_id never has a before value, afaik? + // this might be wrong + + fields.push({ + name: "System Channel", + value: `${newValue}`, + }); + break; + } + case "widget_enabled": { + fields.push({ + name: "Widget Enabled", + value: newValue == true ? "Yes" : "No", + }); + break; + } + case "max_members": { + fields.push({ + name: "Max Members", + value: newValue, + }); + break; + } + default: { + // 🙏 + fields.push({ + name: key, + value: `${oldValue} > ${newValue}`, + inline: true, + }); + break; + } + } + } + + fields.push({ + name: "Updated by", + value: `<@${entry.user.id}>`, + inline: false, + }); + + channel.createMessage({ + embeds: [ + { + color: COLOR_CHANGED, + title: `Server updated`, + fields, + thumbnail: + thumbnail != undefined + ? { + url: thumbnail, + } + : null, + image: + image != undefined + ? { + url: image, + } + : null, + footer: { + text: `Guild ID: ${entry.targetID}`, + }, + timestamp: new Date().toISOString(), + }, + ], + }); + + break; + } + case AuditLogActions.GUILD_PROFILE_UPDATE: { + const fields = []; + + for (const [key, newValue] of Object.entries(after)) { + const oldValue = before[key]; + switch (key) { + case "visibility": { + const newVisibility = GuildProfileVisibility[newValue] + ? GuildProfileVisibility[newValue] + : `Unknown ${newValue}`; + const oldVisibility = GuildProfileVisibility[oldValue] + ? GuildProfileVisibility[oldValue] + : `Unknown ${oldValue}`; + + fields.push({ + name: "Visibility", + value: formatNameChange(oldVisibility, newVisibility), + }); + } + } + } + + fields.push({ + name: "Updated by", + value: `<@${entry.user.id}>`, + inline: false, + }); + + channel.createMessage({ + embeds: [ + { + color: COLOR_CHANGED, + title: `Server Profile updated`, + fields, + footer: { + text: `Guild ID: ${entry.targetID}`, + }, + timestamp: new Date().toISOString(), + }, + ], + }); + + break; + } + case AuditLogActions.MEMBER_UPDATE: { + const fields = []; + + const bot = hf.bot; + const target = bot.users.get(entry.targetID); + + const oldNickname = before?.nick; + const newNickname = after?.nick; + + const isTimedOut = after?.communication_disabled_until != undefined; + + if (oldNickname != newNickname) { + fields.push({ + name: "Nickname", + value: formatNameChange(oldNickname, newNickname), + inline: true, + }); + } + + const timeoutDuration = new Date( + isTimedOut ? after?.communication_disabled_until : before?.communication_disabled_until + ); + + fields.push({ + name: isTimedOut ? "Timed out" : "Was timed out until", + value: ``, + inline: true, + }); + + channel.createMessage({ + embeds: [ + { + color: COLOR_CHANGED, + title: `Member updated`, + description: `<@${entry.user.id}> (${formatUsername(entry.user)}) updated member <@${ + entry.targetID + }> (${formatUsername(target)})`, + fields, + footer: { + text: `User ID: ${entry.targetID}`, + }, + timestamp: new Date().toISOString(), + }, + ], + }); + break; + } case AuditLogActions.MEMBER_ROLE_UPDATE: { const isAdd = entry.after.$add != null; const isSelf = entry.user.id === entry.targetID; diff --git a/src/util/dconstants.js b/src/util/dconstants.js index c028052..c4fbd26 100644 --- a/src/util/dconstants.js +++ b/src/util/dconstants.js @@ -207,3 +207,9 @@ module.exports.VerificationLevelStrings = [ "(╯°□°)╯︵ ┻━┻ (High)", "┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻ (Very High/Phone)", ]; + +module.exports.GuildProfileVisibility = { + 1: "Public", + 2: "Restricted", + 3: "Public (application-only)", +};