From 7a89911fa56ab2442929984fe7d44dd27d1df7bb Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:32:17 +0530 Subject: [PATCH 01/11] feat: advanced logging, first commit --- config.js | 14 +- src/commands/admin/logging.js | 175 +++++++++++++++++++++++++ src/database/schemas/Guild.js | 11 +- src/events/member/guildMemberUpdate.js | 66 ++++++++++ src/events/message/messageDelete.js | 102 +++++++++----- src/events/message/messageUpdate.js | 45 +++++++ 6 files changed, 373 insertions(+), 40 deletions(-) create mode 100644 src/commands/admin/logging.js create mode 100644 src/events/member/guildMemberUpdate.js create mode 100644 src/events/message/messageUpdate.js diff --git a/config.js b/config.js index 33db48fed..ea69a13a1 100644 --- a/config.js +++ b/config.js @@ -6,9 +6,9 @@ module.exports = { DEFAULT_PREFIX: "!", // Default prefix for the bot }, INTERACTIONS: { - SLASH: false, // Should the interactions be enabled - CONTEXT: false, // Should contexts be enabled - GLOBAL: false, // Should the interactions be registered globally + SLASH: true, // Should the interactions be enabled + CONTEXT: true, // Should contexts be enabled + GLOBAL: true, // Should the interactions be registered globally TEST_GUILD_ID: "xxxxxxxxxxx", // Guild ID where the interactions should be registered. [** Test you commands here first **] }, EMBED_COLORS: { @@ -30,7 +30,7 @@ module.exports = { // PLUGINS AUTOMOD: { - ENABLED: false, + ENABLED: true, LOG_EMBED: "#36393F", DM_EMBED: "#36393F", }, @@ -43,7 +43,7 @@ module.exports = { }, ECONOMY: { - ENABLED: false, + ENABLED: true, CURRENCY: "₪", DAILY_COINS: 100, // coins to be received by daily command MIN_BEG_AMOUNT: 100, // minimum coins to be received when beg command is used @@ -81,11 +81,11 @@ module.exports = { }, INVITE: { - ENABLED: false, + ENABLED: true, }, MODERATION: { - ENABLED: false, + ENABLED: true, EMBED_COLORS: { TIMEOUT: "#102027", UNTIMEOUT: "#4B636E", diff --git a/src/commands/admin/logging.js b/src/commands/admin/logging.js new file mode 100644 index 000000000..811fcd1ff --- /dev/null +++ b/src/commands/admin/logging.js @@ -0,0 +1,175 @@ +const { ApplicationCommandOptionType, ChannelType } = require("discord.js"); + +/** + * @type {import("@structures/Command")} + */ +module.exports = { + name: "logging", + description: "enable or disable advance logs", + category: "ADMIN", + userPermissions: ["ManageGuild"], + command: { + enabled: true, + subcommands: [ + { + trigger: "channels <#channel|off>", + description: "enable logging for channel updates", + }, + { + trigger: "members <#channel|off>", + description: "enable logging for member updates", + }, + { + trigger: "voice <#channel|off>", + description: "enable logging for voice channel updates", + }, + { + trigger: "invites <#channel|off>", + description: "enable logging for invite creation and deletion", + }, + { + trigger: "messages <#channel|off>", + description: "enable logging for message updates", + }, + { + trigger: "bans <#channel|off>", + description: "enable logging for bans updates", + }, + ], + minArgsCount: 2, + }, + slashCommand: { + enabled: true, + ephemeral: true, + options: [ + { + name: "channels", + description: "enable logging for channel updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, + { + name: "members", + description: "enable logging for members updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, + { + name: "voice", + description: "enable logging for voice channel updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, + { + name: "invites", + description: "enable logging for invites updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, + { + name: "messages", + description: "enable logging for messages updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, + { + name: "bans", + description: "enable logging for bans updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, + ], + }, + + async messageRun(message, args, data) { + const settings = data.settings; + const sub = args[0].toLowerCase(); + const input = args[1].toLowerCase(); + let targetChannel; + + if (input === "none" || input === "off" || input === "disable") targetChannel = null; + else { + if (message.mentions.channels.size === 0) return message.safeReply("Incorrect command usage"); + targetChannel = message.mentions.channels.first(); + } + + const response = await setChannel(targetChannel, settings, sub); + return message.safeReply(response); + }, + + async interactionRun(interaction, data) { + const sub = interaction.options.getSubcommand(); + let channel = interaction.options.getChannel("channel"); + if (!channel) channel = null; + const response = await setChannel(channel, data.settings, sub); + return interaction.followUp(response); + }, +}; + +async function setChannel(targetChannel, settings, sub) { + if (!targetChannel && !settings[sub]) { + return "It is already disabled"; + } + + if (targetChannel && !targetChannel.canSendEmbeds()) { + return "Ugh! I cannot send logs to that channel? I need the `Write Messages` and `Embed Links` permissions in that channel"; + } + + settings[sub] = targetChannel?.id; + await settings.save(); + return `Configuration saved! **${parseSub(sub)} Updates** channel ${targetChannel ? `updated to ${targetChannel}` : "removed"}`; +} +/** + * @param {string} sub + */ +const parseSub = (sub) => { + return sub.charAt(0).toUpperCase() + sub.slice(1); +} \ No newline at end of file diff --git a/src/database/schemas/Guild.js b/src/database/schemas/Guild.js index 25e2b2f76..77690d164 100644 --- a/src/database/schemas/Guild.js +++ b/src/database/schemas/Guild.js @@ -60,6 +60,15 @@ const Schema = new mongoose.Schema({ enabled: Boolean, }, modlog_channel: String, + advance_logging: { + messages: String, + channels: String, + members: String, + roles: String, + bans: String, + invites: String, + voice: String, + }, max_warn: { action: { type: String, @@ -132,7 +141,7 @@ module.exports = { const userDb = await getUser(owner); await userDb.save(); }) - .catch((ex) => {}); + .catch((ex) => { }); // create a new guild model guildData = new Model({ diff --git a/src/events/member/guildMemberUpdate.js b/src/events/member/guildMemberUpdate.js new file mode 100644 index 000000000..6376a236a --- /dev/null +++ b/src/events/member/guildMemberUpdate.js @@ -0,0 +1,66 @@ + +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent, time } = require("discord.js"); +/** + * @param {import('@src/structures').BotClient} client + * @param {import('discord.js').GuildMember|import('discord.js').PartialGuildMember} oldMember + * @param {import('discord.js').GuildMember|import('discord.js').PartialGuildMember} newMember + */ +module.exports = async (client, oldMember, newMember) => { + if (oldMember.partial) return + if (!newMember.guild) return; + const settings = await getSettings(newMember.guild); + if (!settings.logging.members) return; + const logChannel = client.channels.cache.get(settings.logging.members); + let embed = new EmbedBuilder().setColor("Green").setTimestamp(); + if (oldMember.nickname !== newMember.nickname) { + embed + .setAuthor({ name: "Nickname Changed" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .addFields( + { name: "User", value: newMember.toString(), inline: true }, + { name: "Old Nickname", value: oldMember.nickname || "None", inline: true }, + { name: "New Nickname", value: newMember.nickname || "None", inline: true } + ) + .setFooter({ text: `ID: ${newMember.id}` }) + } + + if (!oldMember.roles.cache.every(role => newMember.roles.cache.has(role.id))) { + const addedRoles = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id)); + const removedRoles = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id)); + + const rolesMessage = []; + + addedRoles.forEach(role => rolesMessage.push(`+ ${role}`)); + removedRoles.forEach(role => rolesMessage.push(`- ${role}`)); + embed + .setAuthor({ name: "Role Updated" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .addFields( + { name: "User", value: newMember.toString(), inline: true }, + { name: "Roles", value: rolesMessage.join("\n") } + ) + .setFooter({ text: `ID: ${newMember.id}` }) + } + if (!oldMember.isCommunicationDisabled() && !newMember.isCommunicationDisabled()) { + const auditLog = await newMember.guild.fetchAuditLogs({ type: AuditLogEvent.MemberUpdate, limit: 1 }); + const entry = auditLog.entries.first(); + const disabledTill = new Date(entry.changes[0].new); + const executor = entry.executor; + embed + .setAuthor({ name: "Member Timed-out" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .setColor("Red") + .addFields( + { name: "User", value: newMember.toString(), inline: true }, + { name: "Till", value: time(disabledTill, "F"), inline: true }, + { name: "Executor", value: executor.toString(), inline: true } + ) + .setFooter({ text: `ID: ${newMember.id}` }) + + } + + if (embed.fields.length === 0) return; + logChannel.send({ embeds: [embed] }).catch(() => { }); + +}; diff --git a/src/events/message/messageDelete.js b/src/events/message/messageDelete.js index d3ca32c3f..4b0aa56de 100644 --- a/src/events/message/messageDelete.js +++ b/src/events/message/messageDelete.js @@ -1,4 +1,4 @@ -const { EmbedBuilder } = require("discord.js"); +const { EmbedBuilder, AuditLogEvent, ButtonBuilder, ActionRowBuilder, ButtonStyle } = require("discord.js"); const { getSettings } = require("@schemas/Guild"); /** @@ -10,40 +10,78 @@ module.exports = async (client, message) => { if (message.author.bot || !message.guild) return; const settings = await getSettings(message.guild); - if (!settings.automod.anti_ghostping || !settings.modlog_channel) return; - const { members, roles, everyone } = message.mentions; - - // Check message if it contains mentions - if (members.size > 0 || roles.size > 0 || everyone) { - const logChannel = message.guild.channels.cache.get(settings.modlog_channel); - if (!logChannel) return; - - const embed = new EmbedBuilder() - .setAuthor({ name: "Ghost ping detected" }) - .setDescription( - `**Message:**\n${message.content}\n\n` + + if (settings.automod.anti_ghostping && settings.modlog_channel) { + const { members, roles, everyone } = message.mentions; + + // Check message if it contains mentions + if (members.size > 0 || roles.size > 0 || everyone) { + const logChannel = message.guild.channels.cache.get(settings.modlog_channel); + if (!logChannel) return; + + const embed = new EmbedBuilder() + .setAuthor({ name: "Ghost ping detected" }) + .setDescription( + `**Message:**\n${message.content}\n\n` + `**Author:** ${message.author.tag} \`${message.author.id}\`\n` + `**Channel:** ${message.channel.toString()}` + ) + .addFields( + { + name: "Members", + value: members.size.toString(), + inline: true, + }, + { + name: "Roles", + value: roles.size.toString(), + inline: true, + }, + { + name: "Everyone?", + value: everyone ? "Yes" : "No", + inline: true, + } + ) + .setFooter({ text: `Sent at: ${message.createdAt}` }); + + logChannel.safeSend({ embeds: [embed] }); + } + } else { + const { content, author } = message; + if (!settings.logging.messages) return + const logChannel = client.channels.cache.get(settings.logging.messages); + + const entry = await message.guild + .fetchAuditLogs({ type: AuditLogEvent.MessageDelete, limit: 1 }) + .then((audit) => audit.entries.first()); + let user = ""; + if (entry && entry.extra.channel.id === message.channel.id && entry.target.id === message.author.id) { + user = entry.executor.username; + } + + const logEmbed = new EmbedBuilder() + .setAuthor({ name: "Message Deleted" }) + .setThumbnail(author.displayAvatarURL()) + .setColor("#2c2d31") + .setFields( + { name: "Author", value: author.toString(), inline: true }, + { name: "Channel", value: message.channel.toString(), inline: true }, + { name: "Deleted Message", value: `> ${content}` }, + { name: "Deleted By", value: user ? user : "Unknown" } ) - .addFields( - { - name: "Members", - value: members.size.toString(), - inline: true, - }, - { - name: "Roles", - value: roles.size.toString(), - inline: true, - }, - { - name: "Everyone?", - value: everyone ? "Yes" : "No", - inline: true, - } - ) - .setFooter({ text: `Sent at: ${message.createdAt}` }); + .setTimestamp(); - logChannel.safeSend({ embeds: [embed] }); + if (message?.attachments?.size !== 0) { + let btn = []; + let i = 0; + message.attachments.forEach((a) => { + i++; + btn.push(new ButtonBuilder().setLabel(`Attachment ${i}`).setURL(a.proxyURL).setStyle(ButtonStyle.Link)); + }); + const row = new ActionRowBuilder().addComponents(btn); + logChannel.safeSend({ embeds: [logEmbed], components: [row] }).catch(() => { }); + } else { + logChannel.safeSend({ embeds: [logEmbed] }).catch(() => { }); + } } }; diff --git a/src/events/message/messageUpdate.js b/src/events/message/messageUpdate.js new file mode 100644 index 000000000..58354f8e4 --- /dev/null +++ b/src/events/message/messageUpdate.js @@ -0,0 +1,45 @@ +const { EmbedBuilder } = require("discord.js"); +/** + * @param {import('@src/structures').BotClient} client + * @param {import('discord.js').Message|import('discord.js').PartialMessage} oldMessage + * @param {import('discord.js').Message|import('discord.js').PartialMessage} newMessage + */ +module.exports = async (client, oldMessage, newMessage) => { + if (oldMessage.partial) return; + const { content } = oldMessage; + const { author } = newMessage; + const logChannel = client.channels.cache.get("1202855179377057822"); + if (newMessage.author.bot) return; + if (newMessage.guild.id !== "852141490105090059") return; + if (!logChannel) return; + const logEmbed = new EmbedBuilder() + .setAuthor({ name: "Message Edited" }) + .setThumbnail(author.displayAvatarURL()) + .setColor("#2c2d31") + .setFields( + { name: "Author", value: author.toString(), inline: true }, + { + name: "Channel", + value: oldMessage.channel.toString(), + inline: true + }, + { + name: "Old Messgae", + value: `${content}` + }, + { + name: "New Message", + value: `${newMessage.content}` + } + ) + .setFooter({ + text: `By ${author.username} (${author.id})`, + iconURL: author.avatarURL() + }) + .setTimestamp(); + + logChannel.safeSend({ embeds: [logEmbed] }).catch(() => { }); +}; + + + From 2f9ebf50158beacbcbbc316328ff2ee5b0eaa2b2 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Sun, 30 Jun 2024 03:53:54 +0530 Subject: [PATCH 02/11] channelUpdate complete (i think) --- src/commands/admin/logging.js | 22 ++- src/database/schemas/Guild.js | 3 +- src/events/channel/channelCreate.js | 28 ++++ src/events/channel/channelDelete.js | 29 ++++ src/events/channel/channelUpdate.js | 183 ++++++++++++++++++++++++ src/events/guildAuditLogEntryCreate.js | 5 + src/events/member/guildMemberUpdate.js | 56 +++++++- src/events/message/messageDelete.js | 4 +- src/events/message/messageDeleteBulk.js | 31 ++++ src/events/message/messageUpdate.js | 2 +- src/events/voice/voiceStateUpdate.js | 50 +++++++ src/helpers/Utils.js | 8 +- src/structures/BotClient.js | 3 +- 13 files changed, 411 insertions(+), 13 deletions(-) create mode 100644 src/events/channel/channelCreate.js create mode 100644 src/events/channel/channelDelete.js create mode 100644 src/events/channel/channelUpdate.js create mode 100644 src/events/guildAuditLogEntryCreate.js create mode 100644 src/events/message/messageDeleteBulk.js diff --git a/src/commands/admin/logging.js b/src/commands/admin/logging.js index 811fcd1ff..26557e80d 100644 --- a/src/commands/admin/logging.js +++ b/src/commands/admin/logging.js @@ -23,6 +23,10 @@ module.exports = { trigger: "voice <#channel|off>", description: "enable logging for voice channel updates", }, + { + trigger: "emojis <#channel|off>", + description: "enable logging for emojis creation/updates and deletion", + }, { trigger: "invites <#channel|off>", description: "enable logging for invite creation and deletion", @@ -84,6 +88,20 @@ module.exports = { }, ], }, + { + name: "emojis", + description: "enable logging for emojis updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, { name: "invites", description: "enable logging for invites updates", @@ -155,7 +173,7 @@ module.exports = { }; async function setChannel(targetChannel, settings, sub) { - if (!targetChannel && !settings[sub]) { + if (!targetChannel && !settings.logging[sub]) { return "It is already disabled"; } @@ -163,7 +181,7 @@ async function setChannel(targetChannel, settings, sub) { return "Ugh! I cannot send logs to that channel? I need the `Write Messages` and `Embed Links` permissions in that channel"; } - settings[sub] = targetChannel?.id; + settings.logging[sub] = targetChannel?.id; await settings.save(); return `Configuration saved! **${parseSub(sub)} Updates** channel ${targetChannel ? `updated to ${targetChannel}` : "removed"}`; } diff --git a/src/database/schemas/Guild.js b/src/database/schemas/Guild.js index 77690d164..f3579352e 100644 --- a/src/database/schemas/Guild.js +++ b/src/database/schemas/Guild.js @@ -60,9 +60,10 @@ const Schema = new mongoose.Schema({ enabled: Boolean, }, modlog_channel: String, - advance_logging: { + logging: { messages: String, channels: String, + emojis: String, members: String, roles: String, bans: String, diff --git a/src/events/channel/channelCreate.js b/src/events/channel/channelCreate.js new file mode 100644 index 000000000..e8f6f2708 --- /dev/null +++ b/src/events/channel/channelCreate.js @@ -0,0 +1,28 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent, ChannelType } = require("discord.js"); +/** + * @param {import('@src/structures').BotClient} client + * @param {import("discord.js").GuildBasedChannel} channel + */ +module.exports = async (client, channel) => { + const settings = await getSettings(channel.guild); + if (!settings.logging.channels) return; + const logChannel = client.channels.cache.get(settings.logging.channels); + const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelCreate, limit: 1 }); + const entry = auditLog.entries.first(); + const executor = entry.target.id === channel.id ? entry.executor : null; + const channelType = ChannelType[channel.type]; + const embed = new EmbedBuilder() + .setAuthor({ name: "Channel Created" }) + .setDescription(`Channel ${channel} was created`) + .addFields({ + name: "Channel Type", + value: channelType, + }, { + name: "Executor", + value: executor ? executor.toString() : "Unknown", + }) + .setTimestamp() + .setFooter({ text: `ID: ${channel.id}` }); + logChannel.safeSend({ embeds: [embed] }); +}; diff --git a/src/events/channel/channelDelete.js b/src/events/channel/channelDelete.js new file mode 100644 index 000000000..0f19e28f8 --- /dev/null +++ b/src/events/channel/channelDelete.js @@ -0,0 +1,29 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent, ChannelType } = require("discord.js"); +/** + * @param {import('@src/structures').BotClient} client + * @param {import("discord.js").GuildBasedChannel} channel + */ +module.exports = async (client, channel) => { + const settings = await getSettings(channel.guild); + if (!settings.logging.channels) return; + const logChannel = client.channels.cache.get(settings.logging.channels); + const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelDelete, limit: 1 }); + const entry = auditLog.entries.first(); + const executor = entry.target.id === channel.id ? entry.executor : null; + const channelType = ChannelType[channel.type]; + const embed = new EmbedBuilder() + .setAuthor({ name: "Channel Deleted" }) + .setDescription(`Channel \`${channel.name}\` was Deleted`) + .addFields({ + name: "Channel Type", + value: channelType, + }, { + name: "Executor", + value: executor ? executor.toString() : "Unknown", + }) + .setTimestamp() + .setColor("Red") + .setFooter({ text: `ID: ${channel.id}` }); + logChannel.safeSend({ embeds: [embed] }); +}; diff --git a/src/events/channel/channelUpdate.js b/src/events/channel/channelUpdate.js new file mode 100644 index 000000000..20d688a36 --- /dev/null +++ b/src/events/channel/channelUpdate.js @@ -0,0 +1,183 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent, PermissionsBitField, OverwriteType } = require("discord.js"); +const utils = require("@helpers/Utils") +/** + * @param {import('@src/structures').BotClient} client + * @param {import("discord.js").GuildBasedChannel} oldChannel + * @param {import("discord.js").GuildBasedChannel} newChannel + */ +module.exports = async (client, oldChannel, newChannel) => { + const settings = await getSettings(newChannel.guild); + if (!settings.logging.channels) return; + const logChannel = client.channels.cache.get(settings.logging.channels); + const embed = new EmbedBuilder() + .setAuthor({ name: "Channel Updated" }) + .setTimestamp() + .setColor("Green") + let auditLogType = AuditLogEvent.ChannelUpdate; + // Return if category deleted, fires for every children channel and not worth logging in my opinion + if (oldChannel.parent && !newChannel.parent) return; + + // Name change + if (oldChannel.name !== newChannel.name) { + embed + .setAuthor({ name: "Channel Updated" }) + .setTitle(`Channel name updated for ${newChannel}`) + .setDescription(`\`${oldChannel.name}\` -> \`${newChannel.name}\``) + .setColor("Green") + } + + // Category Changed + if (oldChannel.parent?.id !== newChannel.parent?.id) { + embed + .setTitle(`Channel category changed for ${newChannel}`) + .setDescription(`\`${oldChannel.parent || "none"}\` -> \`${newChannel.parent || "none"}\``) + + } + + // Overwrites changes + const changes = getDifferrence(oldChannel.permissionOverwrites.cache, newChannel.permissionOverwrites.cache) + if (changes.changes.length > 0) { + auditLogType = changes.entryType; + embed + .setTitle(`Permission overwrites updated in channel ${newChannel}:`) + .setDescription( + changes.changes.join("\n") + ) + } + + // Channel topic + if (oldChannel.topic !== newChannel.topic) { + embed + .setTitle(`Topic changed for ${newChannel}`) + .addFields( + { + name: "Old Topic", + value: `\`${oldChannel.topic}\`` + }, + { + name: "New Topic", + value: `\`${newChannel.topic}\`` + } + ) + } + + // nsfw + if (oldChannel.nsfw !== newChannel.nsfw) { + embed + .setTitle(`NSFW status changed for channel ${newChannel}`) + .setDescription(`\`${oldChannel.nsfw}\` -> \`${newChannel.nsfw}\``) + .setColor("Red") + } + + // Slowmode + if (oldChannel.rateLimitPerUser !== newChannel.rateLimitPerUser) { + embed + .setTitle(`Slowmode updated for ${newChannel}`) + .setDescription(`\`${oldChannel.rateLimitPerUser ? utils.timeformat(oldChannel.rateLimitPerUser) : "0 seconds"}\` -> \`${newChannel.rateLimitPerUser ? utils.timeformat(newChannel.rateLimitPerUser) : "0 seconds"}\``) + } + // bitrate + if (oldChannel.bitrate !== newChannel.bitrate) { + embed + .setTitle(`Channel bitrate changed for ${newChannel}`) + .setDescription(`\`${oldChannel.bitrate}kbps\` -> \`${newChannel.bitrate}kbps\``) + + } + + // User limit for voice + if (oldChannel.userLimit !== newChannel.userLimit) { + embed + .setTitle(`Channel user limit changed for ${newChannel}`) + .setDescription(`\`${oldChannel.userLimit} users\` -> \`${newChannel.userLimit} users\``) + + } + + // Idk if i am missing anything, will add if it comes to notice + const entry = await getAuditLog(auditLogType, newChannel.guild); + const executor = entry.targetId === newChannel.id ? entry.executor : "Unknown" + embed.setFooter({ text: `ID: ${newChannel.id} | Executor: ${executor ? executor.username : "Unknown"}` }); + if (!embed.data.title) return; + logChannel.safeSend({ embeds: [embed] }); +}; + +/** + * Get Permissions overwrites changes + * @param {import("discord.js").Collection { + const changes = []; + let entryType; + // Function to get the permission names from the bitfield + const getPermissionNames = (bitfield) => { + return new PermissionsBitField(bitfield).toArray(); + }; + + // Check for added or modified permission overwrites + newOverwrites.forEach((newOverwrite, id) => { + const oldOverwrite = oldOverwrites.get(id); + + if (!oldOverwrite) { + entryType = AuditLogEvent.ChannelOverwriteCreate + const addedPermissions = getPermissionNames(newOverwrite.allow.bitfield).map(p => `${p}: allow`); + const deniedPermissions = getPermissionNames(newOverwrite.deny.bitfield).map(p => `${p}: deny`); + changes.push(`+ Added new overwrite for ${newOverwrite.type === OverwriteType.Member ? `<@${id}>` : `<@&${id}>`}: ${[...addedPermissions, ...deniedPermissions].join(', ')}`); + } else { + entryType = AuditLogEvent.ChannelOverwriteUpdate + const oldAllow = getPermissionNames(oldOverwrite.allow.bitfield); + const newAllow = getPermissionNames(newOverwrite.allow.bitfield); + const oldDeny = getPermissionNames(oldOverwrite.deny.bitfield); + const newDeny = getPermissionNames(newOverwrite.deny.bitfield); + + const addedAllow = newAllow.filter(p => !oldAllow.includes(p)); + const removedAllow = oldAllow.filter(p => !newAllow.includes(p)); + const addedDeny = newDeny.filter(p => !oldDeny.includes(p)); + const removedDeny = oldDeny.filter(p => !newDeny.includes(p)); + const changeMap = new Map() + if (addedAllow.length || removedAllow.length || addedDeny.length || removedDeny.length) { + changes.push(`+ Modified overwrite for ${newOverwrite.type === OverwriteType.Member ? `<@${id}>` : `<@&${id}>`}:`); + + addedAllow.forEach(p => changeMap.set(p, `none -> allow`)); + removedAllow.forEach(p => changeMap.set(p, `allow -> none`)); + addedDeny.forEach(p => changeMap.set(p, `none -> deny`)); + removedDeny.forEach(p => changeMap.set(p, `deny -> none`)); + + // Handle changes from allow to deny or vice versa + oldAllow.forEach(p => { + if (newDeny.includes(p)) { + changeMap.set(p, `allow -> deny`); + } + }); + oldDeny.forEach(p => { + if (newAllow.includes(p)) { + changeMap.set(p, `deny -> allow`); + } + }); + changeMap.forEach((v, id) => changes.push(`- ${id}: ${v}`)) + } + } + }); + + // Check for removed permission overwrites + oldOverwrites.forEach((oldOverwrite, id) => { + if (!newOverwrites.has(id)) { + entryType = AuditLogEvent.ChannelOverwriteDelete + const removedPermissions = getPermissionNames(oldOverwrite.allow.bitfield).map(p => `${p}: allow`); + const deniedPermissions = getPermissionNames(oldOverwrite.deny.bitfield).map(p => `${p}: deny`); + changes.push(`- Removed overwrites for ${oldOverwrite.type === OverwriteType.Member ? `<@${id}>` : `<@&${id}>`}: ${[...removedPermissions, ...deniedPermissions].join(', ')}`); + } + }); + + return { entryType, changes } +} + +/** + * + * @param {any} type + * @param {import("discord.js").Guild} guild + * @returns + */ +const getAuditLog = async (type, guild) => { + const auditLog = await guild.fetchAuditLogs({ type: type, limit: 1 }); + return auditLog.entries.first(); +} \ No newline at end of file diff --git a/src/events/guildAuditLogEntryCreate.js b/src/events/guildAuditLogEntryCreate.js new file mode 100644 index 000000000..685ac76d6 --- /dev/null +++ b/src/events/guildAuditLogEntryCreate.js @@ -0,0 +1,5 @@ +const { AuditLogEvent } = require("discord.js") +module.exports = async (client, entry, guild) => { + /* console.log("hi") + console.log(AuditLogEvent[entry.action]) */ +} diff --git a/src/events/member/guildMemberUpdate.js b/src/events/member/guildMemberUpdate.js index 6376a236a..7c58c5dce 100644 --- a/src/events/member/guildMemberUpdate.js +++ b/src/events/member/guildMemberUpdate.js @@ -13,6 +13,18 @@ module.exports = async (client, oldMember, newMember) => { if (!settings.logging.members) return; const logChannel = client.channels.cache.get(settings.logging.members); let embed = new EmbedBuilder().setColor("Green").setTimestamp(); + // Onboarding + if (oldMember.pending && !newMember.pending) { + embed + .setAuthor({ name: "Member Completed Onboarding" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .addFields( + { name: "User", value: newMember.toString(), inline: true } + ) + .setFooter({ text: `ID: ${newMember.id}` }); + } + + // Nickname change if (oldMember.nickname !== newMember.nickname) { embed .setAuthor({ name: "Nickname Changed" }) @@ -25,7 +37,8 @@ module.exports = async (client, oldMember, newMember) => { .setFooter({ text: `ID: ${newMember.id}` }) } - if (!oldMember.roles.cache.every(role => newMember.roles.cache.has(role.id))) { + // Role changes + if (!oldMember.roles.cache.every(role => newMember.roles.cache.has(role.id)) || (oldMember.roles.cache.size !== newMember.roles.cache.size)) { const addedRoles = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id)); const removedRoles = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id)); @@ -42,6 +55,8 @@ module.exports = async (client, oldMember, newMember) => { ) .setFooter({ text: `ID: ${newMember.id}` }) } + + // Timeout if (!oldMember.isCommunicationDisabled() && !newMember.isCommunicationDisabled()) { const auditLog = await newMember.guild.fetchAuditLogs({ type: AuditLogEvent.MemberUpdate, limit: 1 }); const entry = auditLog.entries.first(); @@ -60,7 +75,44 @@ module.exports = async (client, oldMember, newMember) => { } + // Guild avatar change + if (oldMember.avatar !== newMember.avatar) { + embed + .setAuthor({ name: "Avatar Changed" }) + .setThumbnail(newMember.displayAvatarURL()) + .addFields( + { name: "User", value: newMember.toString(), inline: true } + ) + .setImage(newMember.displayAvatarURL({ size: 2048 })) + .setFooter({ text: `ID: ${newMember.id}` }); + } + + // Voice muted + if (!oldMember.voice?.serverMute && newMember.voice?.serverMute) { + embed + .setAuthor({ name: "Member Server Muted for Voice Channels" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .setColor("Red") + .addFields( + { name: "User", value: newMember.toString(), inline: true } + ) + .setFooter({ text: `ID: ${newMember.id}` }); + } + + // Voice Mute Expires + if (oldMember.voice?.serverMute && !newMember.voice?.serverMute) { + embed + .setAuthor({ name: "Member Server Mute Removed for Voice Channels" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .setColor("Green") + .addFields({ + name: "User", + value: newMember.toString(), + }) + .setFooter({ text: `ID: ${newMember.id}` }) + } + if (embed.fields.length === 0) return; - logChannel.send({ embeds: [embed] }).catch(() => { }); + logChannel.send({ embeds: [embed] }) }; diff --git a/src/events/message/messageDelete.js b/src/events/message/messageDelete.js index 4b0aa56de..9d7b48bc4 100644 --- a/src/events/message/messageDelete.js +++ b/src/events/message/messageDelete.js @@ -79,9 +79,9 @@ module.exports = async (client, message) => { btn.push(new ButtonBuilder().setLabel(`Attachment ${i}`).setURL(a.proxyURL).setStyle(ButtonStyle.Link)); }); const row = new ActionRowBuilder().addComponents(btn); - logChannel.safeSend({ embeds: [logEmbed], components: [row] }).catch(() => { }); + logChannel.safeSend({ embeds: [logEmbed], components: [row] }) } else { - logChannel.safeSend({ embeds: [logEmbed] }).catch(() => { }); + logChannel.safeSend({ embeds: [logEmbed] }) } } }; diff --git a/src/events/message/messageDeleteBulk.js b/src/events/message/messageDeleteBulk.js new file mode 100644 index 000000000..3bbf5a7fc --- /dev/null +++ b/src/events/message/messageDeleteBulk.js @@ -0,0 +1,31 @@ +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); +const { getSettings } = require("@schemas/Guild"); + +/** + * @param {import('@src/structures').BotClient} client - The bot client instance + * @param {import("discord.js").Collection} messages - A collection of messages or partial + * @param {import("discord.js").GuildTextBasedChannel} channel Channel where the action occured +*/ +module.exports = async (client, messages, channel) => { + const settings = await getSettings(channel.guild); + + if (!settings.logging.messages) return + const logChannel = client.channels.cache.get(settings.logging.messages); + const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.MessageBulkDelete, limit: 1 }); + const entry = auditLog.entries.first(); + const executor = entry.extra.channel.id === channel.id ? entry.executor : null; + const logEmbed = new EmbedBuilder() + .setAuthor({ name: "Bulk Message Deleted" }) + .setThumbnail() + .setColor("Red") + .setDescription(`${messages.size} Messages deleted in ${channel}`) + .setFields({ + name: "Executor", + value: executor ? executor.toString() : "Unknown", + }) + .setTimestamp(); + + + logChannel.safeSend({ embeds: [logEmbed] }) + +}; diff --git a/src/events/message/messageUpdate.js b/src/events/message/messageUpdate.js index 58354f8e4..ea6ac7b13 100644 --- a/src/events/message/messageUpdate.js +++ b/src/events/message/messageUpdate.js @@ -38,7 +38,7 @@ module.exports = async (client, oldMessage, newMessage) => { }) .setTimestamp(); - logChannel.safeSend({ embeds: [logEmbed] }).catch(() => { }); + logChannel.safeSend({ embeds: [logEmbed] }) }; diff --git a/src/events/voice/voiceStateUpdate.js b/src/events/voice/voiceStateUpdate.js index 9c8da8c30..9134eadee 100644 --- a/src/events/voice/voiceStateUpdate.js +++ b/src/events/voice/voiceStateUpdate.js @@ -1,4 +1,6 @@ const { trackVoiceStats } = require("@handlers/stats"); +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder } = require("discord.js") /** * @param {import('@src/structures').BotClient} client @@ -27,4 +29,52 @@ module.exports = async (client, oldState, newState) => { }, client.config.MUSIC.IDLE_TIME * 1000); } } + + // Logging + const settings = await getSettings(newState.guild); + if (!settings.logging.voice) return; + const logChannel = client.channels.cache.get(settings.logging.voice); + + const embed = new EmbedBuilder().setColor("Green").setTimestamp(); + + // Member joins + if (!oldState.channel && newState.channel) { + embed + .setAuthor({ name: "Member Joined Voice Channel" }) + .setThumbnail(newState.member.user.displayAvatarURL()) + .addFields( + { name: "User", value: newState.member.toString(), inline: true }, + { name: "Channel", value: newState.channel.toString(), inline: true } + ) + .setFooter({ text: `ID: ${newState.member.id}` }); + } + + // Member moved + if (oldState.channel.id !== newState.channel.id) { + embed + .setAuthor({ name: "Member Moved Voice Channel" }) + .setThumbnail(newState.member.user.displayAvatarURL()) + .addFields( + { name: "User", value: newState.member.toString(), inline: true }, + { name: "Old Channel", value: oldState.channel.toString(), inline: true }, + { name: "New Channel", value: newState.channel.toString(), inline: true } + ) + .setFooter({ text: `ID: ${newState.member.id}` }); + } + + // Member Left + if (oldState.channel && !newState.channel) { + embed + .setAuthor({ name: "Member Left Voice Channel" }) + .setThumbnail(oldState.member.user.displayAvatarURL()) + .addFields( + { name: "User", value: oldState.member.toString(), inline: true }, + { name: "Channel", value: oldState.channel.toString(), inline: true } + ) + .setColor("Red") + .setFooter({ text: `ID: ${oldState.member.id}` }); + } + + + if (embed.fields.length) logChannel.send({ embeds: [embed] }) }; diff --git a/src/helpers/Utils.js b/src/helpers/Utils.js index 3768d76a9..3307807a5 100644 --- a/src/helpers/Utils.js +++ b/src/helpers/Utils.js @@ -71,10 +71,10 @@ module.exports = class Utils { const minutes = Math.floor((timeInSeconds % 3600) / 60); const seconds = Math.round(timeInSeconds % 60); return ( - (days > 0 ? `${days} days, ` : "") + - (hours > 0 ? `${hours} hours, ` : "") + - (minutes > 0 ? `${minutes} minutes, ` : "") + - (seconds > 0 ? `${seconds} seconds` : "") + (days > 0 ? `${days} days` : "") + + (hours > 0 ? `, ${hours} hours` : "") + + (minutes > 0 ? `, ${minutes} minutes` : "") + + (seconds > 0 ? `, ${seconds} seconds` : "") ); } diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index 5a60c6e6d..5f25aa5f8 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -29,6 +29,7 @@ module.exports = class BotClient extends Client { GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildVoiceStates, + GatewayIntentBits.GuildModeration ], partials: [Partials.User, Partials.Message, Partials.Reaction], allowedMentions: { @@ -283,7 +284,7 @@ module.exports = class BotClient extends Client { const patternMatch = search.match(/(\d{17,20})/); if (patternMatch) { const id = patternMatch[1]; - const fetched = await this.users.fetch(id, { cache: true }).catch(() => {}); // check if mentions contains the ID + const fetched = await this.users.fetch(id, { cache: true }).catch(() => { }); // check if mentions contains the ID if (fetched) { users.push(fetched); return users; From 78391048c0eba0a742c15992dc3ab9fec4850544 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:30:48 +0530 Subject: [PATCH 03/11] final --- config.js | 2 +- src/commands/admin/logging.js | 51 +++++++++++++-- src/events/bans/guildBanAdd.js | 26 ++++++++ src/events/bans/guildBanRemove.js | 26 ++++++++ src/events/channel/channelCreate.js | 5 +- src/events/channel/channelDelete.js | 5 +- src/events/channel/channelUpdate.js | 73 +++++++++------------ src/events/emojis/emojiCreate.js | 24 +++++++ src/events/emojis/emojiDelete.js | 24 +++++++ src/events/emojis/emojiUpdate.js | 47 ++++++++++++++ src/events/guildAuditLogEntryCreate.js | 5 -- src/events/invite/inviteCreate.js | 22 +++++++ src/events/invite/inviteDelete.js | 20 ++++++ src/events/member/guildMemberRemove.js | 19 +++++- src/events/member/guildMemberUpdate.js | 17 ++--- src/events/message/messageUpdate.js | 6 +- src/events/roles/roleCreate.js | 27 ++++++++ src/events/roles/roleDelete.js | 27 ++++++++ src/events/roles/roleUpdate.js | 89 ++++++++++++++++++++++++++ src/events/userUpdate.js | 54 ++++++++++++++++ src/events/voice/voiceStateUpdate.js | 4 +- src/structures/BotClient.js | 3 +- 22 files changed, 504 insertions(+), 72 deletions(-) create mode 100644 src/events/bans/guildBanAdd.js create mode 100644 src/events/bans/guildBanRemove.js create mode 100644 src/events/emojis/emojiCreate.js create mode 100644 src/events/emojis/emojiDelete.js create mode 100644 src/events/emojis/emojiUpdate.js delete mode 100644 src/events/guildAuditLogEntryCreate.js create mode 100644 src/events/roles/roleCreate.js create mode 100644 src/events/roles/roleDelete.js create mode 100644 src/events/roles/roleUpdate.js create mode 100644 src/events/userUpdate.js diff --git a/config.js b/config.js index ea69a13a1..815bb7cec 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,5 @@ module.exports = { - OWNER_IDS: [""], // Bot owner ID's + OWNER_IDS: ["851588007697580033"], // Bot owner ID's SUPPORT_SERVER: "", // Your bot support server PREFIX_COMMANDS: { ENABLED: true, // Enable/Disable prefix commands diff --git a/src/commands/admin/logging.js b/src/commands/admin/logging.js index 26557e80d..a696af721 100644 --- a/src/commands/admin/logging.js +++ b/src/commands/admin/logging.js @@ -1,5 +1,6 @@ +const { EmbedBuilder } = require("discord.js"); const { ApplicationCommandOptionType, ChannelType } = require("discord.js"); - +const subs = ["channels", "bans", "messages", "voice", "members", "emojis", "invites", "status"] /** * @type {import("@structures/Command")} */ @@ -39,8 +40,12 @@ module.exports = { trigger: "bans <#channel|off>", description: "enable logging for bans updates", }, + { + trigger: "status", + description: "check logging setup status", + }, ], - minArgsCount: 2, + minArgsCount: 1, }, slashCommand: { enabled: true, @@ -130,6 +135,11 @@ module.exports = { }, ], }, + { + name: "status", + description: "check logging setup status", + type: ApplicationCommandOptionType.Subcommand, + }, { name: "bans", description: "enable logging for bans updates", @@ -150,16 +160,22 @@ module.exports = { async messageRun(message, args, data) { const settings = data.settings; const sub = args[0].toLowerCase(); - const input = args[1].toLowerCase(); + const input = args[1]?.toLowerCase(); + if (!subs.includes(sub)) return message.safeReply(`Invalid subcommands\nAvaliable Subcommands: ${subs.map((s) => `\`${s}\``).join(", ")}`) let targetChannel; - if (input === "none" || input === "off" || input === "disable") targetChannel = null; + if (input === "none" || input === "off" || input === "disable" || !input) targetChannel = null; else { if (message.mentions.channels.size === 0) return message.safeReply("Incorrect command usage"); targetChannel = message.mentions.channels.first(); } - - const response = await setChannel(targetChannel, settings, sub); + let response; + if (sub === "status") { + response = getLogStatus(settings, message.client) + } + else { + response = await setChannel(targetChannel, settings, sub); + } return message.safeReply(response); }, @@ -167,7 +183,13 @@ module.exports = { const sub = interaction.options.getSubcommand(); let channel = interaction.options.getChannel("channel"); if (!channel) channel = null; - const response = await setChannel(channel, data.settings, sub); + let response; + if (sub === "status") { + response = getLogStatus(data.settings, interaction.client) + } + else { + response = await setChannel(channel, data.settings, sub); + } return interaction.followUp(response); }, }; @@ -190,4 +212,19 @@ async function setChannel(targetChannel, settings, sub) { */ const parseSub = (sub) => { return sub.charAt(0).toUpperCase() + sub.slice(1); +} + +function getLogStatus(settings, client) { + const formatted = Object.entries(settings.logging).map(([k, v]) => { + if (!v) return `${parseSub(k)}: none` + const channel = client.channels.cache.get(v); + return `${parseSub(k)}: ${channel ? channel.toString() : "none"}` + }).join("\n") + return { + embeds: [ + new EmbedBuilder() + .setTitle(`Logging Status`) + .setDescription(formatted) + ] + } } \ No newline at end of file diff --git a/src/events/bans/guildBanAdd.js b/src/events/bans/guildBanAdd.js new file mode 100644 index 000000000..fde4e1afd --- /dev/null +++ b/src/events/bans/guildBanAdd.js @@ -0,0 +1,26 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); + +/** + * Log when someone gets banned + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").GuildBan} ban + */ +module.exports = async (client, ban) => { + + const settings = await getSettings(ban.guild) + if (!settings.logging.bans) return; + const logChannel = client.channels.cache.get(settings.logging.bans); + if (!logChannel) return; + const auditLog = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanAdd, limit: 1 }).then((en) => en.entries.first()) + const executor = auditLog.targetId === ban.user.id ? auditLog.executor : "Unknown" + const embed = new EmbedBuilder() + .setAuthor({ name: "Member Banned" }) + .setTitle(`${ban.user} (${ban.user.username}) was banned!`) + .setColor("Red") + .setDescription(`Reason: ${ban.reason || "None"}`) + .setFooter({ text: `ID: ${ban.user.id} | Executor: ${executor?.username || "Unknown"}` }) + .setThumbnail(ban.user.displayAvatarURL()); + + logChannel.send({ embeds: [embed] }) +} diff --git a/src/events/bans/guildBanRemove.js b/src/events/bans/guildBanRemove.js new file mode 100644 index 000000000..e1dd57fef --- /dev/null +++ b/src/events/bans/guildBanRemove.js @@ -0,0 +1,26 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); + +/** + * Log when someone gets unbanned + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").GuildBan} ban + */ +module.exports = async (client, ban) => { + + const settings = await getSettings(ban.guild) + if (!settings.logging.bans) return; + const logChannel = client.channels.cache.get(settings.logging.bans); + if (!logChannel) return; + const auditLog = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanRemove, limit: 1 }).then((en) => en.entries.first()) + const executor = auditLog.targetId === ban.user.id ? auditLog.executor : "Unknown" + const embed = new EmbedBuilder() + .setAuthor({ name: "Member Unbanned" }) + .setTitle(`${ban.user} (${ban.user.username}) was unbanned!`) + .setColor("Green") + .setDescription(`Reason: ${ban.reason || "None"}`) + .setFooter({ text: `ID: ${ban.user.id} | Executor: ${executor?.username || "Unknown"}` }) + .setThumbnail(ban.user.displayAvatarURL()); + + logChannel.send({ embeds: [embed] }) +} diff --git a/src/events/channel/channelCreate.js b/src/events/channel/channelCreate.js index e8f6f2708..8825f989b 100644 --- a/src/events/channel/channelCreate.js +++ b/src/events/channel/channelCreate.js @@ -1,5 +1,5 @@ const { getSettings } = require("@schemas/Guild"); -const { EmbedBuilder, AuditLogEvent, ChannelType } = require("discord.js"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); /** * @param {import('@src/structures').BotClient} client * @param {import("discord.js").GuildBasedChannel} channel @@ -8,10 +8,11 @@ module.exports = async (client, channel) => { const settings = await getSettings(channel.guild); if (!settings.logging.channels) return; const logChannel = client.channels.cache.get(settings.logging.channels); + if (!logChannel) return; const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelCreate, limit: 1 }); const entry = auditLog.entries.first(); const executor = entry.target.id === channel.id ? entry.executor : null; - const channelType = ChannelType[channel.type]; + const channelType = require("@helpers/channelTypes")(channel.type); const embed = new EmbedBuilder() .setAuthor({ name: "Channel Created" }) .setDescription(`Channel ${channel} was created`) diff --git a/src/events/channel/channelDelete.js b/src/events/channel/channelDelete.js index 0f19e28f8..1d69ca16a 100644 --- a/src/events/channel/channelDelete.js +++ b/src/events/channel/channelDelete.js @@ -1,5 +1,5 @@ const { getSettings } = require("@schemas/Guild"); -const { EmbedBuilder, AuditLogEvent, ChannelType } = require("discord.js"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); /** * @param {import('@src/structures').BotClient} client * @param {import("discord.js").GuildBasedChannel} channel @@ -8,10 +8,11 @@ module.exports = async (client, channel) => { const settings = await getSettings(channel.guild); if (!settings.logging.channels) return; const logChannel = client.channels.cache.get(settings.logging.channels); + if (!logChannel) return; const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelDelete, limit: 1 }); const entry = auditLog.entries.first(); const executor = entry.target.id === channel.id ? entry.executor : null; - const channelType = ChannelType[channel.type]; + const channelType = require("@helpers/channelTypes")(channel.type); const embed = new EmbedBuilder() .setAuthor({ name: "Channel Deleted" }) .setDescription(`Channel \`${channel.name}\` was Deleted`) diff --git a/src/events/channel/channelUpdate.js b/src/events/channel/channelUpdate.js index 20d688a36..b6b97a3a8 100644 --- a/src/events/channel/channelUpdate.js +++ b/src/events/channel/channelUpdate.js @@ -10,6 +10,7 @@ module.exports = async (client, oldChannel, newChannel) => { const settings = await getSettings(newChannel.guild); if (!settings.logging.channels) return; const logChannel = client.channels.cache.get(settings.logging.channels); + if (!logChannel) return; const embed = new EmbedBuilder() .setAuthor({ name: "Channel Updated" }) .setTimestamp() @@ -17,21 +18,18 @@ module.exports = async (client, oldChannel, newChannel) => { let auditLogType = AuditLogEvent.ChannelUpdate; // Return if category deleted, fires for every children channel and not worth logging in my opinion if (oldChannel.parent && !newChannel.parent) return; - + let description = `### Changes to ${newChannel}\n` + let changed = false; // Name change if (oldChannel.name !== newChannel.name) { - embed - .setAuthor({ name: "Channel Updated" }) - .setTitle(`Channel name updated for ${newChannel}`) - .setDescription(`\`${oldChannel.name}\` -> \`${newChannel.name}\``) - .setColor("Green") + changed = true; + description += `**Name Changed:** \`${oldChannel.name}\` -> \`${newChannel.name}\`\n` } // Category Changed if (oldChannel.parent?.id !== newChannel.parent?.id) { - embed - .setTitle(`Channel category changed for ${newChannel}`) - .setDescription(`\`${oldChannel.parent || "none"}\` -> \`${newChannel.parent || "none"}\``) + changed = true; + description = `### Channel category changed for ${newChannel}\n ${oldChannel.parent || "none"} -> ${newChannel.parent || "none"}\n` } @@ -39,64 +37,57 @@ module.exports = async (client, oldChannel, newChannel) => { const changes = getDifferrence(oldChannel.permissionOverwrites.cache, newChannel.permissionOverwrites.cache) if (changes.changes.length > 0) { auditLogType = changes.entryType; - embed - .setTitle(`Permission overwrites updated in channel ${newChannel}:`) - .setDescription( - changes.changes.join("\n") - ) + changed = true; + description = `### Permission overwrites updated in channel ${newChannel}:\n` + changes.changes.join("\n") + } // Channel topic if (oldChannel.topic !== newChannel.topic) { - embed - .setTitle(`Topic changed for ${newChannel}`) - .addFields( - { - name: "Old Topic", - value: `\`${oldChannel.topic}\`` - }, - { - name: "New Topic", - value: `\`${newChannel.topic}\`` - } - ) + changed = true; + embed.addFields( + { + name: "Old Topic", + value: `\`${oldChannel.topic} \`` + }, + { + name: "New Topic", + value: `\`${newChannel.topic} \`` + } + ) } // nsfw if (oldChannel.nsfw !== newChannel.nsfw) { - embed - .setTitle(`NSFW status changed for channel ${newChannel}`) - .setDescription(`\`${oldChannel.nsfw}\` -> \`${newChannel.nsfw}\``) - .setColor("Red") + changed = true; + description += `**NSFW**: \`${oldChannel.nsfw}\` -> \`${newChannel.nsfw}\`\n` + } // Slowmode if (oldChannel.rateLimitPerUser !== newChannel.rateLimitPerUser) { - embed - .setTitle(`Slowmode updated for ${newChannel}`) - .setDescription(`\`${oldChannel.rateLimitPerUser ? utils.timeformat(oldChannel.rateLimitPerUser) : "0 seconds"}\` -> \`${newChannel.rateLimitPerUser ? utils.timeformat(newChannel.rateLimitPerUser) : "0 seconds"}\``) + changed = true; + description += `**Slowmode**: \`${oldChannel.rateLimitPerUser ? utils.timeformat(oldChannel.rateLimitPerUser) : "0 seconds"}\` -> \`${newChannel.rateLimitPerUser ? utils.timeformat(newChannel.rateLimitPerUser) : "0 seconds"}\`\n` } // bitrate if (oldChannel.bitrate !== newChannel.bitrate) { - embed - .setTitle(`Channel bitrate changed for ${newChannel}`) - .setDescription(`\`${oldChannel.bitrate}kbps\` -> \`${newChannel.bitrate}kbps\``) + changed = true; + description += `**Bitrate**: \`${oldChannel.bitrate}kbps\` -> \`${newChannel.bitrate}kbps\`\n` } // User limit for voice if (oldChannel.userLimit !== newChannel.userLimit) { - embed - .setTitle(`Channel user limit changed for ${newChannel}`) - .setDescription(`\`${oldChannel.userLimit} users\` -> \`${newChannel.userLimit} users\``) + changed = true; + description += `**User limit:** \`${oldChannel.userLimit} users\` -> \`${newChannel.userLimit} users\`\n` } // Idk if i am missing anything, will add if it comes to notice const entry = await getAuditLog(auditLogType, newChannel.guild); const executor = entry.targetId === newChannel.id ? entry.executor : "Unknown" - embed.setFooter({ text: `ID: ${newChannel.id} | Executor: ${executor ? executor.username : "Unknown"}` }); - if (!embed.data.title) return; + embed.setFooter({ text: `ID: ${newChannel.id} | Executor: ${executor ? executor.username : "Unknown"}` }).setDescription(description); + if (!changed) return; logChannel.safeSend({ embeds: [embed] }); }; diff --git a/src/events/emojis/emojiCreate.js b/src/events/emojis/emojiCreate.js new file mode 100644 index 000000000..fd924c502 --- /dev/null +++ b/src/events/emojis/emojiCreate.js @@ -0,0 +1,24 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, } = require("discord.js"); + +/** + * Log when emoji is created + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").GuildEmoji} emoji + */ +module.exports = async (client, emoji) => { + const settings = await getSettings(emoji.guild); + if (!settings.logging.emojis) return; + const logChannel = client.channels.cache.get(settings.logging.emojis) + if (!logChannel) return; + const embed = new EmbedBuilder() + .setAuthor({ name: "Emoji Added" }) + .setTitle(`${emoji} was added!`) + .setColor("Green") + .setThumbnail(emoji.imageURL()) + .setDescription(`- **Emoji Name:** ${emoji.name}\n- **Animated:** ${emoji.animated}\n- **Added by**: ${emoji.author}`) + .setFooter({ text: `ID: ${emoji.id}` }) + .setTimestamp(); + + logChannel.send({ embeds: [embed] }) +} \ No newline at end of file diff --git a/src/events/emojis/emojiDelete.js b/src/events/emojis/emojiDelete.js new file mode 100644 index 000000000..9e13ad583 --- /dev/null +++ b/src/events/emojis/emojiDelete.js @@ -0,0 +1,24 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, } = require("discord.js"); + +/** + * Log when emoji is deleted + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").GuildEmoji} emoji + */ +module.exports = async (client, emoji) => { + const settings = await getSettings(emoji.guild); + if (!settings.logging.emojis) return; + const logChannel = client.channels.cache.get(settings.logging.emojis) + if (!logChannel) return; + const embed = new EmbedBuilder() + .setAuthor({ name: "Emoji Deleted" }) + .setTitle(`${emoji} was removed!`) + .setColor("Red") + .setImage(emoji.imageURL()) + .setDescription(`- **Emoji Name:** ${emoji.name}\n- **Animated:** ${emoji.animated}\n- **Added by**: ${emoji.author}`) + .setFooter({ text: `ID: ${emoji.id}` }) + .setTimestamp(); + + logChannel.send({ embeds: [embed] }) +} \ No newline at end of file diff --git a/src/events/emojis/emojiUpdate.js b/src/events/emojis/emojiUpdate.js new file mode 100644 index 000000000..abeaf699a --- /dev/null +++ b/src/events/emojis/emojiUpdate.js @@ -0,0 +1,47 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); + +/** + * Log when emoji is updated + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").GuildEmoji} oldEmoji + * @param {import("discord.js").GuildEmoji} newEmoji + */ +module.exports = async (client, oldEmoji, newEmoji) => { + const settings = await getSettings(newEmoji.guild); + if (!settings.logging.emojis) return; + const logChannel = client.channels.cache.get(settings.logging.emojis) + if (!logChannel) return; + + const auditLog = await newEmoji.guild.fetchAuditLogs({ type: AuditLogEvent.EmojiUpdate, limit: 1 }).then((en) => en.entries.first()); + const executor = auditLog.targetId === newEmoji.id ? auditLog.executor : "Unknown" + const embed = new EmbedBuilder() + .setAuthor({ name: "Emoji Updated" }) + .setTitle(`${newEmoji} was updated`) + .setFooter({ text: `ID: ${newEmoji.id} | Executor: ${executor?.username || "Unknown"}` }) + .setThumbnail(newEmoji.imageURL()) + + // Name changes + if (oldEmoji.name !== newEmoji.name) { + embed.setDescription(` Name: \`${oldEmoji.name}\` -> \`${newEmoji.name}\``) + } + + // Allowed roles changes + if (!oldEmoji.roles.cache.equals(newEmoji.roles.cache)) { + const changes = []; + const oldRoles = oldEmoji.roles.cache; + const newRoles = newEmoji.roles.cache; + newRoles.forEach((role, id) => { + if (!oldRoles.has(id)) changes.push(`+ Added ${role}`); + }); + + oldRoles.forEach((role, id) => { + if (!newRoles.has(id)) changes.push(`\\- Removed ${role}`) + }) + embed.setDescription(`**Allowed Roles Modified:**\n${changes.join("\n")}`) + } + + if (!embed.data.description) return; + logChannel.send({ embeds: [embed] }) + +} \ No newline at end of file diff --git a/src/events/guildAuditLogEntryCreate.js b/src/events/guildAuditLogEntryCreate.js deleted file mode 100644 index 685ac76d6..000000000 --- a/src/events/guildAuditLogEntryCreate.js +++ /dev/null @@ -1,5 +0,0 @@ -const { AuditLogEvent } = require("discord.js") -module.exports = async (client, entry, guild) => { - /* console.log("hi") - console.log(AuditLogEvent[entry.action]) */ -} diff --git a/src/events/invite/inviteCreate.js b/src/events/invite/inviteCreate.js index 713b78888..17c5a4a26 100644 --- a/src/events/invite/inviteCreate.js +++ b/src/events/invite/inviteCreate.js @@ -1,4 +1,6 @@ const { getInviteCache, cacheInvite } = require("@handlers/invite"); +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder } = require("discord.js"); /** * @param {import('@src/structures').BotClient} client @@ -11,4 +13,24 @@ module.exports = async (client, invite) => { if (cachedInvites) { cachedInvites.set(invite.code, cacheInvite(invite, false)); } + const settings = await getSettings(invite.guild); + if (!settings.logging.invites) return; + const logChannel = client.channels.cache.get(settings.logging.invites); + if (!logChannel) return; + const embed = new EmbedBuilder() + .setAuthor({ name: "Invite Created" }) + .setColor("Green") + .setTitle(`Invite created (${invite.code}) for ${invite.channel}`) + .setDescription(`${invite.url}`) + .addFields({ + name: "Expires in", + value: invite.maxAge === 0 ? `Never` : require("@helpers/Utils").timeformat(invite.maxAge) + }, { + name: "Max uses", + value: `${invite.maxUses || "Unlimited"}` + }) + .setTimestamp() + .setFooter({ text: `ID: ${invite.code} | Created by: ${invite.inviter.username}` }) + + logChannel.send({ embeds: [embed] }) }; diff --git a/src/events/invite/inviteDelete.js b/src/events/invite/inviteDelete.js index d14b016dc..ea5f21233 100644 --- a/src/events/invite/inviteDelete.js +++ b/src/events/invite/inviteDelete.js @@ -1,4 +1,6 @@ const { getInviteCache } = require("@handlers/invite"); +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder } = require("discord.js"); /** * @param {import('@src/structures').BotClient} client @@ -11,4 +13,22 @@ module.exports = async (client, invite) => { if (cachedInvites && cachedInvites.get(invite.code)) { cachedInvites.get(invite.code).deletedTimestamp = Date.now(); } + + const settings = await getSettings(invite.guild); + if (!settings.logging.invites) return; + const logChannel = client.channels.cache.get(settings.logging.invites); + if (!logChannel) return; + const embed = new EmbedBuilder() + .setAuthor({ name: "Invite Deleted" }) + .setTitle(`Invite deleted (${invite.code}) for ${invite.channel}`) + .setDescription(`${invite.url}`) + .setColor("Red") + .addFields({ + name: "Total uses", + value: invite.uses?.toString() || "Not Available" + }) + .setTimestamp() + .setFooter({ text: `ID: ${invite.code} | Created by: ${invite.inviter?.username || "Not available"}` }) + + logChannel.send({ embeds: [embed] }) }; diff --git a/src/events/member/guildMemberRemove.js b/src/events/member/guildMemberRemove.js index 5aa0d3890..33a794449 100644 --- a/src/events/member/guildMemberRemove.js +++ b/src/events/member/guildMemberRemove.js @@ -1,6 +1,7 @@ const { inviteHandler, greetingHandler } = require("@src/handlers"); const { getSettings } = require("@schemas/Guild"); - +const { AuditLogEvent } = require("discord.js"); +const { EmbedBuilder } = require("discord.js"); /** * @param {import('@src/structures').BotClient} client * @param {import('discord.js').GuildMember|import('discord.js').PartialGuildMember} member @@ -26,4 +27,20 @@ module.exports = async (client, member) => { // Farewell message greetingHandler.sendFarewell(member, inviterData); + + // Check if member was kicked, not needed for ban since it has its event + const log = await member.guild.fetchAuditLogs({ limit: 5 }); + if (member.user.bot) return; + const possibleLog = log.entries.find((e) => e.action === AuditLogEvent.MemberKick && e.targetId === member.id); + if (possibleLog) { + const logChannel = client.channels.cache.get(settings.logging.members); + const embed = new EmbedBuilder() + .setAuthor({ name: "Member kicked" }) + .setColor("Red") + .setTitle(`${member.displayName} (\`${member.id}\` was kicked.)`) + .setDescription(`Reason: ${possibleLog.reason || "none"}`) + .setTimestamp() + .setFooter({ text: `ID: ${member.id} | Executor: ${possibleLog.executor.username}` }) + logChannel.send({ embeds: [embed] }) + } }; diff --git a/src/events/member/guildMemberUpdate.js b/src/events/member/guildMemberUpdate.js index 7c58c5dce..d74a77fb2 100644 --- a/src/events/member/guildMemberUpdate.js +++ b/src/events/member/guildMemberUpdate.js @@ -9,12 +9,14 @@ const { EmbedBuilder, AuditLogEvent, time } = require("discord.js"); module.exports = async (client, oldMember, newMember) => { if (oldMember.partial) return if (!newMember.guild) return; + if (!newMember.user.bot) return; const settings = await getSettings(newMember.guild); if (!settings.logging.members) return; const logChannel = client.channels.cache.get(settings.logging.members); let embed = new EmbedBuilder().setColor("Green").setTimestamp(); + // Onboarding - if (oldMember.pending && !newMember.pending) { + if (newMember.guild.features.includes("WELCOME_SCREEN_ENABLED") && oldMember.pending && !newMember.pending) { embed .setAuthor({ name: "Member Completed Onboarding" }) .setThumbnail(newMember.user.displayAvatarURL()) @@ -44,10 +46,10 @@ module.exports = async (client, oldMember, newMember) => { const rolesMessage = []; - addedRoles.forEach(role => rolesMessage.push(`+ ${role}`)); - removedRoles.forEach(role => rolesMessage.push(`- ${role}`)); + addedRoles.forEach(role => rolesMessage.push(`+ Added ${role}`)); + removedRoles.forEach(role => rolesMessage.push(`\\- Removed ${role}`)); embed - .setAuthor({ name: "Role Updated" }) + .setAuthor({ name: "Member Role Updated" }) .setThumbnail(newMember.user.displayAvatarURL()) .addFields( { name: "User", value: newMember.toString(), inline: true }, @@ -57,7 +59,7 @@ module.exports = async (client, oldMember, newMember) => { } // Timeout - if (!oldMember.isCommunicationDisabled() && !newMember.isCommunicationDisabled()) { + if (!oldMember.isCommunicationDisabled() && newMember.isCommunicationDisabled()) { const auditLog = await newMember.guild.fetchAuditLogs({ type: AuditLogEvent.MemberUpdate, limit: 1 }); const entry = auditLog.entries.first(); const disabledTill = new Date(entry.changes[0].new); @@ -99,7 +101,7 @@ module.exports = async (client, oldMember, newMember) => { .setFooter({ text: `ID: ${newMember.id}` }); } - // Voice Mute Expires + // Voice Mute Removed if (oldMember.voice?.serverMute && !newMember.voice?.serverMute) { embed .setAuthor({ name: "Member Server Mute Removed for Voice Channels" }) @@ -111,8 +113,7 @@ module.exports = async (client, oldMember, newMember) => { }) .setFooter({ text: `ID: ${newMember.id}` }) } - - if (embed.fields.length === 0) return; + if (embed.data.fields.length === 0) return; logChannel.send({ embeds: [embed] }) }; diff --git a/src/events/message/messageUpdate.js b/src/events/message/messageUpdate.js index ea6ac7b13..193ac7481 100644 --- a/src/events/message/messageUpdate.js +++ b/src/events/message/messageUpdate.js @@ -1,3 +1,4 @@ +const { getSettings } = require("@root/src/database/schemas/Guild"); const { EmbedBuilder } = require("discord.js"); /** * @param {import('@src/structures').BotClient} client @@ -8,9 +9,10 @@ module.exports = async (client, oldMessage, newMessage) => { if (oldMessage.partial) return; const { content } = oldMessage; const { author } = newMessage; - const logChannel = client.channels.cache.get("1202855179377057822"); if (newMessage.author.bot) return; - if (newMessage.guild.id !== "852141490105090059") return; + const settings = await getSettings(newMessage.guild); + if (!settings.logging.messages) return; + const logChannel = client.channels.cache.get(settings.logging.messages) if (!logChannel) return; const logEmbed = new EmbedBuilder() .setAuthor({ name: "Message Edited" }) diff --git a/src/events/roles/roleCreate.js b/src/events/roles/roleCreate.js new file mode 100644 index 000000000..f80017002 --- /dev/null +++ b/src/events/roles/roleCreate.js @@ -0,0 +1,27 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); + +/** + * + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").Role} role + */ +module.exports = async (client, role) => { + const settings = await getSettings(role.guild); + if (!settings.logging.roles) return; + const logChannel = client.channels.cache.get(settings.logging.roles); + if (!logChannel) return; + + // return if role was created by a bot (bot's role) + if (role.managed) return; + const auditLog = await role.guild.fetchAuditLogs({ type: AuditLogEvent.RoleCreate, limit: 1 }).then((en) => en.entries.first()); + const executor = auditLog.targetId === role.id ? auditLog.executor : null + const embed = new EmbedBuilder() + .setAuthor({ name: "Role Created" }) + .setDescription(`## ${role} was created!`) + .setColor("Green") + .setTimestamp() + .setFooter({ text: `ID: ${role.id} | Executor: ${executor?.username || "Unknown"}` }); + + logChannel.send({ embeds: [embed] }) +} \ No newline at end of file diff --git a/src/events/roles/roleDelete.js b/src/events/roles/roleDelete.js new file mode 100644 index 000000000..68e5bb727 --- /dev/null +++ b/src/events/roles/roleDelete.js @@ -0,0 +1,27 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); + +/** + * Log when role is deleted + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").Role} role + */ +module.exports = async (client, role) => { + const settings = await getSettings(role.guild); + if (!settings.logging.roles) return; + const logChannel = client.channels.cache.get(settings.logging.roles); + if (!logChannel) return; + + // return if role was created by a bot (bot's role) + if (role.managed) return; + const auditLog = await role.guild.fetchAuditLogs({ type: AuditLogEvent.RoleDelete, limit: 1 }).then((en) => en.entries.first()); + const executor = auditLog.targetId === role.id ? auditLog.executor : null + const embed = new EmbedBuilder() + .setAuthor({ name: "Role Deleted" }) + .setTitle(`\`${role.name}\` was deleted!`) + .setColor("Red") + .setTimestamp() + .setFooter({ text: `ID: ${role.id} | Executor: ${executor?.username || "Unknown"}` }); + + logChannel.send({ embeds: [embed] }) +} \ No newline at end of file diff --git a/src/events/roles/roleUpdate.js b/src/events/roles/roleUpdate.js new file mode 100644 index 000000000..334084155 --- /dev/null +++ b/src/events/roles/roleUpdate.js @@ -0,0 +1,89 @@ +const { getSettings } = require("@schemas/Guild"); +const { EmbedBuilder, AuditLogEvent } = require("discord.js"); +/** + * Log when a role is updated + * @param {import("@structures/BotClient")} client + * @param {import('discord.js').Role} oldRole + * @param {import("discord.js").Role} newRole + */ +module.exports = async (client, oldRole, newRole) => { + const settings = await getSettings(newRole.guild) + if (!settings.logging.roles) return; + const logChannel = client.channels.cache.get(settings.logging.roles); + if (!logChannel) return; + + const auditLog = await newRole.guild.fetchAuditLogs({ type: AuditLogEvent.RoleUpdate, limit: 1 }).then((en) => en.entries.first()); + const executor = auditLog.targetId === newRole.id ? auditLog.executor : null + + const embed = new EmbedBuilder() + .setAuthor({ name: "Role updated" }) + .setTimestamp() + .setColor("Green") + .setFooter({ text: `ID: ${newRole.id} | Executor: ${executor?.username || "Unknown"}` }) + let description = `## Role Changes for ${newRole}\n` + + // Name + + if (oldRole.name !== newRole.name) { + description += `**Name Changed**: \`${oldRole.name}\` -> \`${newRole.name}\`\n` + } + + // Position + if (oldRole.rawPosition !== newRole.rawPosition) { + description += `**Position Changed**: \`${oldRole.rawPosition}\` -> \`${newRole.rawPosition}\`\n` + } + + // Icon change + if (oldRole.icon !== newRole.icon) { + embed + if (newRole.icon) { + embed.setThumbnail(newRole.iconURL()) + .setImage(newRole.iconURL()) + description += `**Role Icon Changed**\n` + } else if (oldRole.icon && !newRole.icon) { + description += `Icon Removed\n - Old icon: [image](${oldRole.iconURL()})\n` + } + } + + // Color changed + if (oldRole.hexColor !== newRole.hexColor) { + description += `**Color Changed**: \`${oldRole.hexColor || "none"}\` -> \`${newRole.hexColor || "none"}\`\n` + } + + // Permissions changes + const changes = getDifference(oldRole.permissions, newRole.permissions); + if (changes.length > 0) { + embed + description = `## Role permissions changed for ${newRole}\n` + changes.join("\n") + } + + // Hoisted + if (oldRole.hoist !== newRole.hoist) { + description += `**Hoist Changed** (whether to display seperately in members list): \`${oldRole.hoist}\` -> \`${newRole.hoist}\`\n` + } + + embed.setDescription(description) + logChannel.send({ embeds: [embed] }) +} + +/** + * Get permission differences + * @param {import("discord.js").PermissionsBitField} oldPerm + * @param {import("discord.js").PermissionsBitField} newPerm + * @returns {string[]} + */ +const getDifference = (oldPerm, newPerm) => { + const oldPermissions = oldPerm.toArray() + const newPermissions = newPerm.toArray() + const changes = [] + // Added + newPermissions.forEach((perm) => { + if (!oldPermissions.includes(perm)) changes.push(`+ **${perm}**`) + }) + + // Removed + oldPermissions.forEach((perm) => { + if (!newPermissions.includes(perm)) changes.push(`\\- **${perm}**`) + }) + return changes; +} \ No newline at end of file diff --git a/src/events/userUpdate.js b/src/events/userUpdate.js new file mode 100644 index 000000000..637f9249d --- /dev/null +++ b/src/events/userUpdate.js @@ -0,0 +1,54 @@ +const { EmbedBuilder } = require("discord.js"); +const { getSettings } = require("../database/schemas/Guild"); + +/** + * + * @param {import("@structures/BotClient")} client + * @param {import("discord.js").User} oldUser + * @param {import("discord.js").User} newUser + */ +module.exports = async (client, oldUser, newUser) => { + if (newUser.bot) return; + if (oldUser.partial) return; + const guilds = client.guilds.cache.values(); + const commonGuilds = [] + for (const g of guilds) { + // Check member + const member = await g.members.fetch(newUser.id).catch(() => null) + if (member) commonGuilds.push(g) + } + + const embed = new EmbedBuilder() + .setAuthor({ name: "Member Updated" }) + .setColor("Green") + .setThumbnail(newUser.displayAvatarURL()) + .setFooter({ text: `ID: ${newUser.id}` }) + // Avatar + if (oldUser.avatar !== newUser.avatar) { + embed + .setTitle(`${newUser} (${newUser.username}) updated their avatar!`) + .setImage(newUser.displayAvatarURL()) + } + + // User + if (oldUser.username !== oldUser.username) { + embed + .setTitle(`${newUser} (${newUser.username}) updated their username!`) + .setDescription(`\`${oldUser.username}\` -> \`${newUser.username}\``) + } + + // Global name + if (oldUser.globalName !== newUser.globalName) { + embed.setTitle(`${newUser} (${newUser.username}) updated their global name!`) + .setDescription(`\`${oldUser.globalName || "none"}\` -> \`${newUser.globalName || "none"}\``) + } + + for (const g of commonGuilds) { + const settings = await getSettings(g); + if (!settings.logging.members) continue + const channel = client.channels.cache.get(settings.logging.members); + if (!channel) continue; + if (!embed.data.title) continue; + channel.send({ embeds: [embed] }) + } +} \ No newline at end of file diff --git a/src/events/voice/voiceStateUpdate.js b/src/events/voice/voiceStateUpdate.js index 9134eadee..2c6fce53b 100644 --- a/src/events/voice/voiceStateUpdate.js +++ b/src/events/voice/voiceStateUpdate.js @@ -50,7 +50,7 @@ module.exports = async (client, oldState, newState) => { } // Member moved - if (oldState.channel.id !== newState.channel.id) { + if (oldState.channel && newState.channel && oldState.channel.id !== newState.channel.id) { embed .setAuthor({ name: "Member Moved Voice Channel" }) .setThumbnail(newState.member.user.displayAvatarURL()) @@ -76,5 +76,5 @@ module.exports = async (client, oldState, newState) => { } - if (embed.fields.length) logChannel.send({ embeds: [embed] }) + if (embed.data.fields.length) logChannel.send({ embeds: [embed] }) }; diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index 5f25aa5f8..53fc0e899 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -29,7 +29,8 @@ module.exports = class BotClient extends Client { GatewayIntentBits.GuildPresences, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildVoiceStates, - GatewayIntentBits.GuildModeration + GatewayIntentBits.GuildModeration, + GatewayIntentBits.GuildEmojisAndStickers ], partials: [Partials.User, Partials.Message, Partials.Reaction], allowedMentions: { From f14854fd1a2fb2a4bb0f81cfd8132446d2c6d45e Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:13:04 +0530 Subject: [PATCH 04/11] some fixes --- src/commands/admin/logging.js | 19 +++++++++++++++++++ src/events/bans/guildBanAdd.js | 4 ++-- src/events/bans/guildBanRemove.js | 4 ++-- src/events/member/guildMemberRemove.js | 3 +-- src/events/member/guildMemberUpdate.js | 4 ++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/commands/admin/logging.js b/src/commands/admin/logging.js index a696af721..bff3e0ddf 100644 --- a/src/commands/admin/logging.js +++ b/src/commands/admin/logging.js @@ -16,6 +16,10 @@ module.exports = { trigger: "channels <#channel|off>", description: "enable logging for channel updates", }, + { + trigger: "roles <#channel|off>", + description: "enable logging for role updates", + }, { trigger: "members <#channel|off>", description: "enable logging for member updates", @@ -65,6 +69,20 @@ module.exports = { }, ], }, + { + name: "roles", + description: "enable logging for role updates", + type: ApplicationCommandOptionType.Subcommand, + options: [ + { + name: "channel", + description: "channel to send logs (leave empty to disable)", + type: ApplicationCommandOptionType.Channel, + required: false, + channelTypes: [ChannelType.GuildText], + }, + ], + }, { name: "members", description: "enable logging for members updates", @@ -224,6 +242,7 @@ function getLogStatus(settings, client) { embeds: [ new EmbedBuilder() .setTitle(`Logging Status`) + .setColor(`Green`) .setDescription(formatted) ] } diff --git a/src/events/bans/guildBanAdd.js b/src/events/bans/guildBanAdd.js index fde4e1afd..ec0b89600 100644 --- a/src/events/bans/guildBanAdd.js +++ b/src/events/bans/guildBanAdd.js @@ -18,9 +18,9 @@ module.exports = async (client, ban) => { .setAuthor({ name: "Member Banned" }) .setTitle(`${ban.user} (${ban.user.username}) was banned!`) .setColor("Red") - .setDescription(`Reason: ${ban.reason || "None"}`) + .setDescription(`Reason: ${auditLog.reason || "None"}`) .setFooter({ text: `ID: ${ban.user.id} | Executor: ${executor?.username || "Unknown"}` }) .setThumbnail(ban.user.displayAvatarURL()); - logChannel.send({ embeds: [embed] }) + await logChannel.send({ embeds: [embed] }) } diff --git a/src/events/bans/guildBanRemove.js b/src/events/bans/guildBanRemove.js index e1dd57fef..afb70a969 100644 --- a/src/events/bans/guildBanRemove.js +++ b/src/events/bans/guildBanRemove.js @@ -18,9 +18,9 @@ module.exports = async (client, ban) => { .setAuthor({ name: "Member Unbanned" }) .setTitle(`${ban.user} (${ban.user.username}) was unbanned!`) .setColor("Green") - .setDescription(`Reason: ${ban.reason || "None"}`) + .setDescription(`Ban Reason: ${ban.reason || "Not Available"}\n Unban Reason: ${auditLog.reason || "Not"}`) .setFooter({ text: `ID: ${ban.user.id} | Executor: ${executor?.username || "Unknown"}` }) .setThumbnail(ban.user.displayAvatarURL()); - logChannel.send({ embeds: [embed] }) + await logChannel.send({ embeds: [embed] }) } diff --git a/src/events/member/guildMemberRemove.js b/src/events/member/guildMemberRemove.js index 33a794449..fba63050c 100644 --- a/src/events/member/guildMemberRemove.js +++ b/src/events/member/guildMemberRemove.js @@ -30,7 +30,6 @@ module.exports = async (client, member) => { // Check if member was kicked, not needed for ban since it has its event const log = await member.guild.fetchAuditLogs({ limit: 5 }); - if (member.user.bot) return; const possibleLog = log.entries.find((e) => e.action === AuditLogEvent.MemberKick && e.targetId === member.id); if (possibleLog) { const logChannel = client.channels.cache.get(settings.logging.members); @@ -41,6 +40,6 @@ module.exports = async (client, member) => { .setDescription(`Reason: ${possibleLog.reason || "none"}`) .setTimestamp() .setFooter({ text: `ID: ${member.id} | Executor: ${possibleLog.executor.username}` }) - logChannel.send({ embeds: [embed] }) + await logChannel.send({ embeds: [embed] }) } }; diff --git a/src/events/member/guildMemberUpdate.js b/src/events/member/guildMemberUpdate.js index d74a77fb2..292171605 100644 --- a/src/events/member/guildMemberUpdate.js +++ b/src/events/member/guildMemberUpdate.js @@ -113,7 +113,7 @@ module.exports = async (client, oldMember, newMember) => { }) .setFooter({ text: `ID: ${newMember.id}` }) } - if (embed.data.fields.length === 0) return; - logChannel.send({ embeds: [embed] }) + if (embed.data.fields?.length === 0) return; + await logChannel.send({ embeds: [embed] }) }; From 3c7c333ea0cadd90f31b3a6577db798460a0fe85 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:13:36 +0530 Subject: [PATCH 05/11] removed owner id --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index 815bb7cec..ea69a13a1 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,5 @@ module.exports = { - OWNER_IDS: ["851588007697580033"], // Bot owner ID's + OWNER_IDS: [""], // Bot owner ID's SUPPORT_SERVER: "", // Your bot support server PREFIX_COMMANDS: { ENABLED: true, // Enable/Disable prefix commands From 8fe6eff87dec31cea8a8bd29f5f4611750bbecf3 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:14:39 +0530 Subject: [PATCH 06/11] revert config.js --- config.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config.js b/config.js index ea69a13a1..f0efe61db 100644 --- a/config.js +++ b/config.js @@ -6,9 +6,9 @@ module.exports = { DEFAULT_PREFIX: "!", // Default prefix for the bot }, INTERACTIONS: { - SLASH: true, // Should the interactions be enabled - CONTEXT: true, // Should contexts be enabled - GLOBAL: true, // Should the interactions be registered globally + SLASH: false, // Should the interactions be enabled + CONTEXT: false, // Should contexts be enabled + GLOBAL: false, // Should the interactions be registered globally TEST_GUILD_ID: "xxxxxxxxxxx", // Guild ID where the interactions should be registered. [** Test you commands here first **] }, EMBED_COLORS: { @@ -30,7 +30,7 @@ module.exports = { // PLUGINS AUTOMOD: { - ENABLED: true, + ENABLED: false, LOG_EMBED: "#36393F", DM_EMBED: "#36393F", }, @@ -43,7 +43,7 @@ module.exports = { }, ECONOMY: { - ENABLED: true, + ENABLED: false, CURRENCY: "₪", DAILY_COINS: 100, // coins to be received by daily command MIN_BEG_AMOUNT: 100, // minimum coins to be received when beg command is used @@ -81,11 +81,11 @@ module.exports = { }, INVITE: { - ENABLED: true, + ENABLED: false, }, MODERATION: { - ENABLED: true, + ENABLED: false, EMBED_COLORS: { TIMEOUT: "#102027", UNTIMEOUT: "#4B636E", @@ -131,4 +131,4 @@ module.exports = { CREATE_EMBED: "#068ADD", CLOSE_EMBED: "#068ADD", }, -}; +}; \ No newline at end of file From 0c0346b2e44e6c6ee257bc5ebb90aecf170f12b5 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:28:24 +0530 Subject: [PATCH 07/11] revert --- src/helpers/Utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/helpers/Utils.js b/src/helpers/Utils.js index 3307807a5..3768d76a9 100644 --- a/src/helpers/Utils.js +++ b/src/helpers/Utils.js @@ -71,10 +71,10 @@ module.exports = class Utils { const minutes = Math.floor((timeInSeconds % 3600) / 60); const seconds = Math.round(timeInSeconds % 60); return ( - (days > 0 ? `${days} days` : "") + - (hours > 0 ? `, ${hours} hours` : "") + - (minutes > 0 ? `, ${minutes} minutes` : "") + - (seconds > 0 ? `, ${seconds} seconds` : "") + (days > 0 ? `${days} days, ` : "") + + (hours > 0 ? `${hours} hours, ` : "") + + (minutes > 0 ? `${minutes} minutes, ` : "") + + (seconds > 0 ? `${seconds} seconds` : "") ); } From f5e1538c3d2fdfa87248f5e685aed15bdecd758a Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Tue, 2 Jul 2024 00:52:42 +0530 Subject: [PATCH 08/11] fix guildMemberUpdate, and check for nullability of "logging" --- src/events/bans/guildBanAdd.js | 2 +- src/events/bans/guildBanRemove.js | 2 +- src/events/channel/channelCreate.js | 2 +- src/events/channel/channelDelete.js | 2 +- src/events/channel/channelUpdate.js | 2 +- src/events/emojis/emojiCreate.js | 2 +- src/events/emojis/emojiDelete.js | 2 +- src/events/emojis/emojiUpdate.js | 2 +- src/events/invite/inviteCreate.js | 2 +- src/events/invite/inviteDelete.js | 2 +- src/events/member/guildMemberRemove.js | 1 + src/events/member/guildMemberUpdate.js | 4 ++-- src/events/message/messageDelete.js | 2 +- src/events/message/messageDeleteBulk.js | 2 +- src/events/message/messageUpdate.js | 2 +- src/events/roles/roleCreate.js | 2 +- src/events/roles/roleDelete.js | 2 +- src/events/roles/roleUpdate.js | 2 +- src/events/userUpdate.js | 2 +- src/events/voice/voiceStateUpdate.js | 2 +- 20 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/events/bans/guildBanAdd.js b/src/events/bans/guildBanAdd.js index ec0b89600..c9c8f1035 100644 --- a/src/events/bans/guildBanAdd.js +++ b/src/events/bans/guildBanAdd.js @@ -9,7 +9,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); module.exports = async (client, ban) => { const settings = await getSettings(ban.guild) - if (!settings.logging.bans) return; + if (!settings.logging?.bans) return; const logChannel = client.channels.cache.get(settings.logging.bans); if (!logChannel) return; const auditLog = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanAdd, limit: 1 }).then((en) => en.entries.first()) diff --git a/src/events/bans/guildBanRemove.js b/src/events/bans/guildBanRemove.js index afb70a969..ee0fbf112 100644 --- a/src/events/bans/guildBanRemove.js +++ b/src/events/bans/guildBanRemove.js @@ -9,7 +9,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); module.exports = async (client, ban) => { const settings = await getSettings(ban.guild) - if (!settings.logging.bans) return; + if (!settings.logging?.bans) return; const logChannel = client.channels.cache.get(settings.logging.bans); if (!logChannel) return; const auditLog = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanRemove, limit: 1 }).then((en) => en.entries.first()) diff --git a/src/events/channel/channelCreate.js b/src/events/channel/channelCreate.js index 8825f989b..58d4f868c 100644 --- a/src/events/channel/channelCreate.js +++ b/src/events/channel/channelCreate.js @@ -6,7 +6,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); */ module.exports = async (client, channel) => { const settings = await getSettings(channel.guild); - if (!settings.logging.channels) return; + if (!settings.logging?.channels) return; const logChannel = client.channels.cache.get(settings.logging.channels); if (!logChannel) return; const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelCreate, limit: 1 }); diff --git a/src/events/channel/channelDelete.js b/src/events/channel/channelDelete.js index 1d69ca16a..8370a14f7 100644 --- a/src/events/channel/channelDelete.js +++ b/src/events/channel/channelDelete.js @@ -6,7 +6,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); */ module.exports = async (client, channel) => { const settings = await getSettings(channel.guild); - if (!settings.logging.channels) return; + if (!settings.logging?.channels) return; const logChannel = client.channels.cache.get(settings.logging.channels); if (!logChannel) return; const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.ChannelDelete, limit: 1 }); diff --git a/src/events/channel/channelUpdate.js b/src/events/channel/channelUpdate.js index b6b97a3a8..4bb92321b 100644 --- a/src/events/channel/channelUpdate.js +++ b/src/events/channel/channelUpdate.js @@ -8,7 +8,7 @@ const utils = require("@helpers/Utils") */ module.exports = async (client, oldChannel, newChannel) => { const settings = await getSettings(newChannel.guild); - if (!settings.logging.channels) return; + if (!settings.logging?.channels) return; const logChannel = client.channels.cache.get(settings.logging.channels); if (!logChannel) return; const embed = new EmbedBuilder() diff --git a/src/events/emojis/emojiCreate.js b/src/events/emojis/emojiCreate.js index fd924c502..775d0126e 100644 --- a/src/events/emojis/emojiCreate.js +++ b/src/events/emojis/emojiCreate.js @@ -8,7 +8,7 @@ const { EmbedBuilder, } = require("discord.js"); */ module.exports = async (client, emoji) => { const settings = await getSettings(emoji.guild); - if (!settings.logging.emojis) return; + if (!settings.logging?.emojis) return; const logChannel = client.channels.cache.get(settings.logging.emojis) if (!logChannel) return; const embed = new EmbedBuilder() diff --git a/src/events/emojis/emojiDelete.js b/src/events/emojis/emojiDelete.js index 9e13ad583..4aadb2813 100644 --- a/src/events/emojis/emojiDelete.js +++ b/src/events/emojis/emojiDelete.js @@ -8,7 +8,7 @@ const { EmbedBuilder, } = require("discord.js"); */ module.exports = async (client, emoji) => { const settings = await getSettings(emoji.guild); - if (!settings.logging.emojis) return; + if (!settings.logging?.emojis) return; const logChannel = client.channels.cache.get(settings.logging.emojis) if (!logChannel) return; const embed = new EmbedBuilder() diff --git a/src/events/emojis/emojiUpdate.js b/src/events/emojis/emojiUpdate.js index abeaf699a..b0e4b0ec1 100644 --- a/src/events/emojis/emojiUpdate.js +++ b/src/events/emojis/emojiUpdate.js @@ -9,7 +9,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); */ module.exports = async (client, oldEmoji, newEmoji) => { const settings = await getSettings(newEmoji.guild); - if (!settings.logging.emojis) return; + if (!settings.logging?.emojis) return; const logChannel = client.channels.cache.get(settings.logging.emojis) if (!logChannel) return; diff --git a/src/events/invite/inviteCreate.js b/src/events/invite/inviteCreate.js index 17c5a4a26..e00b29f6b 100644 --- a/src/events/invite/inviteCreate.js +++ b/src/events/invite/inviteCreate.js @@ -14,7 +14,7 @@ module.exports = async (client, invite) => { cachedInvites.set(invite.code, cacheInvite(invite, false)); } const settings = await getSettings(invite.guild); - if (!settings.logging.invites) return; + if (!settings.logging?.invites) return; const logChannel = client.channels.cache.get(settings.logging.invites); if (!logChannel) return; const embed = new EmbedBuilder() diff --git a/src/events/invite/inviteDelete.js b/src/events/invite/inviteDelete.js index ea5f21233..c3b451869 100644 --- a/src/events/invite/inviteDelete.js +++ b/src/events/invite/inviteDelete.js @@ -15,7 +15,7 @@ module.exports = async (client, invite) => { } const settings = await getSettings(invite.guild); - if (!settings.logging.invites) return; + if (!settings.logging?.invites) return; const logChannel = client.channels.cache.get(settings.logging.invites); if (!logChannel) return; const embed = new EmbedBuilder() diff --git a/src/events/member/guildMemberRemove.js b/src/events/member/guildMemberRemove.js index fba63050c..352a84be3 100644 --- a/src/events/member/guildMemberRemove.js +++ b/src/events/member/guildMemberRemove.js @@ -32,6 +32,7 @@ module.exports = async (client, member) => { const log = await member.guild.fetchAuditLogs({ limit: 5 }); const possibleLog = log.entries.find((e) => e.action === AuditLogEvent.MemberKick && e.targetId === member.id); if (possibleLog) { + if (settings.logging?.members) return; const logChannel = client.channels.cache.get(settings.logging.members); const embed = new EmbedBuilder() .setAuthor({ name: "Member kicked" }) diff --git a/src/events/member/guildMemberUpdate.js b/src/events/member/guildMemberUpdate.js index 292171605..b1fcece87 100644 --- a/src/events/member/guildMemberUpdate.js +++ b/src/events/member/guildMemberUpdate.js @@ -9,9 +9,9 @@ const { EmbedBuilder, AuditLogEvent, time } = require("discord.js"); module.exports = async (client, oldMember, newMember) => { if (oldMember.partial) return if (!newMember.guild) return; - if (!newMember.user.bot) return; + if (newMember.user.bot) return; const settings = await getSettings(newMember.guild); - if (!settings.logging.members) return; + if (!settings.logging?.members) return; const logChannel = client.channels.cache.get(settings.logging.members); let embed = new EmbedBuilder().setColor("Green").setTimestamp(); diff --git a/src/events/message/messageDelete.js b/src/events/message/messageDelete.js index 9d7b48bc4..6d0599e61 100644 --- a/src/events/message/messageDelete.js +++ b/src/events/message/messageDelete.js @@ -48,7 +48,7 @@ module.exports = async (client, message) => { } } else { const { content, author } = message; - if (!settings.logging.messages) return + if (!settings.logging?.messages) return const logChannel = client.channels.cache.get(settings.logging.messages); const entry = await message.guild diff --git a/src/events/message/messageDeleteBulk.js b/src/events/message/messageDeleteBulk.js index 3bbf5a7fc..10c32a643 100644 --- a/src/events/message/messageDeleteBulk.js +++ b/src/events/message/messageDeleteBulk.js @@ -9,7 +9,7 @@ const { getSettings } = require("@schemas/Guild"); module.exports = async (client, messages, channel) => { const settings = await getSettings(channel.guild); - if (!settings.logging.messages) return + if (!settings.logging?.messages) return const logChannel = client.channels.cache.get(settings.logging.messages); const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.MessageBulkDelete, limit: 1 }); const entry = auditLog.entries.first(); diff --git a/src/events/message/messageUpdate.js b/src/events/message/messageUpdate.js index 193ac7481..fe3a5689c 100644 --- a/src/events/message/messageUpdate.js +++ b/src/events/message/messageUpdate.js @@ -11,7 +11,7 @@ module.exports = async (client, oldMessage, newMessage) => { const { author } = newMessage; if (newMessage.author.bot) return; const settings = await getSettings(newMessage.guild); - if (!settings.logging.messages) return; + if (!settings.logging?.messages) return; const logChannel = client.channels.cache.get(settings.logging.messages) if (!logChannel) return; const logEmbed = new EmbedBuilder() diff --git a/src/events/roles/roleCreate.js b/src/events/roles/roleCreate.js index f80017002..73a04a0ab 100644 --- a/src/events/roles/roleCreate.js +++ b/src/events/roles/roleCreate.js @@ -8,7 +8,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); */ module.exports = async (client, role) => { const settings = await getSettings(role.guild); - if (!settings.logging.roles) return; + if (!settings.logging?.roles) return; const logChannel = client.channels.cache.get(settings.logging.roles); if (!logChannel) return; diff --git a/src/events/roles/roleDelete.js b/src/events/roles/roleDelete.js index 68e5bb727..2ac618617 100644 --- a/src/events/roles/roleDelete.js +++ b/src/events/roles/roleDelete.js @@ -8,7 +8,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); */ module.exports = async (client, role) => { const settings = await getSettings(role.guild); - if (!settings.logging.roles) return; + if (!settings.logging?.roles) return; const logChannel = client.channels.cache.get(settings.logging.roles); if (!logChannel) return; diff --git a/src/events/roles/roleUpdate.js b/src/events/roles/roleUpdate.js index 334084155..d34ccd182 100644 --- a/src/events/roles/roleUpdate.js +++ b/src/events/roles/roleUpdate.js @@ -8,7 +8,7 @@ const { EmbedBuilder, AuditLogEvent } = require("discord.js"); */ module.exports = async (client, oldRole, newRole) => { const settings = await getSettings(newRole.guild) - if (!settings.logging.roles) return; + if (!settings.logging?.roles) return; const logChannel = client.channels.cache.get(settings.logging.roles); if (!logChannel) return; diff --git a/src/events/userUpdate.js b/src/events/userUpdate.js index 637f9249d..05da9ef99 100644 --- a/src/events/userUpdate.js +++ b/src/events/userUpdate.js @@ -45,7 +45,7 @@ module.exports = async (client, oldUser, newUser) => { for (const g of commonGuilds) { const settings = await getSettings(g); - if (!settings.logging.members) continue + if (!settings.logging?.members) continue const channel = client.channels.cache.get(settings.logging.members); if (!channel) continue; if (!embed.data.title) continue; diff --git a/src/events/voice/voiceStateUpdate.js b/src/events/voice/voiceStateUpdate.js index 2c6fce53b..2f986df82 100644 --- a/src/events/voice/voiceStateUpdate.js +++ b/src/events/voice/voiceStateUpdate.js @@ -32,7 +32,7 @@ module.exports = async (client, oldState, newState) => { // Logging const settings = await getSettings(newState.guild); - if (!settings.logging.voice) return; + if (!settings.logging?.voice) return; const logChannel = client.channels.cache.get(settings.logging.voice); const embed = new EmbedBuilder().setColor("Green").setTimestamp(); From 70736e6ae51f65c3f3400b5a2a8d7b47777df47b Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Tue, 2 Jul 2024 01:45:01 +0530 Subject: [PATCH 09/11] untimeout log --- src/events/member/guildMemberUpdate.js | 55 ++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/src/events/member/guildMemberUpdate.js b/src/events/member/guildMemberUpdate.js index b1fcece87..7f54e7595 100644 --- a/src/events/member/guildMemberUpdate.js +++ b/src/events/member/guildMemberUpdate.js @@ -1,6 +1,8 @@ const { getSettings } = require("@schemas/Guild"); const { EmbedBuilder, AuditLogEvent, time } = require("discord.js"); + +const timeOutMap = new Map() /** * @param {import('@src/structures').BotClient} client * @param {import('discord.js').GuildMember|import('discord.js').PartialGuildMember} oldMember @@ -14,9 +16,10 @@ module.exports = async (client, oldMember, newMember) => { if (!settings.logging?.members) return; const logChannel = client.channels.cache.get(settings.logging.members); let embed = new EmbedBuilder().setColor("Green").setTimestamp(); - + let changed = false; // Onboarding if (newMember.guild.features.includes("WELCOME_SCREEN_ENABLED") && oldMember.pending && !newMember.pending) { + changed = true; embed .setAuthor({ name: "Member Completed Onboarding" }) .setThumbnail(newMember.user.displayAvatarURL()) @@ -28,6 +31,7 @@ module.exports = async (client, oldMember, newMember) => { // Nickname change if (oldMember.nickname !== newMember.nickname) { + changed = true; embed .setAuthor({ name: "Nickname Changed" }) .setThumbnail(newMember.user.displayAvatarURL()) @@ -41,6 +45,7 @@ module.exports = async (client, oldMember, newMember) => { // Role changes if (!oldMember.roles.cache.every(role => newMember.roles.cache.has(role.id)) || (oldMember.roles.cache.size !== newMember.roles.cache.size)) { + changed = true; const addedRoles = newMember.roles.cache.filter(role => !oldMember.roles.cache.has(role.id)); const removedRoles = oldMember.roles.cache.filter(role => !newMember.roles.cache.has(role.id)); @@ -60,6 +65,7 @@ module.exports = async (client, oldMember, newMember) => { // Timeout if (!oldMember.isCommunicationDisabled() && newMember.isCommunicationDisabled()) { + changed = true; const auditLog = await newMember.guild.fetchAuditLogs({ type: AuditLogEvent.MemberUpdate, limit: 1 }); const entry = auditLog.entries.first(); const disabledTill = new Date(entry.changes[0].new); @@ -71,14 +77,53 @@ module.exports = async (client, oldMember, newMember) => { .addFields( { name: "User", value: newMember.toString(), inline: true }, { name: "Till", value: time(disabledTill, "F"), inline: true }, - { name: "Executor", value: executor.toString(), inline: true } + { name: "Reason", value: `${entry.reason || "none"}` }, + { name: "Executor", value: executor.toString() } ) - .setFooter({ text: `ID: ${newMember.id}` }) + .setFooter({ text: `ID: ${newMember.id}` }); + // add a timeout to send a log when timeout expires + timeOutMap.set(newMember.id, setTimeout(() => { + const e = new EmbedBuilder() + .setAuthor({ name: "Member Untimeout" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .setColor("Green") + .addFields( + { name: "User", value: newMember.toString(), inline: true }, + { name: "Reson", value: "Timeout Expired", inline: true }, + { name: "Timed-out by", value: executor.toString() } + ) + .setFooter({ text: `ID: ${newMember.id}` }); + logChannel.send({ embeds: [e] }) + timeOutMap.delete(newMember.id) + }, disabledTill.getTime() - Date.now())) } + // Untimeout + if (oldMember.isCommunicationDisabled() && !newMember.isCommunicationDisabled()) { + changed = true; + const timeouTinterval = timeOutMap.get(newMember.id); + if (timeouTinterval) { + clearTimeout(timeouTinterval); + timeOutMap.delete(newMember.id) + } + const auditLog = await newMember.guild.fetchAuditLogs({ type: AuditLogEvent.MemberUpdate, limit: 1 }); + const entry = auditLog.entries.first(); + const executor = entry.executor; + + embed + .setAuthor({ name: "Member Untimeout" }) + .setThumbnail(newMember.user.displayAvatarURL()) + .setColor("Green") + .addFields( + { name: "User", value: newMember.toString(), inline: true }, + { name: "Executor", value: executor.toString(), inline: true } + ) + .setFooter({ text: `ID: ${newMember.id}` }); + } // Guild avatar change if (oldMember.avatar !== newMember.avatar) { + changed = true; embed .setAuthor({ name: "Avatar Changed" }) .setThumbnail(newMember.displayAvatarURL()) @@ -91,6 +136,7 @@ module.exports = async (client, oldMember, newMember) => { // Voice muted if (!oldMember.voice?.serverMute && newMember.voice?.serverMute) { + changed = true; embed .setAuthor({ name: "Member Server Muted for Voice Channels" }) .setThumbnail(newMember.user.displayAvatarURL()) @@ -103,6 +149,7 @@ module.exports = async (client, oldMember, newMember) => { // Voice Mute Removed if (oldMember.voice?.serverMute && !newMember.voice?.serverMute) { + changed = true; embed .setAuthor({ name: "Member Server Mute Removed for Voice Channels" }) .setThumbnail(newMember.user.displayAvatarURL()) @@ -113,7 +160,7 @@ module.exports = async (client, oldMember, newMember) => { }) .setFooter({ text: `ID: ${newMember.id}` }) } - if (embed.data.fields?.length === 0) return; + if (!changed) return; await logChannel.send({ embeds: [embed] }) }; From 784a2cf54bc027d8dc5cfb15da72d48580a3287d Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:32:25 +0530 Subject: [PATCH 10/11] refactor: removed executor from bulk delete log, added text file containing deleted message --- src/events/message/messageDeleteBulk.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/events/message/messageDeleteBulk.js b/src/events/message/messageDeleteBulk.js index 10c32a643..38939c42f 100644 --- a/src/events/message/messageDeleteBulk.js +++ b/src/events/message/messageDeleteBulk.js @@ -1,4 +1,4 @@ -const { EmbedBuilder, AuditLogEvent } = require("discord.js"); +const { EmbedBuilder, AttachmentBuilder } = require("discord.js"); const { getSettings } = require("@schemas/Guild"); /** @@ -11,21 +11,24 @@ module.exports = async (client, messages, channel) => { if (!settings.logging?.messages) return const logChannel = client.channels.cache.get(settings.logging.messages); - const auditLog = await channel.guild.fetchAuditLogs({ type: AuditLogEvent.MessageBulkDelete, limit: 1 }); - const entry = auditLog.entries.first(); - const executor = entry.extra.channel.id === channel.id ? entry.executor : null; + let messagesDesc = "Deleted Messages\n\n"; + for (const m of messages.values()) { + if (m.partial) continue; + if (!m.content.length) continue + messagesDesc += `Author: ${m.author.username}\nContent: ${m.content}\n--------------\n\n`; + } + const file = new AttachmentBuilder(Buffer.from(messagesDesc), { + name: "deletedMessages.txt" + }); const logEmbed = new EmbedBuilder() .setAuthor({ name: "Bulk Message Deleted" }) .setThumbnail() .setColor("Red") - .setDescription(`${messages.size} Messages deleted in ${channel}`) - .setFields({ - name: "Executor", - value: executor ? executor.toString() : "Unknown", - }) + .setDescription(`### ${messages.size} Messages deleted in ${channel}`) .setTimestamp(); - logChannel.safeSend({ embeds: [logEmbed] }) + await logChannel.safeSend({ embeds: [logEmbed] }) + await logChannel.safeSend({ files: [file] }); }; From b7f0a79e5409933f1bbd75715c3e16d3fa2ff4d2 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Sat, 13 Jul 2024 02:26:29 +0530 Subject: [PATCH 11/11] fix: voice logs not working when music command is enabled also permission for audit logs --- src/events/voice/voiceStateUpdate.js | 23 ++++++++++++----------- src/structures/BotClient.js | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/events/voice/voiceStateUpdate.js b/src/events/voice/voiceStateUpdate.js index 2f986df82..c6304a307 100644 --- a/src/events/voice/voiceStateUpdate.js +++ b/src/events/voice/voiceStateUpdate.js @@ -15,18 +15,19 @@ module.exports = async (client, oldState, newState) => { if (client.config.MUSIC.ENABLED) { const guild = oldState.guild; - // if nobody left the channel in question, return. - if (oldState.channelId !== guild.members.me.voice.channelId || newState.channel) return; - // otherwise, check how many people are in the channel now - if (oldState.channel.members.size === 1) { - setTimeout(() => { - // if 1 (you), wait 1 minute - if (!oldState.channel.members.size - 1) { - const player = client.musicManager.getPlayer(guild.id); - if (player) client.musicManager.destroyPlayer(guild.id).then(player.disconnect()); // destroy the player - } - }, client.config.MUSIC.IDLE_TIME * 1000); + if (oldState.channelId === guild.members.me.voice.channelId && !newState.channel) { + + // check how many people are in the channel now + if (oldState.channel.members.size === 1) { + setTimeout(() => { + // if 1 (you), wait 1 minute + if (!oldState.channel.members.size - 1) { + const player = client.musicManager.getPlayer(guild.id); + if (player) client.musicManager.destroyPlayer(guild.id).then(player.disconnect()); // destroy the player + } + }, client.config.MUSIC.IDLE_TIME * 1000); + } } } diff --git a/src/structures/BotClient.js b/src/structures/BotClient.js index 53fc0e899..80b1162f8 100644 --- a/src/structures/BotClient.js +++ b/src/structures/BotClient.js @@ -341,6 +341,7 @@ module.exports = class BotClient extends Client { "SendMessagesInThreads", "Speak", "ViewChannel", + "ViewAuditLog" ], }); }