'use strict'; const { DiscordSnowflake } = require('@sapphire/snowflake'); const { AuditLogOptionsType, AuditLogEvent } = require('discord-api-types/v10'); const AutoModerationRule = require('./AutoModerationRule'); const { GuildScheduledEvent } = require('./GuildScheduledEvent'); const Integration = require('./Integration'); const Invite = require('./Invite'); const { StageInstance } = require('./StageInstance'); const { Sticker } = require('./Sticker'); const Webhook = require('./Webhook'); const Partials = require('../util/Partials'); const { flatten } = require('../util/Util'); const Targets = { All: 'All', Guild: 'Guild', GuildScheduledEvent: 'GuildScheduledEvent', Channel: 'Channel', User: 'User', Role: 'Role', Invite: 'Invite', Webhook: 'Webhook', Emoji: 'Emoji', Message: 'Message', Integration: 'Integration', StageInstance: 'StageInstance', Sticker: 'Sticker', Thread: 'Thread', ApplicationCommand: 'ApplicationCommand', AutoModeration: 'AutoModeration', Unknown: 'Unknown', }; /** * The target of a guild audit log entry. It can be one of: * * A guild * * A channel * * A user * * A role * * An invite * * A webhook * * An emoji * * A message * * An integration * * A stage instance * * A sticker * * A guild scheduled event * * A thread * * An application command * * An auto moderation rule * * An object with an id key if target was deleted or fake entity * * An object where the keys represent either the new value or the old value * @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker| * GuildScheduledEvent|ApplicationCommand|AutoModerationRule)} AuditLogEntryTarget */ /** * The action type of an entry, e.g. `Create`. Here are the available types: * * Create * * Delete * * Update * * All * @typedef {string} AuditLogActionType */ /** * The target type of an entry. Here are the available types: * * Guild * * Channel * * User * * Role * * Invite * * Webhook * * Emoji * * Message * * Integration * * StageInstance * * Sticker * * Thread * * GuildScheduledEvent * * ApplicationCommandPermission * @typedef {string} AuditLogTargetType */ /** * Audit logs entry. */ class GuildAuditLogsEntry { /** * Key mirror of all available audit log targets. * @type {Object} * @memberof GuildAuditLogsEntry */ static Targets = Targets; constructor(guild, data, logs) { /** * The target type of this entry * @type {AuditLogTargetType} */ this.targetType = GuildAuditLogsEntry.targetType(data.action_type); const targetType = this.targetType; /** * The action type of this entry * @type {AuditLogActionType} */ this.actionType = GuildAuditLogsEntry.actionType(data.action_type); /** * The type of action that occurred. * @type {AuditLogEvent} */ this.action = data.action_type; /** * The reason of this entry * @type {?string} */ this.reason = data.reason ?? null; /** * The id of the user that executed this entry * @type {?Snowflake} */ this.executorId = data.user_id; /** * The user that executed this entry * @type {?User} */ this.executor = data.user_id ? guild.client.options.partials.includes(Partials.User) ? guild.client.users._add({ id: data.user_id }) : guild.client.users.cache.get(data.user_id) ?? null : null; /** * An entry in the audit log representing a specific change. * @typedef {Object} AuditLogChange * @property {string} key The property that was changed, e.g. `nick` for nickname changes * For application command permissions updates the key is the id of the user, channel, * role, or a permission constant that was updated instead of an actual property name * @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname * @property {*} [new] The new value of the change, e.g. for nicknames, the new nickname */ /** * Specific property changes * @type {AuditLogChange[]} */ this.changes = data.changes?.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) ?? []; /** * The entry's id * @type {Snowflake} */ this.id = data.id; /** * Any extra data from the entry * @type {?(Object|Role|GuildMember)} */ this.extra = null; switch (data.action_type) { case AuditLogEvent.MemberPrune: this.extra = { removed: Number(data.options.members_removed), days: Number(data.options.delete_member_days), }; break; case AuditLogEvent.MemberMove: case AuditLogEvent.MessageDelete: case AuditLogEvent.MessageBulkDelete: this.extra = { channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id }, count: Number(data.options.count), }; break; case AuditLogEvent.MessagePin: case AuditLogEvent.MessageUnpin: this.extra = { channel: guild.client.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id }, messageId: data.options.message_id, }; break; case AuditLogEvent.MemberDisconnect: this.extra = { count: Number(data.options.count), }; break; case AuditLogEvent.ChannelOverwriteCreate: case AuditLogEvent.ChannelOverwriteUpdate: case AuditLogEvent.ChannelOverwriteDelete: switch (data.options.type) { case AuditLogOptionsType.Role: this.extra = guild.roles.cache.get(data.options.id) ?? { id: data.options.id, name: data.options.role_name, type: AuditLogOptionsType.Role, }; break; case AuditLogOptionsType.Member: this.extra = guild.members.cache.get(data.options.id) ?? { id: data.options.id, type: AuditLogOptionsType.Member, }; break; default: break; } break; case AuditLogEvent.StageInstanceCreate: case AuditLogEvent.StageInstanceDelete: case AuditLogEvent.StageInstanceUpdate: this.extra = { channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id }, }; break; case AuditLogEvent.ApplicationCommandPermissionUpdate: this.extra = { applicationId: data.options.application_id, }; break; case AuditLogEvent.AutoModerationBlockMessage: case AuditLogEvent.AutoModerationFlagToChannel: case AuditLogEvent.AutoModerationUserCommunicationDisabled: this.extra = { autoModerationRuleName: data.options.auto_moderation_rule_name, autoModerationRuleTriggerType: data.options.auto_moderation_rule_trigger_type, channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id }, }; break; default: break; } /** * The id of the target of this entry * @type {?Snowflake} */ this.targetId = data.target_id; /** * The target of this entry * @type {?AuditLogEntryTarget} */ this.target = null; if (targetType === Targets.Unknown) { this.target = this.changes.reduce((o, c) => { o[c.key] = c.new ?? c.old; return o; }, {}); this.target.id = data.target_id; // MemberDisconnect and similar types do not provide a target_id. } else if (targetType === Targets.User && data.target_id) { this.target = guild.client.options.partials.includes(Partials.User) ? guild.client.users._add({ id: data.target_id }) : guild.client.users.cache.get(data.target_id) ?? null; } else if (targetType === Targets.Guild) { this.target = guild.client.guilds.cache.get(data.target_id); } else if (targetType === Targets.Webhook) { this.target = logs?.webhooks.get(data.target_id) ?? new Webhook( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id, guild_id: guild.id, }, ), ); } else if (targetType === Targets.Invite) { let change = this.changes.find(c => c.key === 'code'); change = change.new ?? change.old; this.target = guild.invites.cache.get(change) ?? new Invite( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { guild }, ), ); } else if (targetType === Targets.Message) { // Discord sends a channel id for the MessageBulkDelete action type. this.target = data.action_type === AuditLogEvent.MessageBulkDelete ? guild.channels.cache.get(data.target_id) ?? { id: data.target_id } : guild.client.users.cache.get(data.target_id) ?? null; } else if (targetType === Targets.Integration) { this.target = logs?.integrations.get(data.target_id) ?? new Integration( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id }, ), guild, ); } else if (targetType === Targets.Channel || targetType === Targets.Thread) { this.target = guild.channels.cache.get(data.target_id) ?? this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id }, ); } else if (targetType === Targets.StageInstance) { this.target = guild.stageInstances.cache.get(data.target_id) ?? new StageInstance( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id, channel_id: data.options?.channel_id, guild_id: guild.id, }, ), ); } else if (targetType === Targets.Sticker) { this.target = guild.stickers.cache.get(data.target_id) ?? new Sticker( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id }, ), ); } else if (targetType === Targets.GuildScheduledEvent) { this.target = guild.scheduledEvents.cache.get(data.target_id) ?? new GuildScheduledEvent( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id, guild_id: guild.id }, ), ); } else if (targetType === Targets.ApplicationCommand) { this.target = logs?.applicationCommands.get(data.target_id) ?? { id: data.target_id }; } else if (targetType === Targets.AutoModeration) { this.target = guild.autoModerationRules.cache.get(data.target_id) ?? new AutoModerationRule( guild.client, this.changes.reduce( (o, c) => { o[c.key] = c.new ?? c.old; return o; }, { id: data.target_id, guild_id: guild.id }, ), guild, ); } else if (data.target_id) { this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id }; } } /** * Finds the target type of a guild audit log entry. * @param {AuditLogEvent} target The action target * @returns {AuditLogTargetType} */ static targetType(target) { if (target < 10) return Targets.Guild; if (target < 20) return Targets.Channel; if (target < 30) return Targets.User; if (target < 40) return Targets.Role; if (target < 50) return Targets.Invite; if (target < 60) return Targets.Webhook; if (target < 70) return Targets.Emoji; if (target < 80) return Targets.Message; if (target < 83) return Targets.Integration; if (target < 86) return Targets.StageInstance; if (target < 100) return Targets.Sticker; if (target < 110) return Targets.GuildScheduledEvent; if (target < 120) return Targets.Thread; if (target < 130) return Targets.ApplicationCommand; if (target >= 140 && target < 150) return Targets.AutoModeration; return Targets.Unknown; } /** * Finds the action type from the guild audit log entry action. * @param {AuditLogEvent} action The action target * @returns {AuditLogActionType} */ static actionType(action) { if ( [ AuditLogEvent.ChannelCreate, AuditLogEvent.ChannelOverwriteCreate, AuditLogEvent.MemberBanRemove, AuditLogEvent.BotAdd, AuditLogEvent.RoleCreate, AuditLogEvent.InviteCreate, AuditLogEvent.WebhookCreate, AuditLogEvent.EmojiCreate, AuditLogEvent.MessagePin, AuditLogEvent.IntegrationCreate, AuditLogEvent.StageInstanceCreate, AuditLogEvent.StickerCreate, AuditLogEvent.GuildScheduledEventCreate, AuditLogEvent.ThreadCreate, AuditLogEvent.AutoModerationRuleCreate, AuditLogEvent.AutoModerationBlockMessage, ].includes(action) ) { return 'Create'; } if ( [ AuditLogEvent.ChannelDelete, AuditLogEvent.ChannelOverwriteDelete, AuditLogEvent.MemberKick, AuditLogEvent.MemberPrune, AuditLogEvent.MemberBanAdd, AuditLogEvent.MemberDisconnect, AuditLogEvent.RoleDelete, AuditLogEvent.InviteDelete, AuditLogEvent.WebhookDelete, AuditLogEvent.EmojiDelete, AuditLogEvent.MessageDelete, AuditLogEvent.MessageBulkDelete, AuditLogEvent.MessageUnpin, AuditLogEvent.IntegrationDelete, AuditLogEvent.StageInstanceDelete, AuditLogEvent.StickerDelete, AuditLogEvent.GuildScheduledEventDelete, AuditLogEvent.ThreadDelete, AuditLogEvent.AutoModerationRuleDelete, ].includes(action) ) { return 'Delete'; } if ( [ AuditLogEvent.GuildUpdate, AuditLogEvent.ChannelUpdate, AuditLogEvent.ChannelOverwriteUpdate, AuditLogEvent.MemberUpdate, AuditLogEvent.MemberRoleUpdate, AuditLogEvent.MemberMove, AuditLogEvent.RoleUpdate, AuditLogEvent.InviteUpdate, AuditLogEvent.WebhookUpdate, AuditLogEvent.EmojiUpdate, AuditLogEvent.IntegrationUpdate, AuditLogEvent.StageInstanceUpdate, AuditLogEvent.StickerUpdate, AuditLogEvent.GuildScheduledEventUpdate, AuditLogEvent.ThreadUpdate, AuditLogEvent.ApplicationCommandPermissionUpdate, AuditLogEvent.AutoModerationRuleUpdate, ].includes(action) ) { return 'Update'; } return 'All'; } /** * The timestamp this entry was created at * @type {number} * @readonly */ get createdTimestamp() { return DiscordSnowflake.timestampFrom(this.id); } /** * The time this entry was created at * @type {Date} * @readonly */ get createdAt() { return new Date(this.createdTimestamp); } toJSON() { return flatten(this, { createdTimestamp: true }); } } module.exports = GuildAuditLogsEntry;