From 83db032fb91996c926a5d007a9e5fa4abed65871 Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Thu, 30 Dec 2021 16:55:37 -0500 Subject: add timeout command and fix some other moderation commands --- src/commands/moderation/ban.ts | 21 ++++--- src/commands/moderation/block.ts | 22 +++++--- src/commands/moderation/kick.ts | 2 +- src/commands/moderation/lockdown.ts | 18 +++--- src/commands/moderation/mute.ts | 22 +++++--- src/commands/moderation/role.ts | 12 ++-- src/commands/moderation/timeout.ts | 100 ++++++++++++++++++++++++++++++++++ src/commands/moderation/unban.ts | 2 +- src/commands/moderation/unblock.ts | 6 +- src/commands/moderation/unlockdown.ts | 20 +++---- src/commands/moderation/unmute.ts | 6 +- src/commands/moderation/untimeout.ts | 100 ++++++++++++++++++++++++++++++++++ src/commands/moderation/warn.ts | 4 +- 13 files changed, 274 insertions(+), 61 deletions(-) create mode 100644 src/commands/moderation/timeout.ts create mode 100644 src/commands/moderation/untimeout.ts (limited to 'src/commands') 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 [--delete]'], + usage: ['ban [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 [reason] [duration]'], + usage: ['block [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 @@ -31,15 +31,6 @@ export default class LockdownCommand extends BushCommand { channelTypes: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'], 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.', @@ -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 [reason] [duration]'], + usage: ['mute [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 '], + 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 [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', @@ -19,15 +19,6 @@ export default class UnlockdownCommand extends BushCommand { channelTypes: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'], 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.', @@ -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 [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 [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': -- cgit