From 0a7df6f25d40a29627933b1b274c4d30e02da086 Mon Sep 17 00:00:00 2001 From: Haruka Date: Sun, 11 May 2025 20:18:09 +0300 Subject: [PATCH 1/2] 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)", +}; From b1b7d2d09de5d538423db3eae8f0c9e82d9ae2e1 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 11 May 2025 11:32:15 -0600 Subject: [PATCH 2/2] logging: cleanup --- src/modules/logging.js | 61 +++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/modules/logging.js b/src/modules/logging.js index 9b09e2d..0c3d361 100644 --- a/src/modules/logging.js +++ b/src/modules/logging.js @@ -14,7 +14,6 @@ const { 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; @@ -29,7 +28,7 @@ function formatNameChange(before, after) { const beforeFormatted = before != null ? before : ""; const afterFormatted = after != null ? after : ""; - return `\`${beforeFormatted}\` > \`${afterFormatted}\``; + return `\`${beforeFormatted}\` -> \`${afterFormatted}\``; } const WHITELISTED_EVENTS = new Set([ @@ -144,9 +143,9 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { case AuditLogActions.CHANNEL_DELETE: case AuditLogActions.CHANNEL_CREATE: { const fields = []; - const hasCreated = entry.actionType == AuditLogActions.CHANNEL_CREATE; + const isCreate = entry.actionType == AuditLogActions.CHANNEL_CREATE; - for (const [key, value] of Object.entries(hasCreated ? after : before)) { + for (const [key, value] of Object.entries(isCreate ? after : before)) { switch (key) { case "name": { fields.push({ @@ -176,14 +175,20 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { } } + if (entry.reason != null) + fields.push({ + name: "Reason", + value: `\`${entry.reason.replaceAll("`", "\u02cb")}\``, + }); + 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` - }`, + color: isCreate ? COLOR_ADDED : COLOR_REMOVED, + title: `Channel ${isCreate ? "Created" : "Deleted"}`, + description: `<@${entry.user.id}> (${formatUsername(entry.user)}) ${isCreate ? "created" : "deleted"} <#${ + entry.targetID + }>${entry.target != null ? ` (${entry.target.name})` : ""}`, fields, footer: { text: `Channel ID: ${entry.targetID}`, @@ -220,15 +225,15 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { }); 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 oldIconURL = CDNEndpoints.GUILD_ICON(entry.targetID, oldValue); + const newIconURL = CDNEndpoints.GUILD_ICON(entry.targetID, newValue); const oldIconFormatted = `[Old](${oldIconURL})`; const newIconFormatted = `[New](${newIconURL})`; fields.push({ name: "Icon", - value: `${oldIconFormatted} > ${newIconFormatted}`, + value: `${oldIconFormatted} -> ${newIconFormatted}`, inline: true, }); @@ -236,15 +241,15 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { 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 oldBannerURL = CDNEndpoints.BANNER(entry.targetID, oldValue); + const newBannerURL = CDNEndpoints.BANNER(entry.targetID, newValue); const oldBannerFormatted = `[Old](${oldBannerURL})`; - const newBannerFormated = `[Old](${newBannerURL})`; + const newBannerFormated = `[New](${newBannerURL})`; fields.push({ name: "Banner", - value: `${oldBannerFormatted} > ${newBannerFormated}`, + value: `${oldBannerFormatted} -> ${newBannerFormated}`, inline: true, }); @@ -252,19 +257,18 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { 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}`, + value: `${oldValue ?? ""} -> ${newValue}`, + inline: true, }); break; } case "widget_enabled": { fields.push({ - name: "Widget Enabled", - value: newValue == true ? "Yes" : "No", + name: "Widget", + value: newValue == true ? "Enabled" : "Disabled", + inline: true, }); break; } @@ -272,14 +276,14 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { fields.push({ name: "Max Members", value: newValue, + inline: true, }); break; } default: { - // 🙏 fields.push({ name: key, - value: `${oldValue} > ${newValue}`, + value: `\`${oldValue}\` -> \`${newValue}\``, inline: true, }); break; @@ -338,6 +342,7 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { fields.push({ name: "Visibility", value: formatNameChange(oldVisibility, newVisibility), + inline: true, }); } } @@ -346,9 +351,15 @@ events.add("guildAuditLogEntryCreate", "logging", async function (entry) { fields.push({ name: "Updated by", value: `<@${entry.user.id}>`, - inline: false, + inline: true, }); + if (entry.reason != null) + fields.push({ + name: "Reason", + value: `\`${entry.reason.replaceAll("`", "\u02cb")}\``, + }); + channel.createMessage({ embeds: [ {