diff options
author | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2022-01-07 16:12:28 -0500 |
---|---|---|
committer | IRONM00N <64110067+IRONM00N@users.noreply.github.com> | 2022-01-07 16:12:28 -0500 |
commit | b4d6c2c94d1fb3f6646503834d5cecd9d68d113a (patch) | |
tree | 5ab012e61b1c497729b8e9a5beb54fb06e5cd1c8 | |
parent | db68c02e9d84322f729c17afd9d293d449238ca4 (diff) | |
download | tanzanite-b4d6c2c94d1fb3f6646503834d5cecd9d68d113a.tar.gz tanzanite-b4d6c2c94d1fb3f6646503834d5cecd9d68d113a.tar.bz2 tanzanite-b4d6c2c94d1fb3f6646503834d5cecd9d68d113a.zip |
sync punishments
18 files changed, 291 insertions, 35 deletions
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts index c018852..e5cb872 100644 --- a/src/lib/common/util/Moderation.ts +++ b/src/lib/common/util/Moderation.ts @@ -93,8 +93,7 @@ export class Moderation { const user = (await util.resolveNonCachedUser(options.user))!.id; const moderator = (await util.resolveNonCachedUser(options.moderator))!.id; const guild = client.guilds.resolveId(options.guild)!; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const duration = options.duration || undefined; + const duration = options.duration ? options.duration : undefined; // If guild does not exist create it so the modlog can reference a guild. await Guild.findOrCreate({ diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index d7416bb..9a8e408 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -682,11 +682,11 @@ export class BushClientUtil extends ClientUtil { .filter( (p, i, arr) => typeof Object.getOwnPropertyDescriptor(obj_, p)?.['get'] !== 'function' && // ignore getters - typeof Object.getOwnPropertyDescriptor(obj_, p)?.['set'] !== 'function' && // ignore setters - typeof obj_[p] === 'function' && //only the methods - p !== 'constructor' && //not the constructor - (i == 0 || p !== arr[i - 1]) && //not overriding in this prototype - props.indexOf(p) === -1 //not overridden in a child + typeof Object.getOwnPropertyDescriptor(obj_, p)?.['set'] !== 'function' && // ignore setters + typeof obj_[p] === 'function' && // only the methods + p !== 'constructor' && // not the constructor + (i == 0 || p !== arr[i - 1]) && // not overriding in this prototype + props.indexOf(p) === -1 // not overridden in a child ); const reg = /\(([\s\S]*?)\)/; @@ -705,8 +705,8 @@ export class BushClientUtil extends ClientUtil { ) ); } while ( - (obj_ = Object.getPrototypeOf(obj_)) && //walk-up the prototype chain - Object.getPrototypeOf(obj_) //not the the Object prototype methods (hasOwnProperty, etc...) + (obj_ = Object.getPrototypeOf(obj_)) && // walk-up the prototype chain + Object.getPrototypeOf(obj_) // not the the Object prototype methods (hasOwnProperty, etc...) ); return props.join('\n'); @@ -801,8 +801,6 @@ export class BushClientUtil extends ClientUtil { : message.util.parsed?.prefix ?? client.config.prefix; } - // public retryAsync<P extends [], R>(func: (...args: P) => R, repeatFreq: number, numRepeat: number): R | Promise<null> {} - /** * Recursively apply provided options operations on object * and all of the object properties that are either object or function. @@ -849,6 +847,11 @@ export class BushClientUtil extends ClientUtil { return deepLock; } + public get time(): Record<keyof typeof client.constants.timeUnits, number> { + const values = Object.entries(client.constants.timeUnits).map(([key, value]) => [key, value.value]); + return Object.fromEntries(values); + } + /** * A wrapper for the Argument class that adds custom typings. */ diff --git a/src/listeners/member-custom/bushBan.ts b/src/listeners/member-custom/bushBan.ts index 1243071..c37d872 100644 --- a/src/listeners/member-custom/bushBan.ts +++ b/src/listeners/member-custom/bushBan.ts @@ -23,8 +23,7 @@ export default class BushBanListener extends BushListener { .addField('**Action**', `${duration ? 'Temp Ban' : 'Perm Ban'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (duration) logEmbed.addField('**Duration**', util.humanizeDuration(duration)); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); diff --git a/src/listeners/member-custom/bushBlock.ts b/src/listeners/member-custom/bushBlock.ts index 8e8adb6..13523da 100644 --- a/src/listeners/member-custom/bushBlock.ts +++ b/src/listeners/member-custom/bushBlock.ts @@ -26,8 +26,7 @@ export default class BushBlockListener extends BushListener { .addField('**Channel**', `<#${channel.id}>`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (duration) logEmbed.addField('**Duration**', `${util.humanizeDuration(duration) || duration}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); diff --git a/src/listeners/member-custom/bushKick.ts b/src/listeners/member-custom/bushKick.ts index 26e9617..52b4ff2 100644 --- a/src/listeners/member-custom/bushKick.ts +++ b/src/listeners/member-custom/bushKick.ts @@ -23,8 +23,7 @@ export default class BushKickListener extends BushListener { .addField('**Action**', `${'Kick'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushMute.ts b/src/listeners/member-custom/bushMute.ts index a8637fd..844ae55 100644 --- a/src/listeners/member-custom/bushMute.ts +++ b/src/listeners/member-custom/bushMute.ts @@ -23,8 +23,7 @@ export default class BushMuteListener extends BushListener { .addField('**Action**', `${duration ? 'Temp Mute' : 'Perm Mute'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (duration) logEmbed.addField('**Duration**', `${util.humanizeDuration(duration) || duration}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); diff --git a/src/listeners/member-custom/bushPunishRole.ts b/src/listeners/member-custom/bushPunishRole.ts index 731403b..9abac3c 100644 --- a/src/listeners/member-custom/bushPunishRole.ts +++ b/src/listeners/member-custom/bushPunishRole.ts @@ -23,8 +23,7 @@ export default class BushPunishRoleListener extends BushListener { .addField('**Action**', `${duration ? 'Temp Punishment Role' : 'Perm Punishment Role'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (duration) logEmbed.addField('**Duration**', util.humanizeDuration(duration)); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushPunishRoleRemove.ts b/src/listeners/member-custom/bushPunishRoleRemove.ts index 7d88ec8..a24546e 100644 --- a/src/listeners/member-custom/bushPunishRoleRemove.ts +++ b/src/listeners/member-custom/bushPunishRoleRemove.ts @@ -24,8 +24,7 @@ export default class BushPunishRoleRemoveListener extends BushListener { .addField('**Role**', `${role}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushRemoveTimeout.ts b/src/listeners/member-custom/bushRemoveTimeout.ts index 30a3ab4..0f41039 100644 --- a/src/listeners/member-custom/bushRemoveTimeout.ts +++ b/src/listeners/member-custom/bushRemoveTimeout.ts @@ -23,8 +23,7 @@ export default class BushRemoveTimeoutListener extends BushListener { .addField('**Action**', `${'Remove Timeout'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushTimeout.ts b/src/listeners/member-custom/bushTimeout.ts index 63ba41d..a311710 100644 --- a/src/listeners/member-custom/bushTimeout.ts +++ b/src/listeners/member-custom/bushTimeout.ts @@ -25,8 +25,7 @@ export default class BushTimeoutListener extends BushListener { .addField('**Action**', `${'Timeout'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`) + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`) .addField('**Duration**', `${util.humanizeDuration(duration) || duration}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); diff --git a/src/listeners/member-custom/bushUnban.ts b/src/listeners/member-custom/bushUnban.ts index e7024ef..b2426e3 100644 --- a/src/listeners/member-custom/bushUnban.ts +++ b/src/listeners/member-custom/bushUnban.ts @@ -23,8 +23,7 @@ export default class BushUnbanListener extends BushListener { .addField('**Action**', `${'Unban'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushUnblock.ts b/src/listeners/member-custom/bushUnblock.ts index e313025..43097a1 100644 --- a/src/listeners/member-custom/bushUnblock.ts +++ b/src/listeners/member-custom/bushUnblock.ts @@ -24,8 +24,7 @@ export default class BushUnblockListener extends BushListener { .addField('**Channel**', `<#${channel.id}>`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushUnmute.ts b/src/listeners/member-custom/bushUnmute.ts index 4fa808f..89bca46 100644 --- a/src/listeners/member-custom/bushUnmute.ts +++ b/src/listeners/member-custom/bushUnmute.ts @@ -23,8 +23,7 @@ export default class BushUnmuteListener extends BushListener { .addField('**Action**', `${'Unmute'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/member-custom/bushWarn.ts b/src/listeners/member-custom/bushWarn.ts index dba9fd8..af8fa62 100644 --- a/src/listeners/member-custom/bushWarn.ts +++ b/src/listeners/member-custom/bushWarn.ts @@ -23,8 +23,7 @@ export default class BushWarnListener extends BushListener { .addField('**Action**', `${'Warn'}`) .addField('**User**', `${user} (${user.tag})`) .addField('**Moderator**', `${moderator} (${moderator.tag})`) - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - .addField('**Reason**', `${reason || '[No Reason Provided]'}`); + .addField('**Reason**', `${reason ? reason : '[No Reason Provided]'}`); if (dmSuccess === false) logEmbed.addField('**Additional Info**', 'Could not dm user.'); return await logChannel.send({ embeds: [logEmbed] }); } diff --git a/src/listeners/track-manual-punishments/modlogSyncBan.ts b/src/listeners/track-manual-punishments/modlogSyncBan.ts new file mode 100644 index 0000000..7fe2f7e --- /dev/null +++ b/src/listeners/track-manual-punishments/modlogSyncBan.ts @@ -0,0 +1,65 @@ +import { BushListener, BushUser, Moderation, ModLogType, type BushClientEvents } from '#lib'; +import { MessageEmbed } from 'discord.js'; + +export default class ModlogSyncBanListener extends BushListener { + public constructor() { + super('modlogSyncBan', { + emitter: 'client', + event: 'guildBanAdd', + category: 'guild' + }); + } + + public override async exec(...[ban]: BushClientEvents['guildBanAdd']) { + if (!(await ban.guild.hasFeature('logManualPunishments'))) return; + if (!ban.guild.me!.permissions.has('VIEW_AUDIT_LOG')) { + return ban.guild.error( + 'modlogSyncBan', + `Could not sync the manual ban of ${ban.user.tag} to the modlog because I do not have the "View Audit Log" permission.` + ); + } + + const now = new Date(); + await util.sleep(0.5); // wait for audit log entry + + const logs = (await ban.guild.fetchAuditLogs({ type: 'MEMBER_BAN_ADD' })).entries.filter( + (entry) => entry.target?.id === ban.user.id + ); + + const first = logs.first(); + if (!first) return; + + if (!first.executor || first.executor?.bot) return; + + if (Math.abs(first.createdAt.getTime() - now.getTime()) > util.time.minutes) { + console.log(util.humanizeDuration(Math.abs(first.createdAt.getTime() - now.getTime()))); + throw new Error('Time is off by over a minute'); + } + + const { log } = await Moderation.createModLogEntry({ + type: ModLogType.PERM_BAN, + user: ban.user, + moderator: <BushUser>first.executor, + reason: `[Manual] ${first.reason ? first.reason : 'No reason given'}`, + guild: ban.guild + }); + if (!log) throw new Error('Failed to create modlog entry'); + + const logChannel = await ban.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new MessageEmbed() + .setColor(util.colors.discord.RED) + .setTimestamp() + .setFooter({ text: `CaseID: ${log.id}` }) + .setAuthor({ + name: ban.user.tag, + iconURL: ban.user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined + }) + .addField('**Action**', `${'Manual Ban'}`) + .addField('**User**', `${ban.user} (${ban.user.tag})`) + .addField('**Moderator**', `${first.executor} (${first.executor.tag})`) + .addField('**Reason**', `${first.reason ? first.reason : '[No Reason Provided]'}`); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/track-manual-punishments/modlogSyncKick.ts b/src/listeners/track-manual-punishments/modlogSyncKick.ts new file mode 100644 index 0000000..7c06c4d --- /dev/null +++ b/src/listeners/track-manual-punishments/modlogSyncKick.ts @@ -0,0 +1,65 @@ +import { BushListener, BushUser, Moderation, ModLogType, type BushClientEvents } from '#lib'; +import { MessageEmbed } from 'discord.js'; + +export default class ModlogSyncKickListener extends BushListener { + public constructor() { + super('modlogSyncKick', { + emitter: 'client', + event: 'guildMemberRemove', + category: 'guild' + }); + } + + public override async exec(...[member]: BushClientEvents['guildMemberRemove']) { + if (!(await member.guild.hasFeature('logManualPunishments'))) return; + if (!member.guild.me!.permissions.has('VIEW_AUDIT_LOG')) { + return member.guild.error( + 'modlogSyncKick', + `Could not sync the potential manual kick of ${member.user.tag} to the modlog because I do not have the "View Audit Log" permission.` + ); + } + + const now = new Date(); + await util.sleep(0.5); // wait for audit log entry + + const logs = (await member.guild.fetchAuditLogs({ type: 'MEMBER_KICK' })).entries.filter( + (entry) => entry.target?.id === member.user.id + ); + + const first = logs.first(); + if (!first) return; + + if (!first.executor || first.executor?.bot) return; + + if (Math.abs(first.createdAt.getTime() - now.getTime()) > util.time.minutes) { + console.log(util.humanizeDuration(Math.abs(first.createdAt.getTime() - now.getTime()))); + throw new Error('Time is off by over a minute'); + } + + const { log } = await Moderation.createModLogEntry({ + type: ModLogType.KICK, + user: member.user, + moderator: <BushUser>first.executor, + reason: `[Manual] ${first.reason ? first.reason : 'No reason given'}`, + guild: member.guild + }); + if (!log) throw new Error('Failed to create modlog entry'); + + const logChannel = await member.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new MessageEmbed() + .setColor(util.colors.discord.RED) + .setTimestamp() + .setFooter({ text: `CaseID: ${log.id}` }) + .setAuthor({ + name: member.user.tag, + iconURL: member.user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined + }) + .addField('**Action**', `${'Manual Kick'}`) + .addField('**User**', `${member.user} (${member.user.tag})`) + .addField('**Moderator**', `${first.executor} (${first.executor.tag})`) + .addField('**Reason**', `${first.reason ? first.reason : '[No Reason Provided]'}`); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/track-manual-punishments/modlogSyncTimeout.ts b/src/listeners/track-manual-punishments/modlogSyncTimeout.ts new file mode 100644 index 0000000..e29af7e --- /dev/null +++ b/src/listeners/track-manual-punishments/modlogSyncTimeout.ts @@ -0,0 +1,71 @@ +import { BushListener, BushUser, Moderation, ModLogType, type BushClientEvents } from '#lib'; +import { MessageEmbed } from 'discord.js'; + +export default class ModlogSyncTimeoutListener extends BushListener { + public constructor() { + super('modlogSyncTimeout', { + emitter: 'client', + event: 'guildMemberUpdate', + category: 'guild' + }); + } + + public override async exec(...[_oldMember, newMember]: BushClientEvents['guildMemberUpdate']) { + if (!(await newMember.guild.hasFeature('logManualPunishments'))) return; + if (!newMember.guild.me!.permissions.has('VIEW_AUDIT_LOG')) { + return newMember.guild.error( + 'modlogSyncTimeout', + `Could not sync the potential manual timeout of ${newMember.user.tag} to the modlog because I do not have the "View Audit Log" permission.` + ); + } + + const now = new Date(); + await util.sleep(0.5); // wait for audit log entry + + const logs = (await newMember.guild.fetchAuditLogs({ type: 'MEMBER_UPDATE' })).entries.filter( + (entry) => entry.target?.id === newMember.user.id + ); + + const first = logs.first(); + if (!first) return; + + if (!first.executor || first.executor?.bot) return; + + const timeOut = first.changes?.find((changes) => changes.key === 'communication_disabled_until'); + if (!timeOut) return; + + if (Math.abs(first.createdAt.getTime() - now.getTime()) > util.time.minutes) { + console.log(util.humanizeDuration(Math.abs(first.createdAt.getTime() - now.getTime()))); + throw new Error('Time is off by over a minute'); + } + + const newTime = <string | null>timeOut.new ? new Date(<string>timeOut.new) : null; + + const { log } = await Moderation.createModLogEntry({ + type: newTime ? ModLogType.TIMEOUT : ModLogType.REMOVE_TIMEOUT, + user: newMember.user, + moderator: <BushUser>first.executor, + reason: `[Manual] ${first.reason ? first.reason : 'No reason given'}`, + guild: newMember.guild, + duration: newTime ? newTime.getTime() - now.getTime() : undefined + }); + if (!log) throw new Error('Failed to create modlog entry'); + + const logChannel = await newMember.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new MessageEmbed() + .setColor(util.colors.discord[newTime ? 'ORANGE' : 'GREEN']) + .setTimestamp() + .setFooter({ text: `CaseID: ${log.id}` }) + .setAuthor({ + name: newMember.user.tag, + iconURL: newMember.user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined + }) + .addField('**Action**', `${newTime ? 'Manual Timeout' : 'Manual Remove Timeout'}`) + .addField('**User**', `${newMember.user} (${newMember.user.tag})`) + .addField('**Moderator**', `${first.executor} (${first.executor.tag})`) + .addField('**Reason**', `${first.reason ? first.reason : '[No Reason Provided]'}`); + return await logChannel.send({ embeds: [logEmbed] }); + } +} diff --git a/src/listeners/track-manual-punishments/modlogSyncUnban.ts b/src/listeners/track-manual-punishments/modlogSyncUnban.ts new file mode 100644 index 0000000..67031b0 --- /dev/null +++ b/src/listeners/track-manual-punishments/modlogSyncUnban.ts @@ -0,0 +1,65 @@ +import { BushListener, BushUser, Moderation, ModLogType, type BushClientEvents } from '#lib'; +import { MessageEmbed } from 'discord.js'; + +export default class ModlogSyncUnbanListener extends BushListener { + public constructor() { + super('modlogSyncUnban', { + emitter: 'client', + event: 'guildBanRemove', + category: 'guild' + }); + } + + public override async exec(...[ban]: BushClientEvents['guildBanRemove']) { + if (!(await ban.guild.hasFeature('logManualPunishments'))) return; + if (!ban.guild.me!.permissions.has('VIEW_AUDIT_LOG')) { + return ban.guild.error( + 'modlogSyncBan', + `Could not sync the manual unban of ${ban.user.tag} to the modlog because I do not have the "View Audit Log" permission.` + ); + } + + const now = new Date(); + await util.sleep(0.5); // wait for audit log entry + + const logs = (await ban.guild.fetchAuditLogs({ type: 'MEMBER_BAN_REMOVE' })).entries.filter( + (entry) => entry.target?.id === ban.user.id + ); + + const first = logs.first(); + if (!first) return; + + if (!first.executor || first.executor?.bot) return; + + if (Math.abs(first.createdAt.getTime() - now.getTime()) > util.time.minutes) { + console.log(util.humanizeDuration(Math.abs(first.createdAt.getTime() - now.getTime()))); + throw new Error('Time is off by over a minute'); + } + + const { log } = await Moderation.createModLogEntry({ + type: ModLogType.UNBAN, + user: ban.user, + moderator: <BushUser>first.executor, + reason: `[Manual] ${first.reason ? first.reason : 'No reason given'}`, + guild: ban.guild + }); + if (!log) throw new Error('Failed to create modlog entry'); + + const logChannel = await ban.guild.getLogChannel('moderation'); + if (!logChannel) return; + + const logEmbed = new MessageEmbed() + .setColor(util.colors.discord.ORANGE) + .setTimestamp() + .setFooter({ text: `CaseID: ${log.id}` }) + .setAuthor({ + name: ban.user.tag, + iconURL: ban.user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined + }) + .addField('**Action**', `${'Manual Unban'}`) + .addField('**User**', `${ban.user} (${ban.user.tag})`) + .addField('**Moderator**', `${first.executor} (${first.executor.tag})`) + .addField('**Reason**', `${first.reason ? first.reason : '[No Reason Provided]'}`); + return await logChannel.send({ embeds: [logEmbed] }); + } +} |