From bf4139a7ff4e03ef2b7c50a98f0f7c1c28f66e35 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 25 Mar 2024 04:13:32 +0300 Subject: [PATCH] basic audit log complete! i am so happy about this information --- src/index.ts | 10 +- src/lib/events.ts | 373 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 333 insertions(+), 50 deletions(-) diff --git a/src/index.ts b/src/index.ts index f2162ba..6e729d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,13 +15,15 @@ import { setupListeners } from './lib/events'; const bot = new Client({ intents: [ GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.GuildVoiceStates, - GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildInvites, GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildModeration, + GatewayIntentBits.GuildScheduledEvents, + GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.MessageContent, GatewayIntentBits.DirectMessages, - GatewayIntentBits.GuildModeration, ], }); diff --git a/src/lib/events.ts b/src/lib/events.ts index eefb708..142c12d 100644 --- a/src/lib/events.ts +++ b/src/lib/events.ts @@ -1,4 +1,4 @@ -import { Sticker, ThreadChannel, Message, User, Invite, GuildMember, GuildScheduledEvent, GuildEmoji, Client, Events, AuditLogEvent, ChannelType, PartialMessage, EmbedBuilder, MessageType } from 'discord.js'; +import { ThreadChannel, Message, User, Invite, GuildMember, GuildScheduledEvent, GuildEmoji, Client, Events, AuditLogEvent, ChannelType, PartialMessage, EmbedBuilder, MessageType } from 'discord.js'; import { AuditLog, db } from './db'; import * as log from './log'; import { formatMessageAsEmbed, getUploadLimitForGuild, shortenStr } from './util'; @@ -10,7 +10,6 @@ export enum EventType { EmojiDelete = 'EMOJI_DELETE', EventCreate = 'EVENT_CREATE', - EventEdit = 'EVENT_EDIT', EventDelete = 'EVENT_DELETE', InviteCreate = 'INVITE_CREATE', @@ -19,7 +18,6 @@ export enum EventType { MemberBan = 'MEMBER_BAN', MemberUnban = 'MEMBER_UNBAN', MemberKick = 'MEMBER_KICK', - MemberDisconnect = 'MEMBER_DISCONNECT', MemberNickname = 'MEMBER_NICKNAME', MemberJoin = 'MEMBER_JOIN', MemberLeave = 'MEMBER_LEAVE', @@ -27,9 +25,9 @@ export enum EventType { MessageDelete = 'MESSAGE_DELETE', MessageEdit = 'MESSAGE_EDIT', - StickerCreate = 'STICKER_CREATE', - StickerRename = 'STICKER_RENAME', - StickerDelete = 'STICKER_DELETE', + //StickerCreate = 'STICKER_CREATE', + //StickerRename = 'STICKER_RENAME', + //StickerDelete = 'STICKER_DELETE', ThreadCreate = 'THREAD_CREATE', ThreadEdit = 'THREAD_EDIT', @@ -53,11 +51,6 @@ export type Event = { type: EventType.EventCreate, causer: User | null, event: GuildScheduledEvent, -} | { - type: EventType.EventEdit, - causer: User | null, - oldEvent: GuildScheduledEvent, - event: GuildScheduledEvent, } | { type: EventType.EventDelete, causer: User | null, @@ -74,26 +67,30 @@ export type Event = { type: EventType.MemberBan, causer: User | null, member: GuildMember, - reason: string, + reason: string | null, } | { type: EventType.MemberUnban, causer: User | null, member: GuildMember, - reason: string, + reason: string | null, } | { type: EventType.MemberKick, causer: User | null, member: GuildMember, - reason: string, -} | { - type: EventType.MemberDisconnect, - causer: User | null, - member: GuildMember, + reason: string | null, } | { type: EventType.MemberNickname, causer: User | null, member: GuildMember, - oldNickname: string, newNickname: string, + oldNickname: string | null, newNickname: string | null, +} | { + type: EventType.MemberJoin, + causer: User | null, + member: GuildMember, +} | { + type: EventType.MemberLeave, + causer: User | null, + member: GuildMember, } | { type: EventType.MessageDelete, causer: User | null, @@ -103,7 +100,7 @@ export type Event = { causer: User | null, oldMessage: Message, message: Message, -} | { +}/* | { type: EventType.StickerCreate, causer: User | null, sticker: Sticker, @@ -115,7 +112,7 @@ export type Event = { type: EventType.StickerDelete, causer: User | null, sticker: Sticker, -} | { +}*/ | { type: EventType.ThreadCreate, causer: User | null, thread: ThreadChannel, @@ -151,6 +148,167 @@ export async function triggerEvent(bot: Client, guildId: string, event: Event) { if (channel && (channel.type === ChannelType.GuildText)) { switch(event.type) { + case EventType.EmojiCreate: { + const logEmbed = new EmbedBuilder() + .setDescription(`Emoji **${event.emoji.name}** ${event.emoji} created by ${event.causer}`) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.EmojiRename: { + const logEmbed = new EmbedBuilder() + .setDescription(`Emoji **${event.oldName}** ${event.emoji} renamed to **${event.newName}** by ${event.causer}`) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.EmojiDelete: { + const logEmbed = new EmbedBuilder() + .setDescription(`Emoji **${event.emoji.name}** deleted by ${event.causer}`) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.EventCreate: { + const ts = event.event.scheduledStartAt && Math.floor(event.event.scheduledStartAt.getTime() / 1000); + + const logEmbed = new EmbedBuilder() + .setDescription(`Event **${event.event.name}** created by ${event.causer}` + (ts ? ` for ()` : '')) + .setImage(event.event.coverImageURL()) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.EventDelete: { + const logEmbed = new EmbedBuilder() + .setDescription(`Event **${event.event.name}** deleted by ${event.causer}`) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.InviteCreate: { + const logEmbed = new EmbedBuilder() + .setDescription(`Invite \`${event.invite.code}\` created by ${event.causer} in <#${event.invite.channelId}>`) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.InviteDelete: { + const logEmbed = new EmbedBuilder() + .setDescription(`Invite \`${event.invite.code}\` (made by ${event.invite.inviter} in <#${event.invite.channelId}>) deleted by ${event.causer}`) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.MemberBan: { + const logEmbed = new EmbedBuilder() + .setDescription(`${event.member} banned by ${event.causer}` + (event.reason ? ` (reason: \`${event.reason}\`)` : '')) + .setThumbnail(event.member.avatarURL()) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.MemberUnban: { + const logEmbed = new EmbedBuilder() + .setDescription(`${event.member} unbanned by ${event.causer}` + (event.reason ? ` (reason: \`${event.reason}\`)` : '')) + .setThumbnail(event.member.avatarURL()) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.MemberKick: { + const logEmbed = new EmbedBuilder() + .setDescription(`${event.member} kicked by ${event.causer}` + (event.reason ? ` (reason: \`${event.reason}\`)` : '')) + .setThumbnail(event.member.avatarURL()) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.MemberNickname: { + const verb = event.newNickname ? (event.oldNickname ? 'changed' : 'set') : 'removed'; + + const logEmbed = new EmbedBuilder() + .setDescription(`${event.member}${(event.causer && event.causer.id !== event.member.id) ? `'s nickname was ${verb} by ${event.causer}` : ` ${verb} their nickname`}${event.newNickname ? ` to **${event.newNickname}**` : ''}`) + .setThumbnail(event.member.avatarURL()) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.MemberJoin: { + const logEmbed = new EmbedBuilder() + .setDescription(`${event.member} joined`) + .setThumbnail(event.member.user.avatarURL()) + .setTimestamp(); + + const ts = Math.floor(event.member.user.createdAt.getTime() / 1000); + + logEmbed.addFields({ + name: 'Account Age', + value: ``, + }); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } + case EventType.MemberLeave: { + const logEmbed = new EmbedBuilder() + .setDescription(`${event.member} left`) + .setThumbnail(event.member.avatarURL()) + .setTimestamp(); + + channel.send({ + embeds: [ logEmbed ], + }); + + break; + } case EventType.MessageDelete: { const limit = getUploadLimitForGuild(channel.guild); const { embed, attach } = formatMessageAsEmbed(event.message, limit); @@ -197,28 +355,6 @@ export async function triggerEvent(bot: Client, guildId: string, event: Event) { break; } - case EventType.InviteCreate: { - const logEmbed = new EmbedBuilder() - .setDescription(`Invite ${event.invite.code} created by ${event.causer} in <#${event.invite.channelId}>`) - .setTimestamp(); - - channel.send({ - embeds: [ logEmbed ], - }); - - break; - } - case EventType.InviteDelete: { - const logEmbed = new EmbedBuilder() - .setDescription(`Invite ${event.invite.code} made by ${event.invite.inviter} in <#${event.invite.channelId}> deleted by ${event.causer}`) - .setTimestamp(); - - channel.send({ - embeds: [ logEmbed ], - }); - - break; - } } } else { log.warn(`Channel ${auditLog.channel} from guild ${auditLog.guild} not found! Deleting audit log`); @@ -259,6 +395,7 @@ export function setupListeners(bot: Client) { if (!message.guild) return; if (message.content === old.content) return; if (message.partial || old.partial) return; + if (message.flags.has('Ephemeral')) return; triggerEvent(bot, message.guild.id, { type: EventType.MessageEdit, @@ -271,22 +408,166 @@ export function setupListeners(bot: Client) { triggerEvent(bot, invite.guild.id, { type: EventType.InviteCreate, - causer: invite.inviter, invite, + causer: invite.inviter || (invite.inviterId ? await bot.users.fetch(invite.inviterId) : null), + invite, + }); + }); + bot.on(Events.GuildScheduledEventCreate, async event => { + triggerEvent(bot, event.guildId, { + type: EventType.EventCreate, + causer: event.creator || (event.creatorId ? await bot.users.fetch(event.creatorId) : null), + event, + }); + }); + bot.on(Events.GuildMemberAdd, async member => { + triggerEvent(bot, member.guild.id, { + type: EventType.MemberJoin, + causer: member.user, + member, + }); + }); + bot.on(Events.GuildMemberRemove, async member => { + if (member.partial) return; + + const auditKick = await member.guild.fetchAuditLogs({ + type: AuditLogEvent.MemberKick, + limit: 1, + }); + + const entryKick = auditKick.entries.first(); + + if ( + entryKick && ((Date.now() - entryKick.createdTimestamp) < DELETE_FETCH_TIMEOUT + && entryKick.targetId === member.id) + ) { + return; + } + + const auditBan = await member.guild.fetchAuditLogs({ + type: AuditLogEvent.MemberBanAdd, + limit: 1, + }); + + const entryBan = auditBan.entries.first(); + + if ( + entryBan && ((Date.now() - entryBan.createdTimestamp) < DELETE_FETCH_TIMEOUT + && entryBan.targetId === member.id) + ) { + return; + } + + triggerEvent(bot, member.guild.id, { + type: EventType.MemberLeave, + causer: member.user, + member, + }); + }); + bot.on(Events.ThreadCreate, async (thread, newly) => { + if (!newly) return; + + triggerEvent(bot, thread.guildId, { + type: EventType.ThreadCreate, + causer: (await thread.fetchOwner())?.user ?? null, + thread, + }); + }); + bot.on(Events.ThreadUpdate, async (oldThread, thread) => { + triggerEvent(bot, thread.guildId, { + type: EventType.ThreadEdit, + causer: null, + oldThread, thread, }); }); bot.on(Events.GuildAuditLogEntryCreate, async (entry, guild) => { + const executor = entry.executor || (entry.executorId ? await bot.users.fetch(entry.executorId) : null); + if (entry.action === AuditLogEvent.InviteDelete) { if (!entry.target) return; triggerEvent(bot, guild.id, { type: EventType.InviteDelete, - causer: entry.executor, + causer: executor, // @ts-expect-error shut up invite: entry.target }); } else if (entry.action === AuditLogEvent.EmojiCreate) { - + triggerEvent(bot, guild.id, { + type: EventType.EmojiCreate, + causer: executor, + emoji: await guild.emojis.fetch(entry.targetId!), + }); + } else if (entry.action === AuditLogEvent.EmojiUpdate) { + const nameChange = entry.changes.find(c => c.key === 'name'); + + if (!nameChange) return; + + triggerEvent(bot, guild.id, { + type: EventType.EmojiRename, + causer: executor, + // @ts-expect-error dude + oldName: nameChange.old!, newName: nameChange.new!, + // @ts-expect-error stop + emoji: entry.target, + }); + } else if (entry.action === AuditLogEvent.EmojiDelete) { + triggerEvent(bot, guild.id, { + type: EventType.EmojiDelete, + causer: executor, + // @ts-expect-error help + emoji: entry.target + }); + } else if (entry.action === AuditLogEvent.GuildScheduledEventDelete) { + triggerEvent(bot, guild.id, { + type: EventType.EventDelete, + causer: executor, + // @ts-expect-error shut the fuck up + event: entry.target, + }); + } else if (entry.action === AuditLogEvent.MemberBanAdd) { + triggerEvent(bot, guild.id, { + type: EventType.MemberBan, + causer: executor, + // @ts-expect-error shut the fuck up + member: entry.target, + reason: entry.reason, + }); + } else if (entry.action === AuditLogEvent.MemberBanRemove) { + triggerEvent(bot, guild.id, { + type: EventType.MemberUnban, + causer: executor, + // @ts-expect-error shut the fuck up + member: entry.target, + reason: entry.reason, + }); + } else if (entry.action === AuditLogEvent.MemberKick) { + triggerEvent(bot, guild.id, { + type: EventType.MemberKick, + causer: executor, + // @ts-expect-error please + member: entry.target, + reason: entry.reason, + }); + } else if (entry.action === AuditLogEvent.ThreadDelete) { + triggerEvent(bot, guild.id, { + type: EventType.ThreadDelete, + causer: executor, + // @ts-expect-error be gone + thread: entry.target, + }); + } else if (entry.action === AuditLogEvent.MemberUpdate) { + const nameChange = entry.changes.find(c => c.key === 'nick'); + + if (!nameChange) return; + + triggerEvent(bot, guild.id, { + type: EventType.MemberNickname, + causer: executor, + member: await guild.members.fetch(entry.targetId!), + // @ts-expect-error shut up + oldNickname: nameChange.old || null, newNickname: nameChange.new || null, + }); } }); } \ No newline at end of file