const { Client, Events, EmbedBuilder, AuditLogEvent } = require("discord.js"); const LOG_CHANNEL = process.env.AUDIT_LOG_CHANNEL; const LOG_COLOR = process.env.AUDIT_LOG_COLOR || "#5865F2"; const BRAND = "Willow โ€ข Audit Log"; const logs = { MEMBER_JOIN: true, MEMBER_LEAVE: true, MEMBER_KICK: true, TIMEOUT: true, NICKNAME: true, USERNAME: true, AVATAR: true, MESSAGE_DELETE: true, MESSAGE_EDIT: true, MESSAGE_BULK_DELETE: true, ROLE_CREATE: true, ROLE_DELETE: true, ROLE_UPDATE: true, ROLE_ADD: true, ROLE_REMOVE: true, CHANNEL_CREATE: true, CHANNEL_DELETE: true, CHANNEL_UPDATE: true, VOICE_JOIN: true, VOICE_LEAVE: true, VOICE_MOVE: true, INVITE_CREATE: true, INVITE_DELETE: true, EMOJI_CREATE: true, EMOJI_DELETE: true, EMOJI_UPDATE: true, STICKER_CREATE: true, STICKER_DELETE: true, STICKER_UPDATE: true, THREAD_CREATE: true, THREAD_DELETE: true, THREAD_UPDATE: true, THREAD_STATE: true, BOOST: true, SERVER_UPDATE: true, BAN_ADD: true, BAN_REMOVE: true }; function send(guild, title, fields, description = null, thumbnail = null) { const ch = guild.channels.cache.get(LOG_CHANNEL); if (!ch) return; const embed = new EmbedBuilder() .setAuthor({ name: BRAND, iconURL: guild.client.user.displayAvatarURL() }) .setTitle(title) .setColor(LOG_COLOR) .setTimestamp(); if (description) embed.setDescription(description); if (fields?.length > 0) embed.addFields(fields); if (thumbnail) embed.setThumbnail(thumbnail); ch.send({ embeds: [embed] }).catch(() => {}); } module.exports = (client) => { client.on(Events.GuildMemberAdd, (m) => { if (!logs.MEMBER_JOIN) return; send(m.guild, "๐Ÿ‘ค Member Joined", [ { name: "User", value: `${m} \n\`${m.id}\`` }, { name: "Account Age", value: `` } ], null, m.user.displayAvatarURL()); }); client.on(Events.GuildMemberRemove, async (member) => { if (!logs.MEMBER_LEAVE && !logs.MEMBER_KICK) return; const now = Date.now(); await new Promise(r => setTimeout(r, 2000)); const logsFetched = await member.guild.fetchAuditLogs({ limit: 20, type: AuditLogEvent.MemberKick }).catch(() => null); const entries = logsFetched ? [...logsFetched.entries.values()] : []; const filtered = entries.filter(e => now - e.createdTimestamp < 15000 && e.target?.id === member.id ); const sorted = filtered.sort((a, b) => b.createdTimestamp - a.createdTimestamp); const kickLog = sorted[0]; if (kickLog && logs.MEMBER_KICK) { send(member.guild, "๐Ÿ‘ข Member Kicked", [ { name: "User", value: `${member.user.tag}\n\`${member.id}\`` }, { name: "Moderator", value: `${kickLog.executor}` }, { name: "Reason", value: kickLog.reason || "None" } ], null, member.user.displayAvatarURL()); return; } if (logs.MEMBER_LEAVE) { send(member.guild, "๐Ÿšช Member Left", [ { name: "User", value: `${member.user.tag}\n\`${member.id}\`` } ], null, member.user.displayAvatarURL()); } }); client.on(Events.GuildMemberUpdate, (oldM, newM) => { // nickname if (logs.NICKNAME && oldM.nickname !== newM.nickname) { send(newM.guild, "โœ๏ธ Nickname Updated", [ { name: "User", value: `${newM}` }, { name: "Before", value: oldM.nickname || "*None*" }, { name: "After", value: newM.nickname || "*None*" } ]); } // timeout if (logs.TIMEOUT) { const before = oldM.communicationDisabledUntilTimestamp; const after = newM.communicationDisabledUntilTimestamp; if (before !== after) { if (after) { send(newM.guild, "โณ Timeout Applied", [ { name: "User", value: `${newM}` }, { name: "Until", value: `` } ]); } else { send(newM.guild, "โฑ Timeout Removed", [ { name: "User", value: `${newM}` } ]); } } } }); client.on(Events.UserUpdate, (o, n) => { client.guilds.cache .filter(g => g.members.cache.has(n.id)) .forEach(guild => { if (logs.USERNAME && o.username !== n.username) { send(guild, "๐Ÿ“ Username Updated", [ { name: "User", value: `${n}` }, { name: "Before", value: o.username }, { name: "After", value: n.username } ]); } if (logs.AVATAR && o.avatar !== n.avatar) { send(guild, "๐Ÿ–ผ Avatar Updated", [ { name: "User", value: `${n}` }, { name: "Old Avatar", value: o.displayAvatarURL() }, { name: "New Avatar", value: n.displayAvatarURL() } ], null, n.displayAvatarURL()); } }); }); client.on(Events.MessageDelete, async (msg) => { if (!logs.MESSAGE_DELETE || msg.partial || !msg.guild) return; const audit = await msg.guild.fetchAuditLogs({ type: AuditLogEvent.MessageDelete, limit: 1 }).catch(() => null); const entry = audit?.entries.first(); let moderator = "Unknown"; if ( entry && entry.extra?.channel?.id === msg.channel.id && entry.target?.id === msg.author?.id ) { moderator = entry.executor?.toString() || "Unknown"; } send(msg.guild, "๐Ÿ—‘ Message Deleted", [ { name: "Author", value: `${msg.author}` }, { name: "Channel", value: `${msg.channel}` }, { name: "Moderator", value: moderator }, { name: "Content", value: `\`\`\`\n${msg.content?.slice(0, 1024) || "*None*"}\n\`\`\`` } ], null, msg.author.displayAvatarURL()); }); client.on(Events.MessageUpdate, (o, n) => { if (!logs.MESSAGE_EDIT || n.author?.bot) return; if (o.content === n.content) return; send(n.guild, "โœ๏ธ Message Edited", [ { name: "Author", value: `${n.author}` }, { name: "Channel", value: `${n.channel}` }, { name: "Before", value: `\`\`\`\n${o.content || "*None*"}\n\`\`\`` }, { name: "After", value: `\`\`\`\n${n.content || "*None*"}\n\`\`\`` } ], null, n.author.displayAvatarURL()); }); client.on(Events.MessageBulkDelete, (col) => { if (!logs.MESSAGE_BULK_DELETE) return; send(col.first().guild, "๐Ÿงน Bulk Delete", [ { name: "Channel", value: `${col.first().channel}` }, { name: "Messages Deleted", value: `${col.size}` } ]); }); client.on(Events.RoleCreate, r => logs.ROLE_CREATE && send(r.guild, "๐Ÿ“˜ Role Created", [ { name: "Role", value: `${r}` }, { name: "ID", value: r.id } ]) ); client.on(Events.RoleDelete, r => logs.ROLE_DELETE && send(r.guild, "๐Ÿ“• Role Deleted", [ { name: "Name", value: r.name }, { name: "ID", value: r.id } ]) ); client.on(Events.RoleUpdate, (o, n) => logs.ROLE_UPDATE && send(n.guild, "๐Ÿ“— Role Updated", [ { name: "Before", value: o.name }, { name: "After", value: n.name } ]) ); client.on(Events.ChannelCreate, c => logs.CHANNEL_CREATE && send(c.guild, "๐Ÿ“บ Channel Created", [ { name: "Name", value: c.name }, { name: "ID", value: c.id } ]) ); client.on(Events.ChannelDelete, c => logs.CHANNEL_DELETE && send(c.guild, "๐Ÿ“บ Channel Deleted", [ { name: "Name", value: c.name }, { name: "ID", value: c.id } ]) ); client.on(Events.ChannelUpdate, (o, n) => logs.CHANNEL_UPDATE && send(n.guild, "๐Ÿ“บ Channel Updated", [ { name: "Before", value: o.name }, { name: "After", value: n.name } ]) ); client.on(Events.GuildBanAdd, async (ban) => { if (!logs.BAN_ADD) return; await new Promise(res => setTimeout(res, 1500)); const audit = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanAdd, limit: 1 }).catch(() => null); const entry = audit?.entries.first(); send(ban.guild, "๐Ÿ”จ User Banned", [ { name: "User", value: `${ban.user.tag}\n\`${ban.user.id}\`` }, { name: "Moderator", value: entry?.executor?.toString() || "Unknown" }, { name: "Reason", value: entry?.reason || "None" } ], null, ban.user.displayAvatarURL()); }); client.on(Events.GuildBanRemove, async (ban) => { if (!logs.BAN_REMOVE) return; await new Promise(res => setTimeout(res, 1500)); const audit = await ban.guild.fetchAuditLogs({ type: AuditLogEvent.MemberBanRemove, limit: 1 }).catch(() => null); const entry = audit?.entries.first(); send(ban.guild, "โš–๏ธ User Unbanned", [ { name: "User", value: `${ban.user.tag}\n\`${ban.user.id}\`` }, { name: "Moderator", value: entry?.executor?.toString() || "Unknown" } ], null, ban.user.displayAvatarURL()); }); client.on(Events.GuildMemberUpdate, (o, n) => { if (!logs.BOOST) return; if (!o.premiumSince && n.premiumSince) send(n.guild, "๐ŸŒŸ Server Boosted", [ { name: "User", value: `${n}` } ], null, n.user.displayAvatarURL()); if (o.premiumSince && !n.premiumSince) send(n.guild, "๐Ÿ’” Boost Removed", [ { name: "User", value: `${n}` } ], null, n.user.displayAvatarURL()); }); };