diff --git a/src/structure/components/observers/AuditLog.js b/src/structure/components/observers/AuditLog.js new file mode 100644 index 0000000..5ad7e5e --- /dev/null +++ b/src/structure/components/observers/AuditLog.js @@ -0,0 +1,126 @@ +const { Observer, Infraction } = require("../../interfaces"); +const { GuildWrapper } = require("../../client/wrappers"); + +class AuditLogObserver extends Observer { + + constructor(client) { + + super(client, { + name: 'auditLog', + priority: 0, + disabled: false + }); + + this.client = client; + + this.hooks = [ + ['guildBanAdd', this.guildBanAdd.bind(this)], + ['guildBanRemove', this.guildBanRemove.bind(this)], + ['guildMemberRemove', this.guildMemberRemove.bind(this)], + ['guildMemberUpdate', this.guildMemberUpdate.bind(this)] + ]; + + } + + async guildBanAdd({ guild, user }) { + const wrapper = new GuildWrapper(this.client, guild); + const settings = await wrapper.settings(); + if (!settings.moderation.channel || !settings.moderation.infractions.includes('BAN')) return undefined; //This is checked by the infraction handling, but it may save resources if checked earlier. + const audit = await this._fetchFirstEntry(guild, user, 'MEMBER_BAN_ADD'); + if (!audit) return undefined; + new Infraction(this.client, { + type: 'BAN', + targetType: 'USER', + guild: wrapper, + target: user, + executor: audit.executor, + reason: `${audit.reason || 'N/A'}\n*This action was performed without the bot.*` + }).handle(); + } + + async guildBanRemove({ guild, user }) { + const wrapper = new GuildWrapper(this.client, guild); + const settings = await wrapper.settings(); + if (!settings.moderation.channel || !settings.moderation.infractions.includes('UNBAN')) return undefined; //This is checked by the infraction handling, but it may save resources if checked earlier. + const audit = await this._fetchFirstEntry(guild, user, 'MEMBER_BAN_REMOVE'); + if (!audit) return undefined; + new Infraction(this.client, { + type: 'UNBAN', + targetType: 'USER', + guild: wrapper, + target: user, + executor: audit.executor, + reason: `*This action was performed without the bot.*` + }).handle(); + } + + async guildMemberRemove(member) { + const wrapper = new GuildWrapper(this.client, member.guild); + const settings = await wrapper.settings(); + if (!settings.moderation.channel || !settings.moderation.infractions.includes('KICK')) return undefined; //This is checked by the infraction handling, but it may save resources if checked earlier. + const audit = await this._fetchFirstEntry(member.guild, member.user, 'MEMBER_KICK'); + if (!audit) return undefined; + new Infraction(this.client, { + type: 'KICK', + targetType: 'USER', + guild: wrapper, + target: member.user, + executor: audit.executor, + reason: `${audit.reason || 'N/A'}\n*This action was performed without the bot.*` + }).handle(); + } + + async guildMemberUpdate(oldMember, newMember) { + if (oldMember.roles.cache.size === newMember.roles.cache.size) return undefined; + const { guild } = newMember; + const wrapper = new GuildWrapper(this.client, guild); + const settings = await wrapper.settings(); + if (!settings.moderation.channel) return undefined; + + const mutedRole = settings.mute.role; + if (!mutedRole) return undefined; + + if (!oldMember.roles.cache.has(mutedRole) && newMember.roles.cache.has(mutedRole)) { //Member was assigned muted role. + if (!settings.moderation.infractions.includes('MUTE')) return undefined; + const audit = await this._fetchFirstEntry(newMember.guild, newMember.user, 'MEMBER_ROLE_UPDATE'); + if (!audit) return undefined; + new Infraction(this.client, { + type: 'MUTE', + targetType: 'USER', + guild: wrapper, + target: newMember.user, + executor: audit.executor, + reason: "*This action was performed without the bot.*" + }).handle(); + } else if (oldMember.roles.cache.has(mutedRole) && !newMember.roles.cache.has(mutedRole)) { //Member was unassigned muted role. + if (!settings.moderation.infractions.includes('UNMUTE')) return undefined; + const audit = await this._fetchFirstEntry(newMember.guild, newMember.user, 'MEMBER_ROLE_UPDATE'); + if (!audit) return undefined; + new Infraction(this.client, { + type: 'UNMUTE', + targetType: 'USER', + guild: wrapper, + target: newMember.user, + executor: audit.executor, + reason: "*This action was performed without the bot.*" + }).handle(); + } + + } + + async _fetchFirstEntry(guild, user, type) { + if (!guild.me.permissions.has('VIEW_AUDIT_LOG')) return null; + const audit = await guild.fetchAuditLogs({ limit: 1 }); + if (audit.entries.size === 0) return null; + + const entry = audit.entries.filter((e) => e?.target?.id === user.id).first(); + if (!entry || entry.executor.id === this.client.user.id) return null; + if (entry.target.id !== user.id) return null; + if (entry.action !== type) return null; + + return entry; + } + +} + +module.exports = AuditLogObserver; \ No newline at end of file