diff options
25 files changed, 564 insertions, 131 deletions
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 40ac506..f21ccdc 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -15,7 +15,7 @@ export default class BanCommand extends BushCommand { aliases: ['ban', 'force-ban', 'dban'], category: 'moderation', description: 'Ban a member from the server.', - usage: ['ban <member> <reason> [--delete]'], + usage: ['ban <member> [reasonAndDuration] [--delete]'], examples: ['ban ironm00n 1 day commands in #general --delete 7'], args: [ { @@ -27,10 +27,10 @@ export default class BanCommand extends BushCommand { slashType: 'USER' }, { - id: 'reason', + id: 'reason_and_duration', description: 'The reason and duration of the ban.', type: 'contentWithDuration', - match: 'restContent', + match: 'rest', prompt: 'Why should this user be banned and for how long?', retry: '{error} Choose a valid ban reason and duration.', slashType: 'STRING', @@ -70,12 +70,12 @@ export default class BanCommand extends BushCommand { message: BushMessage | BushSlashMessage, args: { user: ArgType<'user'> | ArgType<'snowflake'>; - reason: OptionalArgType<'contentWithDuration'>; + reason_and_duration: OptionalArgType<'contentWithDuration'>; days: OptionalArgType<'integer'>; force: boolean; } ) { - if (args.reason && typeof args.reason === 'object') args.reason.duration ??= 0; + if (args.reason_and_duration && typeof args.reason_and_duration === 'object') args.reason_and_duration.duration ??= 0; args.days ??= 0; if (!message.guild) return message.util.reply(`${util.emojis.error} This command cannot be used in dms.`); @@ -100,10 +100,13 @@ export default class BanCommand extends BushCommand { } let time: number | null; - if (args.reason) { - time = typeof args.reason === 'string' ? await util.arg.cast('duration', message, args.reason) : args.reason.duration; + if (args.reason_and_duration) { + time = + typeof args.reason_and_duration === 'string' + ? await util.arg.cast('duration', message, args.reason_and_duration) + : args.reason_and_duration.duration; } - const parsedReason = args.reason?.contentWithoutTime ?? null; + const parsedReason = args.reason_and_duration?.contentWithoutTime ?? null; const responseCode = member ? await member.bushBan({ @@ -120,7 +123,7 @@ export default class BanCommand extends BushCommand { deleteDays: args.days }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(user.tag); switch (responseCode) { case 'missing permissions': diff --git a/src/commands/moderation/block.ts b/src/commands/moderation/block.ts index 371975b..d53ffd5 100644 --- a/src/commands/moderation/block.ts +++ b/src/commands/moderation/block.ts @@ -17,7 +17,7 @@ export default class BlockCommand extends BushCommand { aliases: ['block'], category: 'moderation', description: 'Prevent a user from using a channel.', - usage: ['block <member> [reason] [duration]'], + usage: ['block <member> [reasonAndDuration]'], examples: ['block IRONM00N 2h bad jokes'], args: [ { @@ -29,7 +29,7 @@ export default class BlockCommand extends BushCommand { slashType: 'USER' }, { - id: 'reason', + id: 'reason_and_duration', description: 'The reason and duration of the block.', type: 'contentWithDuration', match: 'rest', @@ -58,16 +58,20 @@ export default class BlockCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - args: { user: ArgType<'user'>; reason: OptionalArgType<'contentWithDuration'> | string; force?: ArgType<'boolean'> } + args: { + user: ArgType<'user'>; + reason_and_duration: OptionalArgType<'contentWithDuration'> | string; + force?: ArgType<'boolean'>; + } ) { assert(message.inGuild()); if (!(message.channel instanceof BushTextChannel || message.channel instanceof BushThreadChannel)) return message.util.send(`${util.emojis.error} This command can only be used in text and thread channels.`); - const reason = args.reason - ? typeof args.reason === 'string' - ? await util.arg.cast('contentWithDuration', message, args.reason) - : args.reason + const reason = args.reason_and_duration + ? typeof args.reason_and_duration === 'string' + ? await util.arg.cast('contentWithDuration', message, args.reason_and_duration) + : args.reason_and_duration : { duration: null, contentWithoutTime: '' }; if (reason.duration === null) reason.duration = 0; @@ -91,14 +95,14 @@ export default class BlockCommand extends BushCommand { const parsedReason = reason?.contentWithoutTime ?? ''; - const responseCode = await member.block({ + const responseCode = await member.bushBlock({ reason: parsedReason, moderator: message.member, duration: time ?? 0, channel: message.channel }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(member.user.tag); switch (responseCode) { case 'missing permissions': diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index 587f89e..6d96ae9 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -65,7 +65,7 @@ export default class KickCommand extends BushCommand { moderator: message.member }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(member.user.tag); switch (responseCode) { case 'missing permissions': diff --git a/src/commands/moderation/lockdown.ts b/src/commands/moderation/lockdown.ts index b9febbd..ae9a62b 100644 --- a/src/commands/moderation/lockdown.ts +++ b/src/commands/moderation/lockdown.ts @@ -32,15 +32,6 @@ export default class LockdownCommand extends BushCommand { optional: true }, { - id: 'all', - description: 'Whether or not to lock all configured channels.', - match: 'flag', - flag: '--all', - prompt: 'Would you like to lockdown all configured channels?', - slashType: 'BOOLEAN', - optional: true - }, - { id: 'reason', description: 'The reason for the lockdown.', type: 'string', @@ -48,6 +39,15 @@ export default class LockdownCommand extends BushCommand { prompt: 'What is the reason for the lockdown?', slashType: 'STRING', optional: true + }, + { + id: 'all', + description: 'Whether or not to lock all configured channels.', + match: 'flag', + flag: '--all', + prompt: 'Would you like to lockdown all configured channels?', + slashType: 'BOOLEAN', + optional: true } ], slash: true, diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index eeffc6c..e731e30 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -15,7 +15,7 @@ export default class MuteCommand extends BushCommand { aliases: ['mute'], category: 'moderation', description: 'Mute a user.', - usage: ['mute <member> [reason] [duration]'], + usage: ['mute <member> [reasonAndDuration]'], examples: ['mute ironm00n 1 day commands in #general'], args: [ { @@ -27,7 +27,7 @@ export default class MuteCommand extends BushCommand { slashType: 'USER' }, { - id: 'reason', + id: 'reason_and_duration', description: 'The reason and duration of the mute.', type: 'contentWithDuration', match: 'rest', @@ -56,12 +56,16 @@ export default class MuteCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - args: { user: ArgType<'user'>; reason: OptionalArgType<'contentWithDuration'> | string; force?: ArgType<'boolean'> } + args: { + user: ArgType<'user'>; + reason_and_duration: OptionalArgType<'contentWithDuration'> | string; + force?: ArgType<'boolean'>; + } ) { - const reason = args.reason - ? typeof args.reason === 'string' - ? await util.arg.cast('contentWithDuration', message, args.reason) - : args.reason + const reason = args.reason_and_duration + ? typeof args.reason_and_duration === 'string' + ? await util.arg.cast('contentWithDuration', message, args.reason_and_duration) + : args.reason_and_duration : { duration: null, contentWithoutTime: '' }; if (reason.duration === null) reason.duration = 0; @@ -85,13 +89,13 @@ export default class MuteCommand extends BushCommand { const parsedReason = reason?.contentWithoutTime ?? ''; - const responseCode = await member.mute({ + const responseCode = await member.bushMute({ reason: parsedReason, moderator: message.member, duration: time ?? 0 }); - const responseMessage = () => { + const responseMessage = (): string => { const prefix = util.prefix(message); const victim = util.format.input(member.user.tag); switch (responseCode) { diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts index 9fd34b5..6cb8d2f 100644 --- a/src/commands/moderation/role.ts +++ b/src/commands/moderation/role.ts @@ -154,20 +154,20 @@ export default class RoleCommand extends BushCommand { const responseCode = args.action === 'add' - ? await args.member.addRole({ + ? await args.member.bushAddRole({ moderator: message.member!, addToModlog: shouldLog, role: args.role, duration: args.duration }) - : await args.member.removeRole({ + : await args.member.bushRemoveRole({ moderator: message.member!, addToModlog: shouldLog, role: args.role, duration: args.duration }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(args.member.user.tag); switch (responseCode) { case 'user hierarchy': @@ -178,11 +178,13 @@ export default class RoleCommand extends BushCommand { return `${util.emojis.error} <@&${args.role.id}> is higher or equal to my highest role.`; case 'error creating modlog entry': return `${util.emojis.error} There was an error creating a modlog entry, please report this to my developers.`; - case 'error creating role entry' || 'error removing role entry': + case 'error creating role entry': + case 'error removing role entry': return `${util.emojis.error} There was an error ${ args.action === 'add' ? 'creating' : 'removing' } a punishment entry, please report this to my developers.`; - case 'error adding role' || 'error removing role': + case 'error adding role': + case 'error removing role': return `${util.emojis.error} An error occurred while trying to ${args.action} <@&${args.role.id}> ${ args.action === 'add' ? 'to' : 'from' } ${victim}.`; diff --git a/src/commands/moderation/timeout.ts b/src/commands/moderation/timeout.ts new file mode 100644 index 0000000..f187a58 --- /dev/null +++ b/src/commands/moderation/timeout.ts @@ -0,0 +1,100 @@ +import { AllowedMentions, BushCommand, Moderation, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; + +export default class TimeoutCommand extends BushCommand { + public constructor() { + super('timeout', { + aliases: ['timeout'], + category: 'moderation', + description: 'Timeout a user.', + usage: ['timeout <user> <reasonAndDuration>'], + examples: ['timeout IRONM00N 2h'], + args: [ + { + id: 'user', + description: 'The user to timeout.', + type: 'user', + prompt: 'What user would you like to timeout?', + retry: '{error} Choose a valid user to timeout.', + slashType: 'USER' + }, + { + id: 'reason_and_duration', + description: 'The reason and duration of the timeout.', + type: 'contentWithDuration', + match: 'rest', + prompt: 'Why should this user be timed out and for how long?', + retry: '{error} Choose a valid timeout reason and duration.', + slashType: 'STRING' + }, + { + id: 'force', + description: 'Override permission checks.', + flag: '--force', + match: 'flag', + optional: true, + slashType: false, + only: 'text', + ownerOnly: true + } + ], + slash: true, + channel: 'guild', + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MODERATE_MEMBERS']), + userPermissions: ['MODERATE_MEMBERS'] + }); + } + + public override async exec( + message: BushMessage | BushSlashMessage, + args: { user: ArgType<'user'>; reason_and_duration: ArgType<'contentWithDuration'> | string; force?: ArgType<'boolean'> } + ) { + const reason = args.reason_and_duration + ? typeof args.reason_and_duration === 'string' + ? await util.arg.cast('contentWithDuration', message, args.reason_and_duration) + : args.reason_and_duration + : { duration: null, contentWithoutTime: '' }; + + if (reason.duration === null || reason.duration < 1) + return await message.util.reply(`${util.emojis.error} You must specify a duration for timeouts.`); + const member = await message.guild!.members.fetch(args.user.id).catch(() => null); + if (!member) + return await message.util.reply(`${util.emojis.error} The user you selected is not in the server or is not a valid user.`); + + assert(message.member); + const useForce = args.force && message.author.isOwner(); + const canModerateResponse = await Moderation.permissionCheck(message.member, member, 'timeout', true, useForce); + + if (canModerateResponse !== true) { + return message.util.reply(canModerateResponse); + } + + const time = reason.duration; + const parsedReason = reason.contentWithoutTime ?? ''; + + const responseCode = await member.bushTimeout({ + reason: parsedReason, + moderator: message.member, + duration: time + }); + + const responseMessage = (): string => { + const victim = util.format.input(member.user.tag); + switch (responseCode) { + case 'missing permissions': + return `${util.emojis.error} Could not timeout ${victim} because I am missing the **Timeout Members** permission.`; + case 'duration too long': + return `${util.emojis.error} The duration you specified is too long, the longest you can timeout someone for is 28 days.`; + case 'error timing out': + return `${util.emojis.error} An unknown error occurred while trying to timeout ${victim}.`; + case 'error creating modlog entry': + return `${util.emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case 'failed to dm': + return `${util.emojis.warn} Timed out ${victim} however I could not send them a dm.`; + case 'success': + return `${util.emojis.success} Successfully timed out ${victim}.`; + } + }; + return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + } +} diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index 03c60c0..bbce4e0 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -44,7 +44,7 @@ export default class UnbanCommand extends BushCommand { reason }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(user.tag); switch (responseCode) { case 'missing permissions': diff --git a/src/commands/moderation/unblock.ts b/src/commands/moderation/unblock.ts index f9f069b..4cc8b49 100644 --- a/src/commands/moderation/unblock.ts +++ b/src/commands/moderation/unblock.ts @@ -18,7 +18,7 @@ export default class UnblockCommand extends BushCommand { category: 'moderation', description: 'Allows a user to use a channel.', usage: ['unblock <member> [reason]'], - examples: ['unblock IRONM00N 2h bad jokes'], + examples: ['unblock IRONM00N nvm your jokes are funny'], args: [ { id: 'user', @@ -78,13 +78,13 @@ export default class UnblockCommand extends BushCommand { const parsedReason = args.reason ?? ''; - const responseCode = await member.unblock({ + const responseCode = await member.bushUnblock({ reason: parsedReason, moderator: message.member, channel: message.channel }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(member.user.tag); switch (responseCode) { case 'missing permissions': diff --git a/src/commands/moderation/unlockdown.ts b/src/commands/moderation/unlockdown.ts index 24af305..5bcea32 100644 --- a/src/commands/moderation/unlockdown.ts +++ b/src/commands/moderation/unlockdown.ts @@ -8,7 +8,7 @@ export default class UnlockdownCommand extends BushCommand { category: 'moderation', description: 'Allows you to unlockdown a channel or all configured channels.', usage: ['unlockdown [channel] [reason] [--all]'], - examples: ['unlockdown', 'unlockdown --all'], + examples: ['unlockdown', 'unlockdown raid is over --all'], args: [ { id: 'channel', @@ -20,15 +20,6 @@ export default class UnlockdownCommand extends BushCommand { optional: true }, { - id: 'all', - description: 'Whether or not to unlock all configured channels.', - match: 'flag', - flag: '--all', - prompt: 'Would you like to unlockdown all configured channels?', - slashType: 'BOOLEAN', - optional: true - }, - { id: 'reason', description: 'The reason for the unlock.', type: 'string', @@ -36,6 +27,15 @@ export default class UnlockdownCommand extends BushCommand { prompt: 'What is the reason for the unlock?', slashType: 'STRING', optional: true + }, + { + id: 'all', + description: 'Whether or not to unlock all configured channels.', + match: 'flag', + flag: '--all', + prompt: 'Would you like to unlockdown all configured channels?', + slashType: 'BOOLEAN', + optional: true } ], slash: true, diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index e7a19d6..631f213 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -16,7 +16,7 @@ export default class UnmuteCommand extends BushCommand { category: 'moderation', description: 'unmute a user.', usage: ['unmute <member> [reason]'], - examples: ['unmute 322862723090219008 1 day commands in #general'], + examples: ['unmute 322862723090219008 you have been forgiven'], args: [ { id: 'user', @@ -70,12 +70,12 @@ export default class UnmuteCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const responseCode = await member.unmute({ + const responseCode = await member.bushUnmute({ reason, moderator: message.member }); - const responseMessage = () => { + const responseMessage = (): string => { const prefix = util.prefix(message); const victim = util.format.input(member.user.tag); switch (responseCode) { diff --git a/src/commands/moderation/untimeout.ts b/src/commands/moderation/untimeout.ts new file mode 100644 index 0000000..b5ea5be --- /dev/null +++ b/src/commands/moderation/untimeout.ts @@ -0,0 +1,100 @@ +import { + AllowedMentions, + BushCommand, + Moderation, + type ArgType, + type BushMessage, + type BushSlashMessage, + type OptionalArgType +} from '#lib'; +import assert from 'assert'; + +export default class UntimeoutCommand extends BushCommand { + public constructor() { + super('untimeout', { + aliases: ['untimeout', 'remove-timeout'], + category: 'moderation', + description: 'Removes a timeout from a user.', + usage: ['untimeout <user> [reason]'], + examples: ['untimeout 1 2'], + args: [ + { + id: 'user', + description: 'The user to remove a timeout from.', + type: 'user', + prompt: 'What user would you like to untimeout?', + retry: '{error} Choose a valid user to untimeout.', + slashType: 'USER' + }, + { + id: 'reason', + description: 'The reason for removing the timeout.', + type: 'string', + match: 'rest', + prompt: 'Why should this user have their timeout removed?', + retry: '{error} Choose a valid reason to remove the timeout.', + slashType: 'STRING', + optional: true + }, + { + id: 'force', + description: 'Override permission checks.', + flag: '--force', + match: 'flag', + optional: true, + slashType: false, + only: 'text', + ownerOnly: true + } + ], + slash: true, + channel: 'guild', + clientPermissions: (m) => util.clientSendAndPermCheck(m, ['MODERATE_MEMBERS']), + userPermissions: ['MODERATE_MEMBERS'] + }); + } + + public override async exec( + message: BushMessage | BushSlashMessage, + args: { user: ArgType<'user'>; reason: OptionalArgType<'string'>; force?: ArgType<'boolean'> } + ) { + assert(message.member); + + const member = await message.guild!.members.fetch(args.user.id).catch(() => null); + if (!member) + return await message.util.reply(`${util.emojis.error} The user you selected is not in the server or is not a valid user.`); + + if (!member.isCommunicationDisabled()) return message.util.reply(`${util.emojis.error} That user is not timed out.`); + + const useForce = args.force && message.author.isOwner(); + const canModerateResponse = await Moderation.permissionCheck(message.member, member, 'timeout', true, useForce); + + if (canModerateResponse !== true) { + return message.util.reply(canModerateResponse); + } + + const responseCode = await member.bushRemoveTimeout({ + reason: args.reason ?? undefined, + moderator: message.member + }); + + const responseMessage = (): string => { + const victim = util.format.input(member.user.tag); + switch (responseCode) { + case 'missing permissions': + return `${util.emojis.error} Could not timeout ${victim} because I am missing the **Timeout Members** permission.`; + case 'duration too long': + return `${util.emojis.error} The duration you specified is too long, the longest you can timeout someone for is 28 days.`; + case 'error removing timeout': + return `${util.emojis.error} An unknown error occurred while trying to timeout ${victim}.`; + case 'error creating modlog entry': + return `${util.emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case 'failed to dm': + return `${util.emojis.warn} Timed out ${victim} however I could not send them a dm.`; + case 'success': + return `${util.emojis.success} Successfully timed out ${victim}.`; + } + }; + return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + } +} diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index 5093c9b..05b1e36 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -67,12 +67,12 @@ export default class WarnCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const { result: response, caseNum } = await member.warn({ + const { result: response, caseNum } = await member.bushWarn({ reason, moderator: message.member }); - const responseMessage = () => { + const responseMessage = (): string => { const victim = util.format.input(member.user.tag); switch (response) { case 'error creating modlog entry': diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts index cc1bbbd..d7da532 100644 --- a/src/lib/common/AutoMod.ts +++ b/src/lib/common/AutoMod.ts @@ -183,7 +183,7 @@ export class AutoMod { case Severity.WARN: { color = util.colors.yellow; void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.message.member?.warn({ + void this.message.member?.bushWarn({ moderator: this.message.guild!.me!, reason: `[AutoMod] ${highestOffence.reason}` }); @@ -193,7 +193,7 @@ export class AutoMod { case Severity.TEMP_MUTE: { color = util.colors.orange; void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.message.member?.mute({ + void this.message.member?.bushMute({ moderator: this.message.guild!.me!, reason: `[AutoMod] ${highestOffence.reason}`, duration: 900_000 // 15 minutes @@ -204,7 +204,7 @@ export class AutoMod { case Severity.PERM_MUTE: { color = util.colors.red; void this.message.delete().catch((e) => deleteError.bind(this, e)); - void this.message.member?.mute({ + void this.message.member?.bushMute({ moderator: this.message.guild!.me!, reason: `[AutoMod] ${highestOffence.reason}`, duration: 0 // permanent diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts index c06ad25..a09930b 100644 --- a/src/lib/common/util/Moderation.ts +++ b/src/lib/common/util/Moderation.ts @@ -36,7 +36,9 @@ export class Moderation { | 'add a punishment role to' | 'remove a punishment role from' | 'block' - | 'unblock', + | 'unblock' + | 'timeout' + | 'untimeout', checkModerator = true, force = false ): Promise<true | string> { diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 706b52a..8cbc6fe 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -449,19 +449,19 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re export interface BushClient extends EventEmitter, PatchedElements { on<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this; - on<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; + // on<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; once<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this; - once<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; + // once<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; emit<K extends keyof BushClientEvents>(event: K, ...args: BushClientEvents[K]): boolean; - emit<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, ...args: unknown[]): boolean; + // emit<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, ...args: unknown[]): boolean; off<K extends keyof BushClientEvents>(event: K, listener: (...args: BushClientEvents[K]) => Awaitable<void>): this; - off<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; + // off<S extends string | symbol>(event: Exclude<S, keyof BushClientEvents>, listener: (...args: any[]) => Awaitable<void>): this; removeAllListeners<K extends keyof BushClientEvents>(event?: K): this; - removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof BushClientEvents>): this; + // removeAllListeners<S extends string | symbol>(event?: Exclude<S, keyof BushClientEvents>): this; } export interface BushStats { diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index f20a53e..30cc83f 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -515,11 +515,11 @@ export class BushClientUtil extends ClientUtil { // in the beginning of the argument let contentWithoutTime = ` ${content}`; - for (const unit in BushConstants.TimeUnits) { - const regex = BushConstants.TimeUnits[unit as keyof typeof BushConstants.TimeUnits].match; + for (const unit in BushConstants.timeUnits) { + const regex = BushConstants.timeUnits[unit as keyof typeof BushConstants.timeUnits].match; const match = regex.exec(contentWithoutTime); const value = Number(match?.groups?.[unit]); - if (!isNaN(value)) duration! += value * BushConstants.TimeUnits[unit as keyof typeof BushConstants.TimeUnits].value; + if (!isNaN(value)) duration! += value * BushConstants.timeUnits[unit as keyof typeof BushConstants.timeUnits].value; if (remove) contentWithoutTime = contentWithoutTime.replace(regex, ''); } diff --git a/src/lib/extensions/discord.js/BushClientEvents.d.ts b/src/lib/extensions/discord.js/BushClientEvents.d.ts index b779991..6dc94a1 100644 --- a/src/lib/extensions/discord.js/BushClientEvents.d.ts +++ b/src/lib/extensions/discord.js/BushClientEvents.d.ts @@ -227,6 +227,25 @@ export interface BushClientEvents extends AkairoClientEvents { channel: BushTextChannel | BushNewsChannel | BushThreadChannel, messages: Collection<Snowflake, BushMessage> ]; + bushRemoveTimeout: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + dmSuccess: boolean, + evidence?: string + ]; + bushTimeout: [ + victim: BushGuildMember, + moderator: BushUser, + guild: BushGuild, + reason: string | undefined, + caseID: string, + duration: number, + dmSuccess: boolean, + evidence?: string + ]; bushUnban: [ victim: BushUser, moderator: BushUser, diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index b67c71b..d182be4 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -130,6 +130,29 @@ export class BushGuild extends Guild { } /** + * Sends a message to the guild's specified logging channel + * @param logType The corresponding channel that the message will be sent to + * @param message The parameters for {@link BushTextChannel.send} + */ + public async sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions) { + const logChannel = await this.getLogChannel(logType); + if (!logChannel || logChannel.type !== 'GUILD_TEXT') return; + if (!logChannel.permissionsFor(this.me!.id)?.has(['VIEW_CHANNEL', 'SEND_MESSAGES', 'EMBED_LINKS'])) return; + + return await logChannel.send(message).catch(() => null); + } + + /** + * Sends a formatted error message in a guild's error log channel + * @param title The title of the error embed + * @param message The description of the error embed + */ + public async error(title: string, message: string) { + void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); + void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] }); + } + + /** * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. * @param options Options for banning the user. * @returns A string status message of the ban. @@ -143,7 +166,7 @@ export class BushGuild extends Guild { const moderator = (await util.resolveNonCachedUser(options.moderator!)) ?? client.user!; const ret = await (async () => { - await this.members.cache.get(user.id)?.punishDM('banned', options.reason, options.duration ?? 0); + await this.members.cache.get(user.id)?.bushPunishDM('banned', options.reason, options.duration ?? 0); // ban const banSuccess = await this.bans @@ -252,29 +275,6 @@ export class BushGuild extends Guild { } /** - * Sends a message to the guild's specified logging channel - * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link BushTextChannel.send} - */ - public async sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions) { - const logChannel = await this.getLogChannel(logType); - if (!logChannel || logChannel.type !== 'GUILD_TEXT') return; - if (!logChannel.permissionsFor(this.me!.id)?.has(['VIEW_CHANNEL', 'SEND_MESSAGES', 'EMBED_LINKS'])) return; - - return await logChannel.send(message).catch(() => null); - } - - /** - * Sends a formatted error message in a guild's error log channel - * @param title The title of the error embed - * @param message The description of the error embed - */ - public async error(title: string, message: string) { - void client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); - void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: util.colors.error }] }); - } - - /** * Denies send permissions in specified channels * @param options The options for locking down the guild */ @@ -336,7 +336,7 @@ export class BushGuild extends Guild { /** * Options for unbanning a user */ -interface BushUnbanOptions { +export interface BushUnbanOptions { /** * The user to unban */ @@ -356,7 +356,7 @@ interface BushUnbanOptions { /** * Options for banning a user */ -interface BushBanOptions { +export interface BushBanOptions { /** * The user to ban */ @@ -388,22 +388,22 @@ interface BushBanOptions { evidence?: string; } -type PunishmentResponse = 'success' | 'missing permissions' | 'error creating modlog entry'; +export type PunishmentResponse = 'success' | 'missing permissions' | 'error creating modlog entry'; /** * Response returned when banning a user */ -type BanResponse = PunishmentResponse | 'error banning' | 'error creating ban entry'; +export type BanResponse = PunishmentResponse | 'error banning' | 'error creating ban entry'; /** * Response returned when unbanning a user */ -type UnbanResponse = PunishmentResponse | 'user not banned' | 'error unbanning' | 'error removing ban entry'; +export type UnbanResponse = PunishmentResponse | 'user not banned' | 'error unbanning' | 'error removing ban entry'; /** * Options for locking down channel(s) */ -interface LockdownOptions { +export interface LockdownOptions { /** * The moderator responsible for the lockdown */ @@ -433,7 +433,7 @@ interface LockdownOptions { /** * Response returned when locking down a channel */ -type LockdownResponse = +export type LockdownResponse = | `success: ${number}` | 'all not chosen and no channel specified' | 'no channels configured' diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index ac8fb54..ffca507 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -35,7 +35,7 @@ export class BushGuildMember extends GuildMember { * @param sendFooter Whether or not to send the guild's punishment footer with the dm. * @returns Whether or not the dm was sent successfully. */ - public async punishDM(punishment: string, reason?: string | null, duration?: number, sendFooter = true): Promise<boolean> { + public async bushPunishDM(punishment: string, reason?: string | null, duration?: number, sendFooter = true): Promise<boolean> { const ending = await this.guild.getSetting('punishmentEnding'); const dmEmbed = ending && ending.length && sendFooter @@ -56,12 +56,12 @@ export class BushGuildMember extends GuildMember { * @returns An object with the result of the warning, and the case number of the warn. * @emits {@link BushClientEvents.bushWarn} */ - public async warn(options: BushPunishmentOptions): Promise<{ result: WarnResponse | null; caseNum: number | null }> { + public async bushWarn(options: BushPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> { let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; - const ret = await (async () => { + const ret = await (async (): Promise<{ result: WarnResponse; caseNum: number | null }> => { // add modlog entry const result = await Moderation.createModLogEntry( { @@ -78,7 +78,7 @@ export class BushGuildMember extends GuildMember { if (!result || !result.log) return { result: 'error creating modlog entry', caseNum: null }; // dm user - const dmSuccess = await this.punishDM('warned', options.reason); + const dmSuccess = await this.bushPunishDM('warned', options.reason); dmSuccessEvent = dmSuccess; if (!dmSuccess) return { result: 'failed to dm', caseNum: result.caseNum }; @@ -86,7 +86,7 @@ export class BushGuildMember extends GuildMember { })(); if (!(['error creating modlog entry'] as const).includes(ret.result)) client.emit('bushWarn', this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); - return ret as { result: WarnResponse | null; caseNum: number | null }; + return ret; } /** @@ -95,7 +95,7 @@ export class BushGuildMember extends GuildMember { * @returns A status message for adding the add. * @emits {@link BushClientEvents.bushPunishRole} */ - public async addRole(options: AddRoleOptions): Promise<AddRoleResponse> { + public async bushAddRole(options: AddRoleOptions): Promise<AddRoleResponse> { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); if (ifShouldAddRole !== true) return ifShouldAddRole; @@ -159,7 +159,7 @@ export class BushGuildMember extends GuildMember { * @returns A status message for removing the role. * @emits {@link BushClientEvents.bushPunishRoleRemove} */ - public async removeRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> { + public async bushRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> { const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); if (ifShouldAddRole !== true) return ifShouldAddRole; @@ -240,7 +240,7 @@ export class BushGuildMember extends GuildMember { * @returns A status message for muting the user. * @emits {@link BushClientEvents.bushMute} */ - public async mute(options: BushTimedPunishmentOptions): Promise<MuteResponse> { + public async bushMute(options: BushTimedPunishmentOptions): Promise<MuteResponse> { // checks if (!this.guild.me!.permissions.has('MANAGE_ROLES')) return 'missing permissions'; const muteRoleID = await this.guild.getSetting('muteRole'); @@ -290,7 +290,7 @@ export class BushGuildMember extends GuildMember { if (!punishmentEntrySuccess) return 'error creating mute entry'; // dm user - const dmSuccess = await this.punishDM('muted', options.reason, options.duration ?? 0); + const dmSuccess = await this.bushPunishDM('muted', options.reason, options.duration ?? 0); dmSuccessEvent = dmSuccess; if (!dmSuccess) return 'failed to dm'; @@ -319,7 +319,7 @@ export class BushGuildMember extends GuildMember { * @returns A status message for unmuting the user. * @emits {@link BushClientEvents.bushUnmute} */ - public async unmute(options: BushPunishmentOptions): Promise<UnmuteResponse> { + public async bushUnmute(options: BushPunishmentOptions): Promise<UnmuteResponse> { // checks if (!this.guild.me!.permissions.has('MANAGE_ROLES')) return 'missing permissions'; const muteRoleID = await this.guild.getSetting('muteRole'); @@ -365,7 +365,7 @@ export class BushGuildMember extends GuildMember { if (!removePunishmentEntrySuccess) return 'error removing mute entry'; // dm user - const dmSuccess = await this.punishDM('unmuted', options.reason, undefined, false); + const dmSuccess = await this.bushPunishDM('unmuted', options.reason, undefined, false); dmSuccessEvent = dmSuccess; if (!dmSuccess) return 'failed to dm'; @@ -402,7 +402,7 @@ export class BushGuildMember extends GuildMember { const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; const ret = await (async () => { // dm user - const dmSuccess = await this.punishDM('kicked', options.reason); + const dmSuccess = await this.bushPunishDM('kicked', options.reason); dmSuccessEvent = dmSuccess; // kick @@ -452,7 +452,7 @@ export class BushGuildMember extends GuildMember { const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; const ret = await (async () => { // dm user - const dmSuccess = await this.punishDM('banned', options.reason, options.duration ?? 0); + const dmSuccess = await this.bushPunishDM('banned', options.reason, options.duration ?? 0); dmSuccessEvent = dmSuccess; // ban @@ -507,7 +507,7 @@ export class BushGuildMember extends GuildMember { * Prevents a user from speaking in a channel. * @param options Options for blocking the user. */ - public async block(options: BlockOptions): Promise<BlockResponse> { + public async bushBlock(options: BlockOptions): Promise<BlockResponse> { const _channel = this.guild.channels.resolve(options.channel); if (!_channel || (!_channel.isText() && !_channel.isThread())) return 'invalid channel'; const channel = _channel as BushGuildTextBasedChannel; @@ -588,7 +588,7 @@ export class BushGuildMember extends GuildMember { * Allows a user to speak in a channel. * @param options Options for unblocking the user. */ - public async unblock(options: UnblockOptions): Promise<UnblockResponse> { + public async bushUnblock(options: UnblockOptions): Promise<UnblockResponse> { const _channel = this.guild.channels.resolve(options.channel); if (!_channel || (!_channel.isText() && !_channel.isThread())) return 'invalid channel'; const channel = _channel as BushGuildTextBasedChannel; @@ -659,6 +659,122 @@ export class BushGuildMember extends GuildMember { } /** + * Mutes a user using discord's timeout feature. + * @param options Options for timing out the user. + */ + public async bushTimeout(options: BushTimeoutOptions): Promise<TimeoutResponse> { + // checks + if (!this.guild.me!.permissions.has('MODERATE_MEMBERS')) return 'missing permissions'; + + const twentyEightDays = client.consts.timeUnits.days.value * 28; + if (options.duration > twentyEightDays) return 'duration too long'; + + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; + + const ret = await (async () => { + // timeout + const timeoutSuccess = await this.timeout( + options.duration, + `${moderator.tag} | ${options.reason ?? 'No reason provided.'}` + ).catch(() => false); + if (!timeoutSuccess) return 'error timing out'; + + // add modlog entry + const { log: modlog } = await Moderation.createModLogEntry({ + type: ModLogType.TIMEOUT, + user: this, + moderator: moderator.id, + reason: options.reason, + duration: options.duration, + guild: this.guild, + evidence: options.evidence + }); + + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; + + // dm user + const dmSuccess = await this.bushPunishDM('timed out', options.reason, options.duration); + dmSuccessEvent = dmSuccess; + + if (!dmSuccess) return 'failed to dm'; + + return 'success'; + })(); + + if (!(['error timing out', 'error creating modlog entry'] as const).includes(ret)) + client.emit( + 'bushTimeout', + this, + moderator, + this.guild, + options.reason ?? undefined, + caseID!, + options.duration ?? 0, + dmSuccessEvent!, + options.evidence + ); + return ret; + } + + /** + * Removes a timeout from a user. + * @param options Options for removing the timeout. + */ + public async bushRemoveTimeout(options: BushPunishmentOptions): Promise<RemoveTimeoutResponse> { + // checks + if (!this.guild.me!.permissions.has('MODERATE_MEMBERS')) return 'missing permissions'; + + let caseID: string | undefined = undefined; + let dmSuccessEvent: boolean | undefined = undefined; + const moderator = (await util.resolveNonCachedUser(options.moderator ?? this.guild.me))!; + + const ret = await (async () => { + // remove timeout + const timeoutSuccess = await this.timeout(null, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`).catch( + () => false + ); + if (!timeoutSuccess) return 'error removing timeout'; + + // add modlog entry + const { log: modlog } = await Moderation.createModLogEntry({ + type: ModLogType.REMOVE_TIMEOUT, + user: this, + moderator: moderator.id, + reason: options.reason, + guild: this.guild, + evidence: options.evidence + }); + + if (!modlog) return 'error creating modlog entry'; + caseID = modlog.id; + + // dm user + const dmSuccess = await this.bushPunishDM('un timed out', options.reason); + dmSuccessEvent = dmSuccess; + + if (!dmSuccess) return 'failed to dm'; + + return 'success'; + })(); + + if (!(['error removing timeout', 'error creating modlog entry'] as const).includes(ret)) + client.emit( + 'bushRemoveTimeout', + this, + moderator, + this.guild, + options.reason ?? undefined, + caseID!, + dmSuccessEvent!, + options.evidence + ); + return ret; + } + + /** * Whether or not the user is an owner of the bot. */ public isOwner(): boolean { @@ -763,6 +879,16 @@ export interface UnblockOptions extends BushPunishmentOptions { channel: BushGuildTextChannelResolvable | BushThreadChannelResolvable; } +/** + * Punishment options for punishments that can be temporary. + */ +export interface BushTimeoutOptions extends BushPunishmentOptions { + /** + * The duration of the punishment. + */ + duration: number; +} + export type PunishmentResponse = 'success' | 'error creating modlog entry' | 'failed to dm'; /** @@ -774,7 +900,7 @@ export type WarnResponse = PunishmentResponse; * Response returned when adding a role to a user. */ export type AddRoleResponse = - | PunishmentResponse + | Exclude<PunishmentResponse, 'failed to dm'> | 'user hierarchy' | 'role managed' | 'client hierarchy' @@ -785,7 +911,7 @@ export type AddRoleResponse = * Response returned when removing a role from a user. */ export type RemoveRoleResponse = - | PunishmentResponse + | Exclude<PunishmentResponse, 'failed to dm'> | 'user hierarchy' | 'role managed' | 'client hierarchy' @@ -846,11 +972,17 @@ export type UnblockResponse = | 'missing permissions' | 'error unblocking'; -export type PartialBushGuildMember = Partialize< - BushGuildMember, - 'joinedAt' | 'joinedTimestamp', - 'warn' | 'addRole' | 'removeRole' | 'mute' | 'unmute' | 'bushKick' | 'bushBan' | 'isOwner' | 'isSuperUser' | 'block' ->; +/** + * Response returned when timing out a user. + */ +export type TimeoutResponse = PunishmentResponse | 'missing permissions' | 'duration too long' | 'error timing out'; + +/** + * Response returned when removing a timeout from a user. + */ +export type RemoveTimeoutResponse = PunishmentResponse | 'missing permissions' | 'duration too long' | 'error removing timeout'; + +export type PartialBushGuildMember = Partialize<BushGuildMember, 'joinedAt' | 'joinedTimestamp'>; /** * @typedef {BushClientEvents} VSCodePleaseDontRemove diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts index e19ddd4..7ddd64c 100644 --- a/src/lib/models/ModLog.ts +++ b/src/lib/models/ModLog.ts @@ -19,7 +19,9 @@ export enum ModLogType { REMOVE_PUNISHMENT_ROLE = 'REMOVE_PUNISHMENT_ROLE', PERM_CHANNEL_BLOCK = 'PERM_CHANNEL_BLOCK', TEMP_CHANNEL_BLOCK = 'TEMP_CHANNEL_BLOCK', - CHANNEL_UNBLOCK = 'CHANNEL_UNBLOCK' + CHANNEL_UNBLOCK = 'CHANNEL_UNBLOCK', + TIMEOUT = 'TIMEOUT', + REMOVE_TIMEOUT = 'REMOVE_TIMEOUT' } export interface ModLogModel { diff --git a/src/lib/utils/BushConstants.ts b/src/lib/utils/BushConstants.ts index 4f5fb87..306f580 100644 --- a/src/lib/utils/BushConstants.ts +++ b/src/lib/utils/BushConstants.ts @@ -49,7 +49,7 @@ export class BushConstants { } as const); // Somewhat stolen from @Mzato0001 - public static TimeUnits = BushClientUtil.deepFreeze({ + public static timeUnits = BushClientUtil.deepFreeze({ milliseconds: { match: / (?:(?<milliseconds>-?(?:\d+)?\.?\d+) *(?:milliseconds?|msecs?|ms))/im, value: 1 diff --git a/src/listeners/member-custom/bushRemoveTimeout.ts b/src/listeners/member-custom/bushRemoveTimeout.ts new file mode 100644 index 0000000..30a3ab4 --- /dev/null +++ b/src/listeners/member-custom/bushRemoveTimeout.ts @@ -0,0 +1,31 @@ +import { BushListener, type BushClientEvents } from '#lib'; +import { GuildMember, MessageEmbed } from 'discord.js'; + +export default class BushRemoveTimeoutListener extends BushListener { + public constructor() { + super('bushRemoveTimeout', { + emitter: 'client', + event: 'bushRemoveTimeout', + category: 'member-custom' + }); + } + + public override async exec(...[victim, moderator, guild, reason, caseID, dmSuccess]: BushClientEvents['bushRemoveTimeout']) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new MessageEmbed() + .setColor(util.colors.discord.GREEN) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined }) + .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]'}`); + 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 new file mode 100644 index 0000000..63ba41d --- /dev/null +++ b/src/listeners/member-custom/bushTimeout.ts @@ -0,0 +1,34 @@ +import { BushListener, type BushClientEvents } from '#lib'; +import { GuildMember, MessageEmbed } from 'discord.js'; + +export default class BushTimeoutListener extends BushListener { + public constructor() { + super('bushTimeout', { + emitter: 'client', + event: 'bushTimeout', + category: 'member-custom' + }); + } + + public override async exec( + ...[victim, moderator, guild, reason, caseID, duration, dmSuccess]: BushClientEvents['bushTimeout'] + ) { + const logChannel = await guild.getLogChannel('moderation'); + if (!logChannel) return; + const user = victim instanceof GuildMember ? victim.user : victim; + + const logEmbed = new MessageEmbed() + .setColor(util.colors.discord.ORANGE) + .setTimestamp() + .setFooter({ text: `CaseID: ${caseID}` }) + .setAuthor({ name: user.tag, iconURL: user.avatarURL({ dynamic: true, format: 'png', size: 4096 }) ?? undefined }) + .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('**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/tasks/removeExpiredPunishements.ts b/src/tasks/removeExpiredPunishements.ts index 3c18ebc..7b27b07 100644 --- a/src/tasks/removeExpiredPunishements.ts +++ b/src/tasks/removeExpiredPunishements.ts @@ -45,7 +45,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { await entry.destroy(); // channel overrides are removed when the member leaves the guild continue; } - const result = await member.unblock({ reason: 'Punishment expired.', channel: entry.extraInfo }); + const result = await member.bushUnblock({ reason: 'Punishment expired.', channel: entry.extraInfo }); if (['success', 'user not blocked'].includes(result)) await entry.destroy(); else throw new Error(result); void client.logger.verbose(`removeExpiredPunishments`, `Unblocked ${entry.user}.`); @@ -53,7 +53,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { } case ActivePunishmentType.MUTE: { if (!member) continue; - const result = await member.unmute({ reason: 'Punishment expired.' }); + const result = await member.bushUnmute({ reason: 'Punishment expired.' }); if (['success', 'failed to dm'].includes(result)) await entry.destroy(); else throw new Error(result); void client.logger.verbose(`removeExpiredPunishments`, `Unmuted ${entry.user}.`); @@ -63,7 +63,7 @@ export default class RemoveExpiredPunishmentsTask extends BushTask { if (!member) continue; const role = guild?.roles?.cache?.get(entry.extraInfo); if (!role) throw new Error(`Cannot unmute ${member.user.tag} because I cannot find the mute role.`); - const result = await member.removeRole({ + const result = await member.bushRemoveRole({ reason: 'Punishment expired.', role: role, addToModlog: true |