2024-03-25 02:13:32 +01:00
import { ThreadChannel , Message , User , Invite , GuildMember , GuildScheduledEvent , GuildEmoji , Client , Events , AuditLogEvent , ChannelType , PartialMessage , EmbedBuilder , MessageType } from 'discord.js' ;
2024-03-18 13:09:49 +01:00
import { AuditLog , db } from './db' ;
import * as log from './log' ;
2024-03-18 20:19:14 +01:00
import { formatMessageAsEmbed , getUploadLimitForGuild , shortenStr } from './util' ;
import { Change , diffWords } from 'diff' ;
2024-03-17 22:22:03 +01:00
export enum EventType {
2024-03-18 13:09:49 +01:00
EmojiCreate = 'EMOJI_CREATE' ,
EmojiRename = 'EMOJI_RENAME' ,
EmojiDelete = 'EMOJI_DELETE' ,
EventCreate = 'EVENT_CREATE' ,
EventDelete = 'EVENT_DELETE' ,
InviteCreate = 'INVITE_CREATE' ,
InviteDelete = 'INVITE_DELETE' ,
MemberBan = 'MEMBER_BAN' ,
MemberUnban = 'MEMBER_UNBAN' ,
MemberKick = 'MEMBER_KICK' ,
MemberNickname = 'MEMBER_NICKNAME' ,
2024-03-18 20:19:14 +01:00
MemberJoin = 'MEMBER_JOIN' ,
MemberLeave = 'MEMBER_LEAVE' ,
2024-03-17 22:22:03 +01:00
2024-03-18 13:09:49 +01:00
MessageDelete = 'MESSAGE_DELETE' ,
MessageEdit = 'MESSAGE_EDIT' ,
2024-03-17 22:22:03 +01:00
2024-03-25 02:13:32 +01:00
//StickerCreate = 'STICKER_CREATE',
//StickerRename = 'STICKER_RENAME',
//StickerDelete = 'STICKER_DELETE',
2024-03-17 22:22:03 +01:00
2024-03-18 13:09:49 +01:00
ThreadCreate = 'THREAD_CREATE' ,
ThreadEdit = 'THREAD_EDIT' ,
ThreadDelete = 'THREAD_DELETE' ,
2024-03-17 22:22:03 +01:00
}
export type Event = {
type : EventType . EmojiCreate ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
emoji : GuildEmoji ,
} | {
type : EventType . EmojiRename ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
emoji : GuildEmoji ,
oldName : string , newName : string ,
} | {
type : EventType . EmojiDelete ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
emoji : GuildEmoji ,
} | {
type : EventType . EventCreate ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
event : GuildScheduledEvent ,
} | {
type : EventType . EventDelete ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
event : GuildScheduledEvent ,
} | {
type : EventType . InviteCreate ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
invite : Invite ,
} | {
type : EventType . InviteDelete ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
invite : Invite ,
} | {
type : EventType . MemberBan ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
member : GuildMember ,
2024-03-25 02:13:32 +01:00
reason : string | null ,
2024-03-17 22:22:03 +01:00
} | {
type : EventType . MemberUnban ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
member : GuildMember ,
2024-03-25 02:13:32 +01:00
reason : string | null ,
2024-03-17 22:22:03 +01:00
} | {
type : EventType . MemberKick ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
member : GuildMember ,
2024-03-25 02:13:32 +01:00
reason : string | null ,
2024-03-17 22:22:03 +01:00
} | {
2024-03-25 02:13:32 +01:00
type : EventType . MemberNickname ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
member : GuildMember ,
2024-03-25 02:13:32 +01:00
oldNickname : string | null , newNickname : string | null ,
2024-03-17 22:22:03 +01:00
} | {
2024-03-25 02:13:32 +01:00
type : EventType . MemberJoin ,
causer : User | null ,
member : GuildMember ,
} | {
type : EventType . MemberLeave ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
member : GuildMember ,
} | {
type : EventType . MessageDelete ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
message : Message | PartialMessage ,
2024-03-17 22:22:03 +01:00
} | {
type : EventType . MessageEdit ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
oldMessage : Message ,
message : Message ,
2024-03-25 02:13:32 +01:00
} / * | {
2024-03-17 22:22:03 +01:00
type : EventType . StickerCreate ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
sticker : Sticker ,
} | {
type : EventType . StickerRename ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
oldName : string , newName : string ,
} | {
type : EventType . StickerDelete ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
sticker : Sticker ,
2024-03-25 02:13:32 +01:00
} * / | {
2024-03-17 22:22:03 +01:00
type : EventType . ThreadCreate ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
thread : ThreadChannel ,
} | {
type : EventType . ThreadEdit ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
oldThread : ThreadChannel ,
thread : ThreadChannel ,
} | {
type : EventType . ThreadDelete ,
2024-03-18 20:19:14 +01:00
causer : User | null ,
2024-03-17 22:22:03 +01:00
thread : ThreadChannel ,
} ;
2024-03-18 20:19:14 +01:00
export async function triggerEvent ( bot : Client , guildId : string , event : Event ) {
2024-03-18 13:09:49 +01:00
const type = event . type ;
2024-03-18 20:19:14 +01:00
log . info ( ` got event ${ event . type } ` ) ;
2024-03-18 13:09:49 +01:00
const logs = await db < AuditLog > ( 'auditLogs' )
. select ( 'guild' , 'channel' )
2024-03-18 20:19:14 +01:00
. where ( 'guild' , guildId )
2024-03-18 13:09:49 +01:00
// @ts-expect-error this LITERALLY works
. whereLike ( db . raw ( 'UPPER(eventTypes)' ) , ` % ${ type . toUpperCase ( ) } % ` ) ;
for ( const auditLog of logs ) {
let channel ;
try {
channel = await bot . channels . fetch ( auditLog . channel ) ;
} catch ( err ) {
log . warn ( err ) ;
}
2024-03-18 20:19:14 +01:00
if ( channel && ( channel . type === ChannelType . GuildText ) ) {
switch ( event . type ) {
2024-03-25 02:13:32 +01:00
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 ( )
2024-03-25 02:34:41 +01:00
. setDescription ( ` Event [** ${ event . event . name } **]( ${ event . event . url } ) created by ${ event . causer } ` + ( ts ? ` for <t: ${ ts } :R> (<t: ${ ts } >) ` : '' ) )
2024-03-25 02:13:32 +01:00
. setImage ( event . event . coverImageURL ( ) )
. setTimestamp ( ) ;
channel . send ( {
embeds : [ logEmbed ] ,
} ) ;
break ;
}
case EventType . EventDelete : {
const logEmbed = new EmbedBuilder ( )
2024-03-25 02:34:41 +01:00
. setDescription ( ` Event ** ${ event . event . name } ** cancelled by ${ event . causer } ` )
. setFooter ( { text : '\'Cancelled\' is equivalent to \'deleted\' in Discord terms' } )
2024-03-25 02:13:32 +01:00
. 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 : ` <t: ${ ts } :R> ` ,
} ) ;
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 ;
}
2024-03-18 20:19:14 +01:00
case EventType . MessageDelete : {
const limit = getUploadLimitForGuild ( channel . guild ) ;
const { embed , attach } = formatMessageAsEmbed ( event . message , limit ) ;
const logEmbed = new EmbedBuilder ( )
. setDescription ( ` Message sent by ${ event . message . author } in <# ${ event . message . channelId } > deleted by ** ${ event . causer } **: ` )
. setTimestamp ( ) ;
channel . send ( {
embeds : [ logEmbed , embed ] ,
files : attach ? [ {
attachment : attach.url ,
name : attach.name ,
description : attach.url ,
} ] : [ ] ,
} ) ;
break ;
}
case EventType . MessageEdit : {
const logEmbed = new EmbedBuilder ( )
2024-03-25 02:34:41 +01:00
. setDescription ( ` [Message]( ${ event . message . url } ) sent by ${ event . message . author } in <# ${ event . message . channelId } > edited: ` )
2024-03-18 20:19:14 +01:00
. setTimestamp ( ) ;
const diff = diffWords ( event . oldMessage . content . replace ( /`/g , '' ) , event . message . content . replace ( /`/g , '' ) ) ;
let output = '' ;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff . forEach ( ( part : Change ) = > {
if ( part . added ) output += ` \ x1B[2;32m \ x1B[1;32m ${ part . value } \ x1B[0m ` ;
if ( part . removed ) output += ` \ x1B[2;41m \ x1B[1;2m ${ part . value } \ x1B[0m ` ;
if ( ! part . added && ! part . removed ) output += part . value ;
} ) ;
const diffMsg = ` \` \` \` ansi \ n ${ shortenStr ( output , 4000 ) } \` \` \` ` ;
const editEmbed = new EmbedBuilder ( )
. setAuthor ( { name : event.message.author.tag , iconURL : event.message.author.displayAvatarURL ( ) } )
. setDescription ( diffMsg ) ;
channel . send ( {
embeds : [ logEmbed , editEmbed ] ,
} ) ;
break ;
}
}
2024-03-18 13:09:49 +01:00
} else {
log . warn ( ` Channel ${ auditLog . channel } from guild ${ auditLog . guild } not found! Deleting audit log ` ) ;
await db < AuditLog > ( 'auditLogs' )
2024-03-18 20:19:14 +01:00
. where ( 'guild' , auditLog . guild )
2024-03-18 13:09:49 +01:00
. where ( 'channel' , auditLog . channel )
. delete ( ) ;
}
}
}
2024-03-18 20:19:14 +01:00
const DELETE_FETCH_TIMEOUT = 2 _000 ;
2024-03-18 13:09:49 +01:00
export function setupListeners ( bot : Client ) {
2024-03-18 20:19:14 +01:00
bot . on ( Events . MessageDelete , async message = > {
if ( ! message . guild ) return ;
if ( ! ( message . type === MessageType . Default || message . type === MessageType . Reply || message . type === MessageType . ThreadStarterMessage ) ) return ;
let causer : User | null = message . author ;
2024-03-18 13:09:49 +01:00
2024-03-18 20:19:14 +01:00
const audit = await message . guild . fetchAuditLogs ( {
type : AuditLogEvent . MessageDelete ,
limit : 1 ,
} ) ;
2024-03-18 13:09:49 +01:00
2024-03-18 20:19:14 +01:00
const entry = audit . entries . first ( ) ;
if ( entry && ( ( Date . now ( ) - entry . createdTimestamp ) < DELETE_FETCH_TIMEOUT || ( entry . extra . count > 1 ) ) && entry . targetId === message . author ? . id ) {
causer = entry . executor ;
}
2024-03-18 13:09:49 +01:00
2024-03-18 20:19:14 +01:00
triggerEvent ( bot , message . guild . id , {
type : EventType . MessageDelete ,
causer , message ,
} ) ;
} ) ;
bot . on ( Events . MessageUpdate , async ( old , message ) = > {
if ( ! message . guild ) return ;
if ( message . content === old . content ) return ;
if ( message . partial || old . partial ) return ;
2024-03-25 02:13:32 +01:00
if ( message . flags . has ( 'Ephemeral' ) ) return ;
2024-03-18 13:09:49 +01:00
2024-03-18 20:19:14 +01:00
triggerEvent ( bot , message . guild . id , {
type : EventType . MessageEdit ,
causer : message.author ,
oldMessage : old , message : message ,
} ) ;
} ) ;
bot . on ( Events . InviteCreate , async invite = > {
if ( ! invite . guild || ! invite . inviter ) return ;
triggerEvent ( bot , invite . guild . id , {
type : EventType . InviteCreate ,
2024-03-25 02:13:32 +01:00
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 ,
2024-03-18 20:19:14 +01:00
} ) ;
} ) ;
bot . on ( Events . GuildAuditLogEntryCreate , async ( entry , guild ) = > {
2024-03-25 02:13:32 +01:00
const executor = entry . executor || ( entry . executorId ? await bot . users . fetch ( entry . executorId ) : null ) ;
2024-03-18 20:19:14 +01:00
if ( entry . action === AuditLogEvent . InviteDelete ) {
if ( ! entry . target ) return ;
triggerEvent ( bot , guild . id , {
type : EventType . InviteDelete ,
2024-03-25 02:13:32 +01:00
causer : executor ,
2024-03-18 20:19:14 +01:00
// @ts-expect-error shut up
invite : entry.target
} ) ;
} else if ( entry . action === AuditLogEvent . EmojiCreate ) {
2024-03-25 02:13:32 +01:00
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 ,
} ) ;
2024-03-18 20:19:14 +01:00
}
} ) ;
2024-03-17 22:22:03 +01:00
}