diff --git a/assets/localisations/events/eng-US.json b/assets/localisations/events/eng-US.json index f6d276b91..2580f34ff 100644 --- a/assets/localisations/events/eng-US.json +++ b/assets/localisations/events/eng-US.json @@ -14,6 +14,8 @@ "events.messageUpdate.description": "{user} has updated their message in {channel}.", "events.messageUpdate.fields.before": "Before", "events.messageUpdate.fields.after": "After", + "events.memberKick.title": "User kicked", + "events.memberKick.description": "{user} has been kicked by {moderator}.", "events.entryRequestAccept.title": "Entry request accepted", "events.entryRequestAccept.description": "{user}'s entry request has been accepted by {moderator}.", "events.entryRequestReject.title": "Entry request rejected", diff --git a/source/constants/contexts.ts b/source/constants/contexts.ts index 89ce381a9..a1d9ee02b 100644 --- a/source/constants/contexts.ts +++ b/source/constants/contexts.ts @@ -1214,6 +1214,10 @@ export default Object.freeze({ after: localise("events.messageUpdate.fields.after", locale)(), }, }), + memberKick: ({ localise, locale }) => ({ + title: localise("events.memberKick.title", locale)(), + description: localise("events.memberKick.description", locale), + }), entryRequestAccept: ({ localise, locale }) => ({ title: localise("events.entryRequestAccept.title", locale)(), description: localise("events.entryRequestAccept.description", locale), diff --git a/source/constants/emojis.ts b/source/constants/emojis.ts index 79574ed03..f2d23944d 100644 --- a/source/constants/emojis.ts +++ b/source/constants/emojis.ts @@ -20,6 +20,7 @@ export default Object.freeze({ unbanned: "😇", joined: "😁", left: "😔", + kicked: "🚪", }, message: { updated: "⬆️", diff --git a/source/library/stores/journalling.ts b/source/library/stores/journalling.ts index 4d830eec7..32a5eed22 100644 --- a/source/library/stores/journalling.ts +++ b/source/library/stores/journalling.ts @@ -132,12 +132,22 @@ class JournallingStore { } async #guildMemberRemove(user: Discord.User, guildId: bigint): Promise { - const wasKicked = await this.#wasKicked({ user, guildId }); - if (wasKicked) { - await this.tryLog("guildMemberKick", { guildId, args: [user, guildId] }); - } else { - await this.tryLog("guildMemberRemove", { guildId, args: [user, guildId] }); + const kickInformation = await this.#getKickInformation({ user, guildId }); + if (kickInformation !== undefined) { + if (kickInformation.userId === null) { + return; + } + + const authorMember = this.#client.entities.members.get(guildId)?.get(BigInt(kickInformation.userId)); + if (authorMember === undefined) { + return; + } + + await this.tryLog("guildMemberKick", { guildId, args: [user, authorMember] }); + return; } + + await this.tryLog("guildMemberRemove", { guildId, args: [user, guildId] }); } async #messageDelete( @@ -161,13 +171,25 @@ class JournallingStore { await this.tryLog("messageUpdate", { guildId, args: [message, oldMessage] }); } - async #wasKicked({ user, guildId }: { user: Logos.User; guildId: bigint }): Promise { + async #getKickInformation({ + user, + guildId, + }: { user: Logos.User; guildId: bigint }): Promise { const now = Date.now(); - const auditLog = await this.#client.bot.helpers.getAuditLog(guildId, { actionType: AuditLogEvents.MemberKick }); + const auditLog = await this.#client.bot.helpers + .getAuditLog(guildId, { actionType: AuditLogEvents.MemberKick }) + .catch((reason) => { + this.log.warn(`Could not get audit log for ${this.#client.diagnostics.guild(guildId)}:`, reason); + return undefined; + }); + if (auditLog === undefined) { + return undefined; + } + return auditLog.auditLogEntries .filter((entry) => snowflakeToTimestamp(BigInt(entry.id)) >= now - constants.time.second * 5) - .some((entry) => entry.targetId === user.id.toString()); + .find((entry) => entry.targetId === user.id.toString()); } } diff --git a/source/library/stores/journalling/logos/guild-member-kick.ts b/source/library/stores/journalling/logos/guild-member-kick.ts new file mode 100644 index 000000000..109099cdc --- /dev/null +++ b/source/library/stores/journalling/logos/guild-member-kick.ts @@ -0,0 +1,22 @@ +import type { EventLogger } from "logos/stores/journalling/loggers"; + +const logger: EventLogger<"guildMemberKick"> = (client, [user, author], { guildLocale }) => { + const strings = constants.contexts.memberKick({ + localise: client.localise.bind(client), + locale: guildLocale, + }); + return { + embeds: [ + { + title: `${constants.emojis.events.user.kicked} ${strings.title}`, + color: constants.colours.warning, + description: strings.description({ + user: client.diagnostics.user(user), + moderator: client.diagnostics.member(author), + }), + }, + ], + }; +}; + +export default logger; diff --git a/source/types.d.ts b/source/types.d.ts index e168904d2..4d042380f 100644 --- a/source/types.d.ts +++ b/source/types.d.ts @@ -100,7 +100,7 @@ declare global { /** Type representing events that occur within a guild. */ type Events = { /** Fill-in Discord event for a member having been kicked. */ - guildMemberKick: [user: Logos.User, guildId: bigint]; + guildMemberKick: [user: Logos.User, by: Logos.Member]; } & { /** An entry request has been submitted. */ entryRequestSubmit: [user: Logos.User, entryRequest: EntryRequest];