diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/automod/AutomodShared.ts | 8 | ||||
-rw-r--r-- | lib/common/Appeals.ts | 140 | ||||
-rw-r--r-- | lib/common/Moderation.ts | 53 | ||||
-rw-r--r-- | lib/extensions/discord.js/BotClientEvents.ts | 20 | ||||
-rw-r--r-- | lib/extensions/discord.js/ExtendedGuild.ts | 137 | ||||
-rw-r--r-- | lib/extensions/discord.js/ExtendedGuildMember.ts | 508 | ||||
-rw-r--r-- | lib/utils/FormatResponse.ts | 267 |
7 files changed, 764 insertions, 369 deletions
diff --git a/lib/automod/AutomodShared.ts b/lib/automod/AutomodShared.ts index 48217dd..e886ad0 100644 --- a/lib/automod/AutomodShared.ts +++ b/lib/automod/AutomodShared.ts @@ -175,9 +175,9 @@ export async function handleAutomodInteraction(interaction: ButtonInteraction) { const victimUserFormatted = (await interaction.client.utils.resolveNonCachedUser(userId))?.tag ?? userId; const content = (() => { - if (result === unmuteResponse.SUCCESS) { + if (result === unmuteResponse.Success) { return `${emojis.success} Successfully banned ${Format.input(victimUserFormatted)}.`; - } else if (result === unmuteResponse.DM_ERROR) { + } else if (result === unmuteResponse.DmError) { return `${emojis.warn} Banned ${Format.input(victimUserFormatted)} however I could not send them a dm.`; } else { return `${emojis.error} Could not ban ${Format.input(victimUserFormatted)}: \`${result}\` .`; @@ -218,9 +218,9 @@ export async function handleAutomodInteraction(interaction: ButtonInteraction) { const victimUserFormatted = victim.user.tag; const content = (() => { - if (result === unmuteResponse.SUCCESS) { + if (result === unmuteResponse.Success) { return `${emojis.success} Successfully unmuted ${Format.input(victimUserFormatted)}.`; - } else if (result === unmuteResponse.DM_ERROR) { + } else if (result === unmuteResponse.DmError) { return `${emojis.warn} Unmuted ${Format.input(victimUserFormatted)} however I could not send them a dm.`; } else { return `${emojis.error} Could not unmute ${Format.input(victimUserFormatted)}: \`${result}\` .`; diff --git a/lib/common/Appeals.ts b/lib/common/Appeals.ts index 43c56fd..4eb0ea3 100644 --- a/lib/common/Appeals.ts +++ b/lib/common/Appeals.ts @@ -1,6 +1,12 @@ import { AppealStatus, ModLog } from '#lib/models/instance/ModLog.js'; import { colors, emojis } from '#lib/utils/Constants.js'; import { input } from '#lib/utils/Format.js'; +import { + formatUnbanResponse, + formatUnblockResponse, + formatUnmuteResponse, + formatUntimeoutResponse +} from '#lib/utils/FormatResponse.js'; import { capitalize, ModalInput } from '#lib/utils/Utils.js'; import { ActionRowBuilder, @@ -17,12 +23,28 @@ import { Action, punishments } from './Moderation.js'; type AppealBase = 'appeal_attempt' | 'appeal_submit' | 'appeal_accept' | 'appeal_deny'; -type RawAppealInfo = [baseId: AppealBase, punishment: `${Action}`, guildId: Snowflake, userId: Snowflake, modlogId: string]; - -type AppealInfo = [baseId: AppealBase, punishment: Action, guildId: Snowflake, userId: Snowflake, modlogId: string]; +type RawAppealInfo = [ + baseId: AppealBase, + punishment: `${Action}`, + guildId: Snowflake, + userId: Snowflake, + modlogId: string, + extraId?: Snowflake +]; + +type AppealInfo = [ + baseId: AppealBase, + punishment: Action, + guildId: Snowflake, + userId: Snowflake, + modlogId: string, + extraId?: Snowflake +]; export type AppealIdString = - `${RawAppealInfo[0]};${RawAppealInfo[1]};${RawAppealInfo[2]};${RawAppealInfo[3]};${RawAppealInfo[4]}`; + `${RawAppealInfo[0]};${RawAppealInfo[1]};${RawAppealInfo[2]};${RawAppealInfo[3]};${RawAppealInfo[4]}${RawAppealInfo[5] extends undefined + ? '' + : `;${RawAppealInfo[5]}`}`; function parseAppeal(customId: AppealIdString | string): AppealInfo { const [baseId, _punishment, guildId, userId, modlogId] = customId.split(';') as RawAppealInfo; @@ -37,7 +59,7 @@ function parseAppeal(customId: AppealIdString | string): AppealInfo { * @param interaction A button interaction with a custom id thar starts with "appeal_attempt;". */ export async function handleAppealAttempt(interaction: ButtonInteraction) { - const [baseId, punishment, guildId, userId, modlogId] = parseAppeal(interaction.customId); + const [baseId, punishment, guildId, userId, modlogId, extraId] = parseAppeal(interaction.customId); const { base, past, appealCustom } = punishments[punishment]; const appealName = appealCustom ?? capitalize(base); @@ -79,7 +101,7 @@ export async function handleAppealAttempt(interaction: ButtonInteraction) { }; return await interaction.showModal({ - customId: `appeal_submit;${punishment};${guildId};${userId};${modlogId}`, + customId: `appeal_submit;${punishment};${guildId};${userId};${modlogId}${extraId ? `;${extraId}` : ''}`, title: `${appealName} Appeal`, components: [ ModalInput({ @@ -109,7 +131,7 @@ export async function handleAppealAttempt(interaction: ButtonInteraction) { * @param interaction A modal interaction with a custom id that starts with "appeal_submit;". */ export async function handleAppealSubmit(interaction: ModalSubmitInteraction) { - const [baseId, punishment, guildId, userId, modlogId] = parseAppeal(interaction.customId); + const [baseId, punishment, guildId, userId, modlogId, extraId] = parseAppeal(interaction.customId); const { base, past, appealCustom } = punishments[punishment]; const appealName = appealCustom ?? capitalize(base); @@ -159,12 +181,12 @@ export async function handleAppealSubmit(interaction: ModalSubmitInteraction) { components: [ new ActionRowBuilder<ButtonBuilder>().addComponents( new ButtonBuilder({ - customId: `appeal_accept;${punishment};${guildId};${userId};${modlogId}`, + customId: `appeal_accept;${punishment};${guildId};${userId};${modlogId}${extraId ? `;${extraId}` : ''}`, label: 'Accept Appeal', style: ButtonStyle.Success }), new ButtonBuilder({ - customId: `appeal_deny;${punishment};${guildId};${userId};${modlogId}`, + customId: `appeal_deny;${punishment};${guildId};${userId};${modlogId}${extraId ? `;${extraId}` : ''}`, label: 'Deny Appeal', style: ButtonStyle.Danger }) @@ -178,7 +200,12 @@ export async function handleAppealSubmit(interaction: ModalSubmitInteraction) { * @param interaction A button interaction with a custom id that starts with "appeal_accept;" or "appeal_deny;". */ export async function handleAppealDecision(interaction: ButtonInteraction) { - const [baseId, punishment, guildId, userId, modlogId] = parseAppeal(interaction.customId); + if (!interaction.inCachedGuild()) { + void interaction.client.console.warn('Appeals', `Appeal decision made in uncached guild: ${interaction.guildId}`); + return; + } + + const [baseId, punishment, guildId, userId, modlogId, extraId] = parseAppeal(interaction.customId); const { base, past, appealCustom } = punishments[punishment]; const appealName = (appealCustom ?? base).toLowerCase(); @@ -186,12 +213,12 @@ export async function handleAppealDecision(interaction: ButtonInteraction) { const modlog = await ModLog.findByPk(modlogId); if (!modlog) { - return await interaction.reply(`:skull: I cannot find the modlog ${input(modlogId)}. Please report this to my developers.`); + return await interaction.reply(`:boom: I cannot find the modlog ${input(modlogId)}. Please report this to my developers.`); } if (modlog.appeal !== AppealStatus.Submitted) { return await interaction.reply( - `:skull: Case ${input(modlogId)} has an invalid state of ${input(modlog.appeal)}. Please report this to my developers.` + `:boom: Case ${input(modlogId)} has an invalid state of ${input(modlog.appeal)}. Please report this to my developers.` ); } @@ -218,12 +245,8 @@ export async function handleAppealDecision(interaction: ButtonInteraction) { ] }); } else if (baseId === 'appeal_accept') { - modlog.appeal = AppealStatus.Accepted; - await modlog.save(); - - await interaction.client.users - .send(userId, `Your ${appealName} appeal has been accepted in ${interaction.client.guilds.resolve(guildId)!}.`) - .catch(() => {}); + const guild = interaction.client.guilds.resolve(guildId); + if (!guild) return await interaction.reply(`:boom: I can't find this server.`); switch (punishment) { case Action.Warn: @@ -236,25 +259,98 @@ export async function handleAppealDecision(interaction: ButtonInteraction) { assert.fail(`Cannot appeal ${appealName} (Action.${Action[punishment]})`); return; case Action.Mute: { - throw new Error('Not implemented'); + const member = await guild.members.fetch(userId); + + const res = await member.customUnmute({ + reason: `Appeal accepted.`, + moderator: interaction.member, + noDM: true + }); + + if (res !== 'success') { + return await interaction.reply({ + content: formatUnmuteResponse('/', member, res), + ephemeral: false + }); + } + + break; } case Action.Ban: { - throw new Error('Not implemented'); + const user = await interaction.client.users.fetch(userId); + + const res = await guild.customUnban({ + user: userId, + reason: `Appeal accepted.`, + moderator: interaction.member, + noDM: true + }); + + if (res !== 'success') { + return await interaction.reply({ + content: formatUnbanResponse(user, res), + ephemeral: false + }); + } + + break; } case Action.Timeout: { - throw new Error('Not implemented'); + const member = await guild.members.fetch(userId); + + const res = await member.customRemoveTimeout({ + reason: `Appeal accepted.`, + moderator: interaction.member, + noDM: true + }); + + if (res !== 'success') { + return await interaction.reply({ + content: formatUntimeoutResponse(member, res), + ephemeral: false + }); + } + + break; } case Action.Block: { - throw new Error('Not implemented'); + assert(extraId, 'Block appeal must have extraId'); + const member = await guild.members.fetch(userId); + + const res = await member.customUnblock({ + reason: `Appeal accepted.`, + channel: extraId, + moderator: interaction.member, + noDM: true + }); + + if (res !== 'success') { + return await interaction.reply({ + content: formatUnblockResponse(member, res), + ephemeral: false + }); + } + + break; } case Action.AddPunishRole: { throw new Error('Not implemented'); + + break; } default: { const _exhaustiveCheck: never = punishment; } } + modlog.appeal = AppealStatus.Accepted; + await modlog.save(); + + // dm + await interaction.client.users + .send(userId, `Your ${appealName} appeal (${input(modlogId)}) has been accepted in ${guild}.`) + .catch(() => {}); + return await interaction.update({ content: `${emojis.check} Appeal accepted.`, embeds: interaction.message.embeds, diff --git a/lib/common/Moderation.ts b/lib/common/Moderation.ts index 7697b2f..a79d1df 100644 --- a/lib/common/Moderation.ts +++ b/lib/common/Moderation.ts @@ -264,17 +264,17 @@ export async function checkMutePermissions( guild: Guild ): Promise<ValueOf<typeof baseMuteResponse> | ValueOf<typeof permissionsResponse> | true> { if (!guild.members.me!.permissions.has('ManageRoles')) { - return permissionsResponse.MISSING_PERMISSIONS; + return permissionsResponse.MissingPermissions; } const muteRoleID = await guild.getSetting('muteRole'); - if (!muteRoleID) return baseMuteResponse.NO_MUTE_ROLE; + if (!muteRoleID) return baseMuteResponse.NoMuteRole; const muteRole = guild.roles.cache.get(muteRoleID); - if (!muteRole) return baseMuteResponse.MUTE_ROLE_INVALID; + if (!muteRole) return baseMuteResponse.MuteRoleInvalid; if (muteRole.position >= guild.members.me!.roles.highest.position || muteRole.managed) { - return baseMuteResponse.MUTE_ROLE_NOT_MANAGEABLE; + return baseMuteResponse.MuteRoleNotManageable; } return true; @@ -570,6 +570,14 @@ export interface PunishDMOptions extends BaseOptions { * The channel that the user was (un)blocked from. */ channel?: Snowflake; + + /** + * The role that the user was given/removed. + */ + role?: { + id: Snowflake; + name: string; + }; } /** @@ -587,17 +595,38 @@ export async function punishDM(options: PunishDMOptions): Promise<boolean> { const appealsEnabled = (await options.guild.hasFeature('punishmentAppeals')) && Boolean(await options.guild.getLogChannel('appeals')); - let content = `You have been ${options.punishment} `; + let content = ''; + + switch (options.punishment) { + case Action.AddPunishRole: + assert(options.role, 'Role is required for adding a punishment role.'); + content += `You have received the "${options.role.name}" punishment role`; + break; + case Action.RemovePunishRole: + assert(options.role, 'Role is required for removing a punishment role.'); + content += `The "${options.role.name}" punishment role has been removed from you`; + break; + default: + content += `You have been ${options.punishment}`; + break; + } + if ([Action.Block, Action.Unblock].includes(options.punishment)) { assert(options.channel); - content += `from <#${options.channel}> `; + content += ` from <#${options.channel}>`; } - content += `in ${format.input(options.guild.name)} `; + + content += ` in ${format.input(options.guild.name)}`; if (options.duration !== null && options.duration !== undefined) { - content += options.duration ? `for ${humanizeDuration(options.duration)} ` : 'permanently '; + content += options.duration ? ` for ${humanizeDuration(options.duration)}` : ' permanently'; + } + + if (![Action.AddPunishRole, Action.RemovePunishRole].includes(options.punishment)) { + const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided'; + content += ` for ${format.input(reason)}.`; + } else { + content += '.'; } - const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided'; - content += `for ${format.input(reason)}.`; let components; if (appealsEnabled && options.modlog) { @@ -606,11 +635,13 @@ export async function punishDM(options: PunishDMOptions): Promise<boolean> { const userId = options.client.users.resolveId(options.user); const modlogCase = options.modlog; + const extraId = options.channel ?? options.role?.id; + components = [ new ActionRowBuilder<ButtonBuilder>({ components: [ new ButtonBuilder({ - customId: `appeal_attempt;${Action[punishment]};${guildId};${userId};${modlogCase}`, + customId: `appeal_attempt;${Action[punishment]};${guildId};${userId};${modlogCase}${extraId ? `;${extraId}` : ''}`, style: ButtonStyle.Primary, label: 'Appeal Punishment' }) diff --git a/lib/extensions/discord.js/BotClientEvents.ts b/lib/extensions/discord.js/BotClientEvents.ts index 88f67e9..de06d62 100644 --- a/lib/extensions/discord.js/BotClientEvents.ts +++ b/lib/extensions/discord.js/BotClientEvents.ts @@ -25,7 +25,7 @@ export interface BotClientEvents extends AkairoClientEvents { reason: string | undefined, caseID: string, duration: number, - dmSuccess?: boolean, + dmSuccess?: boolean | null, evidence?: string ]; [TanzaniteEvent.Block]: [ @@ -35,7 +35,7 @@ export interface BotClientEvents extends AkairoClientEvents { reason: string | undefined, caseID: string, duration: number, - dmSuccess: boolean, + dmSuccess: boolean | null, channel: GuildTextBasedChannel, evidence?: string ]; @@ -45,7 +45,7 @@ export interface BotClientEvents extends AkairoClientEvents { guild: Guild, reason: string | undefined, caseID: string, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.Mute]: [ @@ -55,7 +55,7 @@ export interface BotClientEvents extends AkairoClientEvents { reason: string | undefined, caseID: string, duration: number, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.PunishRoleAdd]: [ @@ -89,7 +89,7 @@ export interface BotClientEvents extends AkairoClientEvents { guild: Guild, reason: string | undefined, caseID: string, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.Timeout]: [ @@ -99,7 +99,7 @@ export interface BotClientEvents extends AkairoClientEvents { reason: string | undefined, caseID: string, duration: number, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.Unban]: [ @@ -108,7 +108,7 @@ export interface BotClientEvents extends AkairoClientEvents { guild: Guild, reason: string | undefined, caseID: string, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.Unblock]: [ @@ -117,7 +117,7 @@ export interface BotClientEvents extends AkairoClientEvents { guild: Guild, reason: string | undefined, caseID: string, - dmSuccess: boolean, + dmSuccess: boolean | null, channel: GuildTextBasedChannel, evidence?: string ]; @@ -127,7 +127,7 @@ export interface BotClientEvents extends AkairoClientEvents { guild: Guild, reason: string | undefined, caseID: string, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.UpdateModlog]: [ @@ -150,7 +150,7 @@ export interface BotClientEvents extends AkairoClientEvents { guild: Guild, reason: string | undefined, caseID: string, - dmSuccess: boolean, + dmSuccess: boolean | null, evidence?: string ]; [TanzaniteEvent.LevelUpdate]: [ diff --git a/lib/extensions/discord.js/ExtendedGuild.ts b/lib/extensions/discord.js/ExtendedGuild.ts index 84db7d0..2c1fd62 100644 --- a/lib/extensions/discord.js/ExtendedGuild.ts +++ b/lib/extensions/discord.js/ExtendedGuild.ts @@ -23,7 +23,6 @@ import { PermissionFlagsBits, SnowflakeUtil, ThreadChannel, - WebhookCreateMessageOptions, type APIMessage, type GuildMember, type GuildMemberResolvable, @@ -35,11 +34,12 @@ import { type User, type UserResolvable, type VoiceChannel, - type Webhook + type Webhook, + type WebhookCreateMessageOptions } from 'discord.js'; import { camelCase } from 'lodash-es'; import { TanzaniteClient } from '../discord-akairo/TanzaniteClient.js'; -import { banResponse, BanResponse, dmResponse, permissionsResponse, punishmentEntryRemove } from './ExtendedGuildMember.js'; +import { banResponse, BanResponse, dmResponse, permissionsResponse, punishmentEntryError } from './ExtendedGuildMember.js'; interface Extension { /** @@ -113,7 +113,7 @@ interface Extension { * @param options Options for banning the user. * @returns A string status message of the ban. * **Preconditions:** - * - {@link me} has the `BanMembers` permission + * - {@link members.me} has the `BanMembers` permission * **Warning:** * - Doesn't emit customBan Event */ @@ -230,15 +230,15 @@ export class ExtendedGuild extends Guild implements Extension { public override async customBan(options: GuildCustomBanOptions): Promise<BanResponse> { // checks - if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS; + if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const user = await this.client.utils.resolveNonCachedUser(options.user); const moderator = this.client.users.resolve(options.moderator ?? this.client.user!); - if (!user || !moderator) return banResponse.CANNOT_RESOLVE_USER; + if (!user || !moderator) return banResponse.CannotResolveUser; - if ((await this.bans.fetch()).has(user.id)) return banResponse.ALREADY_BANNED; + if ((await this.bans.fetch()).has(user.id)) return banResponse.AlreadyBanned; const ret = await (async () => { // add modlog entry @@ -252,20 +252,22 @@ export class ExtendedGuild extends Guild implements Extension { guild: this, evidence: options.evidence }); - if (!modlog) return banResponse.MODLOG_ERROR; + if (!modlog) return banResponse.ModlogError; caseID = modlog.id; - // dm user - dmSuccessEvent = await punishDM({ - client: this.client, - modlog: modlog.id, - guild: this, - user: user, - punishment: Action.Ban, - duration: options.duration ?? 0, - reason: options.reason ?? undefined, - sendFooter: true - }); + if (!options.noDM) { + // dm user + dmSuccess = await punishDM({ + client: this.client, + modlog: modlog.id, + guild: this, + user: user, + punishment: Action.Ban, + duration: options.duration ?? 0, + reason: options.reason ?? undefined, + sendFooter: true + }); + } // ban const banSuccess = await this.bans @@ -274,7 +276,7 @@ export class ExtendedGuild extends Guild implements Extension { deleteMessageDays: options.deleteDays }) .catch(() => false); - if (!banSuccess) return banResponse.ACTION_ERROR; + if (!banSuccess) return banResponse.ActionError; // add punishment entry so they can be unbanned later const punishmentEntrySuccess = await createPunishmentEntry({ @@ -285,13 +287,16 @@ export class ExtendedGuild extends Guild implements Extension { duration: options.duration, modlog: modlog.id }); - if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR; + if (!punishmentEntrySuccess) return banResponse.PunishmentEntryError; - if (!dmSuccessEvent) return banResponse.DM_ERROR; - return banResponse.SUCCESS; + if (!options.noDM && !dmSuccess) { + return banResponse.DmError; + } + + return banResponse.Success; })(); - if (!([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret)) + if (!([banResponse.ActionError, banResponse.ModlogError, banResponse.PunishmentEntryError] as const).includes(ret)) this.client.emit( TanzaniteEvent.Ban, user, @@ -300,14 +305,14 @@ export class ExtendedGuild extends Guild implements Extension { options.reason ?? undefined, caseID!, options.duration ?? 0, - dmSuccessEvent, + dmSuccess, options.evidence ); return ret; } public override async massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse> { - if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED; + if (this.bans.cache.has(options.user)) return banResponse.AlreadyBanned; const ret = await (async () => { // add modlog entry @@ -320,7 +325,7 @@ export class ExtendedGuild extends Guild implements Extension { duration: 0, guild: this.id }); - if (!modlog) return banResponse.MODLOG_ERROR; + if (!modlog) return banResponse.ModlogError; let dmSuccessEvent: boolean | undefined = undefined; // dm user @@ -344,7 +349,7 @@ export class ExtendedGuild extends Guild implements Extension { deleteMessageDays: options.deleteDays }) .catch(() => false); - if (!banSuccess) return banResponse.ACTION_ERROR; + if (!banSuccess) return banResponse.ActionError; // add punishment entry so they can be unbanned later const punishmentEntrySuccess = await createPunishmentEntry({ @@ -355,29 +360,29 @@ export class ExtendedGuild extends Guild implements Extension { duration: 0, modlog: modlog.id }); - if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR; + if (!punishmentEntrySuccess) return banResponse.PunishmentEntryError; - if (!dmSuccessEvent) return banResponse.DM_ERROR; - return banResponse.SUCCESS; + if (!dmSuccessEvent) return banResponse.DmError; + return banResponse.Success; })(); return ret; } public override async customUnban(options: GuildCustomUnbanOptions): Promise<UnbanResponse> { // checks - if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return unbanResponse.MISSING_PERMISSIONS; + if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return unbanResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const user = await this.client.utils.resolveNonCachedUser(options.user); const moderator = this.client.users.resolve(options.moderator ?? this.client.user!); - if (!user || !moderator) return unbanResponse.CANNOT_RESOLVE_USER; + if (!user || !moderator) return unbanResponse.CannotResolveUser; const ret = await (async () => { - const bans = await this.bans.fetch(); + const ban = await this.bans.fetch(user.id); let notBanned = false; - if (!bans.has(user.id)) notBanned = true; + if (ban?.user?.id === user.id) notBanned = true; const unbanSuccess = await this.bans .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`) @@ -388,8 +393,8 @@ export class ExtendedGuild extends Guild implements Extension { } else return false; }); - if (notBanned) return unbanResponse.NOT_BANNED; - if (!unbanSuccess) return unbanResponse.ACTION_ERROR; + if (notBanned) return unbanResponse.NotBanned; + if (!unbanSuccess) return unbanResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -401,7 +406,7 @@ export class ExtendedGuild extends Guild implements Extension { guild: this, evidence: options.evidence }); - if (!modlog) return unbanResponse.MODLOG_ERROR; + if (!modlog) return unbanResponse.ModlogError; caseID = modlog.id; // remove punishment entry @@ -411,26 +416,24 @@ export class ExtendedGuild extends Guild implements Extension { user: user.id, guild: this }); - if (!removePunishmentEntrySuccess) return unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR; + if (!removePunishmentEntrySuccess) return unbanResponse.PunishmentEntryError; - // dm user - dmSuccessEvent = await punishDM({ - client: this.client, - guild: this, - user: user, - punishment: Action.Unban, - reason: options.reason ?? undefined, - sendFooter: false - }); + if (!options.noDM) { + // dm user + dmSuccess = await punishDM({ + client: this.client, + guild: this, + user: user, + punishment: Action.Unban, + reason: options.reason ?? undefined, + sendFooter: false + }); - if (!dmSuccessEvent) return unbanResponse.DM_ERROR; - return unbanResponse.SUCCESS; + if (dmSuccess === false) return unbanResponse.DmError; + } + return unbanResponse.Success; })(); - if ( - !([unbanResponse.ACTION_ERROR, unbanResponse.MODLOG_ERROR, unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR] as const).includes( - ret - ) - ) + if (!([unbanResponse.ActionError, unbanResponse.ModlogError, unbanResponse.PunishmentEntryError] as const).includes(ret)) this.client.emit( TanzaniteEvent.Unban, user, @@ -438,7 +441,7 @@ export class ExtendedGuild extends Guild implements Extension { this, options.reason ?? undefined, caseID!, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; @@ -756,6 +759,11 @@ export interface GuildCustomUnbanOptions { * The evidence for the unban */ evidence?: string; + + /** + * Don't send a dm to the user. + */ + noDM?: boolean; } export interface GuildMassBanOneOptions { @@ -813,6 +821,11 @@ export interface GuildCustomBanOptions { * The evidence for the ban */ evidence?: string; + + /** + * Don't send a dm to the user. + */ + noDM?: boolean; } type ValueOf<T> = T[keyof T]; @@ -820,8 +833,8 @@ type ValueOf<T> = T[keyof T]; export const unbanResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, - ...punishmentEntryRemove, - NOT_BANNED: 'user not banned' + ...punishmentEntryError, + NotBanned: 'user not banned' } as const); /** diff --git a/lib/extensions/discord.js/ExtendedGuildMember.ts b/lib/extensions/discord.js/ExtendedGuildMember.ts index b11e9e3..8cfd4f6 100644 --- a/lib/extensions/discord.js/ExtendedGuildMember.ts +++ b/lib/extensions/discord.js/ExtendedGuildMember.ts @@ -42,51 +42,51 @@ interface Extension { * Warn the user, create a modlog entry, and send a dm to the user. * @param options Options for warning the user. * @returns An object with the result of the warning, and the case number of the warn. - * @emits {@link BotClientEvents.warnMember} + * @emits {@link TanzaniteEvent.Warn} */ customWarn(options: CustomPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }>; /** * Add a role to the user, if it is a punishment create a modlog entry, and create a punishment entry if it is temporary or a punishment. * @param options Options for adding a role to the user. * @returns A status message for adding the add. - * @emits {@link BotClientEvents.punishRole} + * @emits {@link TanzaniteEvent.PunishRoleAdd} */ customAddRole(options: AddRoleOptions): Promise<AddRoleResponse>; /** * Remove a role from the user, if it is a punishment create a modlog entry, and destroy a punishment entry if it was temporary or a punishment. * @param options Options for removing a role from the user. * @returns A status message for removing the role. - * @emits {@link BotClientEvents.punishRoleRemove} + * @emits {@link TanzaniteEvent.PunishRoleRemove} */ customRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse>; /** * Mute the user, create a modlog entry, creates a punishment entry, and dms the user. * @param options Options for muting the user. * @returns A status message for muting the user. - * @emits {@link BotClientEvents.customMute} + * @emits {@link TanzaniteEvent.Mute} */ customMute(options: CustomTimedPunishmentOptions): Promise<MuteResponse>; /** * Unmute the user, create a modlog entry, remove the punishment entry, and dm the user. * @param options Options for unmuting the user. * @returns A status message for unmuting the user. - * @emits {@link BotClientEvents.customUnmute} + * @emits {@link TanzaniteEvent.Unmute} */ customUnmute(options: CustomPunishmentOptions): Promise<UnmuteResponse>; /** * Kick the user, create a modlog entry, and dm the user. * @param options Options for kicking the user. * @returns A status message for kicking the user. - * @emits {@link BotClientEvents.customKick} + * @emits {@link TanzaniteEvent.Kick} */ customKick(options: CustomPunishmentOptions): Promise<KickResponse>; /** * Ban the user, create a modlog entry, create a punishment entry, and dm the user. * @param options Options for banning the user. * @returns A status message for banning the user. - * @emits {@link BotClientEvents.customBan} + * @emits {@link TanzaniteEvent.Ban} */ - customBan(options: CustomBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>>; + customBan(options: CustomBanOptions): Promise<Exclude<BanResponse, typeof banResponse['AlreadyBanned']>>; /** * Prevents a user from speaking in a channel. * @param options Options for blocking the user. @@ -124,6 +124,34 @@ declare module 'discord.js' { } export class ExtendedGuildMember extends GuildMember implements Extension { + /** + * Sets the default values for the options. + */ + #optionDefaults<T extends CustomPunishmentOptions>(options: T): T { + options.noDM ??= options.silent ?? false; + return options; + } + + /** + * Check whether or not a role should be added/removed from the user based on hierarchy. + * @param role The role to check if can be modified. + * @param moderator The moderator that is trying to add/remove the role. + * @returns `true` if the role should be added/removed or a string for the reason why it shouldn't. + */ + #checkIfShouldAddRole( + role: Role | Role, + moderator?: GuildMember + ): true | 'user hierarchy' | 'role managed' | 'client hierarchy' { + if (moderator && moderator.roles.highest.position <= role.position && this.guild.ownerId !== this.user.id) { + return shouldAddRoleResponse.UserHierarchy; + } else if (role.managed) { + return shouldAddRoleResponse.RoleManaged; + } else if (this.guild.members.me!.roles.highest.position <= role.position) { + return shouldAddRoleResponse.ClientHierarchy; + } + return true; + } + public override async customPunishDM( punishment: Action, reason?: string | null, @@ -144,10 +172,12 @@ export class ExtendedGuildMember extends GuildMember implements Extension { } public override async customWarn(options: CustomPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> { - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + options = this.#optionDefaults(options); + + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return { result: warnResponse.CANNOT_RESOLVE_USER, caseNum: null }; + if (!moderator) return { result: warnResponse.CannotResolveUser, caseNum: null }; const ret = await (async (): Promise<{ result: WarnResponse; caseNum: number | null }> => { // add modlog entry @@ -164,32 +194,33 @@ export class ExtendedGuildMember extends GuildMember implements Extension { }, true ); - caseID = result.log?.id; - if (!result || !result.log) return { result: warnResponse.MODLOG_ERROR, caseNum: null }; - if (!options.silent) { + caseID = result.log?.id ?? null; + if (!result || !result.log) return { result: warnResponse.ModlogError, caseNum: null }; + + if (!options.noDM) { // dm user - const dmSuccess = await this.customPunishDM(Action.Warn, options.reason); - dmSuccessEvent = dmSuccess; - if (!dmSuccess) return { result: warnResponse.DM_ERROR, caseNum: result.caseNum }; + dmSuccess = await this.customPunishDM(Action.Warn, options.reason); + + if (dmSuccess === false) return { result: warnResponse.DmError, caseNum: result.caseNum }; } - return { result: warnResponse.SUCCESS, caseNum: result.caseNum }; + return { result: warnResponse.Success, caseNum: result.caseNum }; })(); - if (!([warnResponse.MODLOG_ERROR] as const).includes(ret.result) && !options.silent) - this.client.emit(TanzaniteEvent.Warn, this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccessEvent!); + if (!([warnResponse.ModlogError] as const).includes(ret.result) && !options.silent) + this.client.emit(TanzaniteEvent.Warn, this, moderator, this.guild, options.reason ?? undefined, caseID!, dmSuccess); return ret; } public override async customAddRole(options: AddRoleOptions): Promise<AddRoleResponse> { // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return addRoleResponse.MISSING_PERMISSIONS; + if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return roleResponse.MissingPermissions; const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); if (ifShouldAddRole !== true) return ifShouldAddRole; - let caseID: string | undefined = undefined; + let caseID: string | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return addRoleResponse.CANNOT_RESOLVE_USER; + if (!moderator) return roleResponse.CannotResolveUser; const ret = await (async () => { if (options.addToModlog || options.duration) { @@ -205,7 +236,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { hidden: options.silent ?? false }); - if (!modlog) return addRoleResponse.MODLOG_ERROR; + if (!modlog) return roleResponse.ModlogError; caseID = modlog.id; if (options.addToModlog || options.duration) { @@ -218,19 +249,17 @@ export class ExtendedGuildMember extends GuildMember implements Extension { duration: options.duration, extraInfo: options.role.id }); - if (!punishmentEntrySuccess) return addRoleResponse.PUNISHMENT_ENTRY_ADD_ERROR; + if (!punishmentEntrySuccess) return roleResponse.PunishmentEntryError; } } const removeRoleSuccess = await this.roles.add(options.role, `${moderator.tag}`); - if (!removeRoleSuccess) return addRoleResponse.ACTION_ERROR; + if (!removeRoleSuccess) return roleResponse.ActionError; - return addRoleResponse.SUCCESS; + return roleResponse.Success; })(); if ( - !( - [addRoleResponse.ACTION_ERROR, addRoleResponse.MODLOG_ERROR, addRoleResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const - ).includes(ret) && + !([roleResponse.ActionError, roleResponse.ModlogError, roleResponse.PunishmentEntryError] as const).includes(ret) && options.addToModlog && !options.silent ) @@ -250,13 +279,13 @@ export class ExtendedGuildMember extends GuildMember implements Extension { public override async customRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> { // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return removeRoleResponse.MISSING_PERMISSIONS; + if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return roleResponse.MissingPermissions; const ifShouldAddRole = this.#checkIfShouldAddRole(options.role, options.moderator); if (ifShouldAddRole !== true) return ifShouldAddRole; let caseID: string | undefined = undefined; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return removeRoleResponse.CANNOT_RESOLVE_USER; + if (!moderator) return roleResponse.CannotResolveUser; const ret = await (async () => { if (options.addToModlog) { @@ -271,7 +300,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { hidden: options.silent ?? false }); - if (!modlog) return removeRoleResponse.MODLOG_ERROR; + if (!modlog) return roleResponse.ModlogError; caseID = modlog.id; const punishmentEntrySuccess = await removePunishmentEntry({ @@ -282,26 +311,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { extraInfo: options.role.id }); - if (!punishmentEntrySuccess) return removeRoleResponse.PUNISHMENT_ENTRY_REMOVE_ERROR; + if (!punishmentEntrySuccess) return roleResponse.PunishmentEntryError; } const removeRoleSuccess = await this.roles.remove(options.role, `${moderator.tag}`); - if (!removeRoleSuccess) return removeRoleResponse.ACTION_ERROR; + if (!removeRoleSuccess) return roleResponse.ActionError; - return removeRoleResponse.SUCCESS; + return roleResponse.Success; })(); - if ( - !( - [ - removeRoleResponse.ACTION_ERROR, - removeRoleResponse.MODLOG_ERROR, - removeRoleResponse.PUNISHMENT_ENTRY_REMOVE_ERROR - ] as const - ).includes(ret) && - options.addToModlog && - !options.silent - ) + const nonSuccess = ( + [roleResponse.ActionError, roleResponse.ModlogError, roleResponse.PunishmentEntryError] as const + ).includes(ret); + + if (!nonSuccess && options.addToModlog && !options.silent) { this.client.emit( TanzaniteEvent.PunishRoleRemove, this, @@ -312,30 +335,13 @@ export class ExtendedGuildMember extends GuildMember implements Extension { options.role, options.evidence ); - return ret; - } - - /** - * Check whether or not a role should be added/removed from the user based on hierarchy. - * @param role The role to check if can be modified. - * @param moderator The moderator that is trying to add/remove the role. - * @returns `true` if the role should be added/removed or a string for the reason why it shouldn't. - */ - #checkIfShouldAddRole( - role: Role | Role, - moderator?: GuildMember - ): true | 'user hierarchy' | 'role managed' | 'client hierarchy' { - if (moderator && moderator.roles.highest.position <= role.position && this.guild.ownerId !== this.user.id) { - return shouldAddRoleResponse.USER_HIERARCHY; - } else if (role.managed) { - return shouldAddRoleResponse.ROLE_MANAGED; - } else if (this.guild.members.me!.roles.highest.position <= role.position) { - return shouldAddRoleResponse.CLIENT_HIERARCHY; } - return true; + return ret; } public override async customMute(options: CustomTimedPunishmentOptions): Promise<MuteResponse> { + options = this.#optionDefaults(options); + // checks const checks = await checkMutePermissions(this.guild); if (checks !== true) return checks; @@ -343,10 +349,10 @@ export class ExtendedGuildMember extends GuildMember implements Extension { const muteRoleID = (await this.guild.getSetting('muteRole'))!; const muteRole = this.guild.roles.cache.get(muteRoleID)!; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return muteResponse.CANNOT_RESOLVE_USER; + if (!moderator) return muteResponse.CannotResolveUser; const ret = await (async () => { // add role @@ -357,7 +363,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { this.client.console.debug(e); return false; }); - if (!muteSuccess) return muteResponse.ACTION_ERROR; + if (!muteSuccess) return muteResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -372,7 +378,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { hidden: options.silent ?? false }); - if (!modlog) return muteResponse.MODLOG_ERROR; + if (!modlog) return muteResponse.ModlogError; caseID = modlog.id; // add punishment entry so they can be unmuted later @@ -385,20 +391,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { modlog: modlog.id }); - if (!punishmentEntrySuccess) return muteResponse.PUNISHMENT_ENTRY_ADD_ERROR; + if (!punishmentEntrySuccess) return muteResponse.PunishmentEntryError; - if (!options.silent) { + if (!options.noDM) { // dm user - const dmSuccess = await this.customPunishDM(Action.Mute, options.reason, options.duration ?? 0, modlog.id); - dmSuccessEvent = dmSuccess; - if (!dmSuccess) return muteResponse.DM_ERROR; + dmSuccess = await this.customPunishDM(Action.Mute, options.reason, options.duration ?? 0, modlog.id); + + if (dmSuccess === false) return muteResponse.DmError; } - return muteResponse.SUCCESS; + return muteResponse.Success; })(); if ( - !([muteResponse.ACTION_ERROR, muteResponse.MODLOG_ERROR, muteResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) && + !([muteResponse.ActionError, muteResponse.ModlogError, muteResponse.PunishmentEntryError] as const).includes(ret) && !options.silent ) this.client.emit( @@ -409,13 +415,15 @@ export class ExtendedGuildMember extends GuildMember implements Extension { options.reason ?? undefined, caseID!, options.duration ?? 0, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; } public override async customUnmute(options: CustomPunishmentOptions): Promise<UnmuteResponse> { + options = this.#optionDefaults(options); + // checks const checks = await checkMutePermissions(this.guild); if (checks !== true) return checks; @@ -423,10 +431,10 @@ export class ExtendedGuildMember extends GuildMember implements Extension { const muteRoleID = (await this.guild.getSetting('muteRole'))!; const muteRole = this.guild.roles.cache.get(muteRoleID)!; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return unmuteResponse.CANNOT_RESOLVE_USER; + if (!moderator) return unmuteResponse.CannotResolveUser; const ret = await (async () => { // remove role @@ -436,7 +444,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { await this.client.console.warn('muteRoleAddError', formatError(e, true)); return false; }); - if (!muteSuccess) return unmuteResponse.ACTION_ERROR; + if (!muteSuccess) return unmuteResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -450,7 +458,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { hidden: options.silent ?? false }); - if (!modlog) return unmuteResponse.MODLOG_ERROR; + if (!modlog) return unmuteResponse.ModlogError; caseID = modlog.id; // remove mute entry @@ -461,22 +469,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { guild: this.guild }); - if (!removePunishmentEntrySuccess) return unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR; + if (!removePunishmentEntrySuccess) return unmuteResponse.PunishmentEntryError; - if (!options.silent) { + if (!options.noDM) { // dm user - const dmSuccess = await this.customPunishDM(Action.Unmute, options.reason, undefined, '', false); - dmSuccessEvent = dmSuccess; - if (!dmSuccess) return unmuteResponse.DM_ERROR; + dmSuccess = await this.customPunishDM(Action.Unmute, options.reason, undefined, '', false); + + if (dmSuccess === false) return unmuteResponse.DmError; } - return unmuteResponse.SUCCESS; + return unmuteResponse.Success; })(); if ( - !( - [unmuteResponse.ACTION_ERROR, unmuteResponse.MODLOG_ERROR, unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR] as const - ).includes(ret) && + !([unmuteResponse.ActionError, unmuteResponse.ModlogError, unmuteResponse.PunishmentEntryError] as const).includes(ret) && !options.silent ) this.client.emit( @@ -486,21 +492,23 @@ export class ExtendedGuildMember extends GuildMember implements Extension { this.guild, options.reason ?? undefined, caseID!, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; } public override async customKick(options: CustomPunishmentOptions): Promise<KickResponse> { + options = this.#optionDefaults(options); + // checks if (!this.guild.members.me?.permissions.has(PermissionFlagsBits.KickMembers) || !this.kickable) - return kickResponse.MISSING_PERMISSIONS; + return kickResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return kickResponse.CANNOT_RESOLVE_USER; + if (!moderator) return kickResponse.CannotResolveUser; const ret = await (async () => { // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -513,21 +521,26 @@ export class ExtendedGuildMember extends GuildMember implements Extension { evidence: options.evidence, hidden: options.silent ?? false }); - if (!modlog) return kickResponse.MODLOG_ERROR; + if (!modlog) return kickResponse.ModlogError; caseID = modlog.id; // dm user - const dmSuccess = options.silent ? null : await this.customPunishDM(Action.Kick, options.reason, undefined, modlog.id); - dmSuccessEvent = dmSuccess ?? undefined; + if (!options.noDM) { + dmSuccess = await this.customPunishDM(Action.Kick, options.reason, undefined, modlog.id); + } // kick const kickSuccess = await this.kick(`${moderator?.tag} | ${options.reason ?? 'No reason provided.'}`).catch(() => false); - if (!kickSuccess) return kickResponse.ACTION_ERROR; - if (dmSuccess === false) return kickResponse.DM_ERROR; - return kickResponse.SUCCESS; + if (!kickSuccess) return kickResponse.ActionError; + + if (!options.noDM && dmSuccess === false) { + return kickResponse.DmError; + } + + return kickResponse.Success; })(); - if (!([kickResponse.ACTION_ERROR, kickResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent) + if (!([kickResponse.ActionError, kickResponse.ModlogError] as const).includes(ret) && !options.silent) this.client.emit( TanzaniteEvent.Kick, this, @@ -535,23 +548,23 @@ export class ExtendedGuildMember extends GuildMember implements Extension { this.guild, options.reason ?? undefined, caseID!, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; } - public override async customBan( - options: CustomBanOptions - ): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>> { + public override async customBan(options: CustomBanOptions): Promise<Exclude<BanResponse, typeof banResponse['AlreadyBanned']>> { + options = this.#optionDefaults(options); + // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.BanMembers) || !this.bannable) - return banResponse.MISSING_PERMISSIONS; + return banResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return banResponse.CANNOT_RESOLVE_USER; + if (!moderator) return banResponse.CannotResolveUser; // ignore result, they should still be banned even if their mute cannot be removed await this.customUnmute({ @@ -573,21 +586,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { evidence: options.evidence, hidden: options.silent ?? false }); - if (!modlog) return banResponse.MODLOG_ERROR; + if (!modlog) return banResponse.ModlogError; caseID = modlog.id; - // dm user - const dmSuccess = options.silent - ? null - : await this.customPunishDM(Action.Ban, options.reason, options.duration ?? 0, modlog.id); - dmSuccessEvent = dmSuccess ?? undefined; + if (!options.noDM) { + // dm user + dmSuccess = await this.customPunishDM(Action.Ban, options.reason, options.duration ?? 0, modlog.id); + } // ban const banSuccess = await this.ban({ reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`, deleteMessageDays: options.deleteDays }).catch(() => false); - if (!banSuccess) return banResponse.ACTION_ERROR; + if (!banSuccess) return banResponse.ActionError; // add punishment entry so they can be unbanned later const punishmentEntrySuccess = await createPunishmentEntry({ @@ -598,13 +610,16 @@ export class ExtendedGuildMember extends GuildMember implements Extension { duration: options.duration, modlog: modlog.id }); - if (!punishmentEntrySuccess) return banResponse.PUNISHMENT_ENTRY_ADD_ERROR; + if (!punishmentEntrySuccess) return banResponse.PunishmentEntryError; - if (!dmSuccess) return banResponse.DM_ERROR; - return banResponse.SUCCESS; + if (!options.noDM && dmSuccess === false) { + return banResponse.DmError; + } + + return banResponse.Success; })(); if ( - !([banResponse.ACTION_ERROR, banResponse.MODLOG_ERROR, banResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes(ret) && + !([banResponse.ActionError, banResponse.ModlogError, banResponse.PunishmentEntryError] as const).includes(ret) && !options.silent ) this.client.emit( @@ -615,24 +630,26 @@ export class ExtendedGuildMember extends GuildMember implements Extension { options.reason ?? undefined, caseID!, options.duration ?? 0, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; } public override async customBlock(options: BlockOptions): Promise<BlockResponse> { + options = this.#optionDefaults(options); + const channel = this.guild.channels.resolve(options.channel); - if (!channel || (!channel.isTextBased() && !channel.isThread())) return blockResponse.INVALID_CHANNEL; + if (!channel || (!channel.isTextBased() && !channel.isThread())) return blockResponse.InvalidChannel; // checks if (!channel.permissionsFor(this.guild.members.me!)!.has(PermissionFlagsBits.ManageChannels)) - return blockResponse.MISSING_PERMISSIONS; + return blockResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return blockResponse.CANNOT_RESOLVE_USER; + if (!moderator) return blockResponse.CannotResolveUser; const ret = await (async () => { // change channel permissions @@ -641,7 +658,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { const blockSuccess = await channelToUse.permissionOverwrites .edit(this, perm, { reason: `[Block] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}` }) .catch(() => false); - if (!blockSuccess) return blockResponse.ACTION_ERROR; + if (!blockSuccess) return blockResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -654,7 +671,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { evidence: options.evidence, hidden: options.silent ?? false }); - if (!modlog) return blockResponse.MODLOG_ERROR; + if (!modlog) return blockResponse.ModlogError; caseID = modlog.id; // add punishment entry so they can be unblocked later @@ -667,32 +684,29 @@ export class ExtendedGuildMember extends GuildMember implements Extension { modlog: modlog.id, extraInfo: channel.id }); - if (!punishmentEntrySuccess) return blockResponse.PUNISHMENT_ENTRY_ADD_ERROR; + if (!punishmentEntrySuccess) return blockResponse.PunishmentEntryError; - // dm user - const dmSuccess = options.silent - ? null - : await punishDM({ - client: this.client, - punishment: Action.Block, - reason: options.reason ?? undefined, - duration: options.duration ?? 0, - modlog: modlog.id, - guild: this.guild, - user: this, - sendFooter: true, - channel: channel.id - }); - dmSuccessEvent = !!dmSuccess; - if (!dmSuccess) return blockResponse.DM_ERROR; + if (!options.noDM) { + // dm user + dmSuccess = await punishDM({ + client: this.client, + punishment: Action.Block, + reason: options.reason ?? undefined, + duration: options.duration ?? 0, + modlog: modlog.id, + guild: this.guild, + user: this, + sendFooter: true, + channel: channel.id + }); + if (dmSuccess === false) return blockResponse.DmError; + } - return blockResponse.SUCCESS; + return blockResponse.Success; })(); if ( - !([blockResponse.ACTION_ERROR, blockResponse.MODLOG_ERROR, blockResponse.PUNISHMENT_ENTRY_ADD_ERROR] as const).includes( - ret - ) && + !([blockResponse.ActionError, blockResponse.ModlogError, blockResponse.PunishmentEntryError] as const).includes(ret) && !options.silent ) this.client.emit( @@ -703,7 +717,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { options.reason ?? undefined, caseID!, options.duration ?? 0, - dmSuccessEvent!, + dmSuccess, channel, options.evidence ); @@ -711,18 +725,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { } public override async customUnblock(options: UnblockOptions): Promise<UnblockResponse> { + options = this.#optionDefaults(options); + const _channel = this.guild.channels.resolve(options.channel); - if (!_channel || (_channel.type !== ChannelType.GuildText && !_channel.isThread())) return unblockResponse.INVALID_CHANNEL; + if (!_channel || (_channel.type !== ChannelType.GuildText && !_channel.isThread())) return unblockResponse.InvalidChannel; const channel = _channel as GuildTextBasedChannel; // checks if (!channel.permissionsFor(this.guild.members.me!)!.has(PermissionFlagsBits.ManageChannels)) - return unblockResponse.MISSING_PERMISSIONS; + return unblockResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return unblockResponse.CANNOT_RESOLVE_USER; + if (!moderator) return unblockResponse.CannotResolveUser; const ret = await (async () => { // change channel permissions @@ -731,7 +747,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { const blockSuccess = await channelToUse.permissionOverwrites .edit(this, perm, { reason: `[Unblock] ${moderator.tag} | ${options.reason ?? 'No reason provided.'}` }) .catch(() => false); - if (!blockSuccess) return unblockResponse.ACTION_ERROR; + if (!blockSuccess) return unblockResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -744,7 +760,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { evidence: options.evidence, hidden: options.silent ?? false }); - if (!modlog) return unblockResponse.MODLOG_ERROR; + if (!modlog) return unblockResponse.ModlogError; caseID = modlog.id; // remove punishment entry @@ -755,31 +771,28 @@ export class ExtendedGuildMember extends GuildMember implements Extension { guild: this.guild, extraInfo: channel.id }); - if (!punishmentEntrySuccess) return unblockResponse.ACTION_ERROR; + if (!punishmentEntrySuccess) return unblockResponse.ActionError; - // dm user - const dmSuccess = options.silent - ? null - : await punishDM({ - client: this.client, - punishment: Action.Unblock, - reason: options.reason ?? undefined, - guild: this.guild, - user: this, - sendFooter: false, - channel: channel.id - }); - dmSuccessEvent = !!dmSuccess; - if (!dmSuccess) return blockResponse.DM_ERROR; + if (!options.noDM) { + // dm user + dmSuccess = await punishDM({ + client: this.client, + punishment: Action.Unblock, + reason: options.reason ?? undefined, + guild: this.guild, + user: this, + sendFooter: false, + channel: channel.id + }); - dmSuccessEvent = !!dmSuccess; - if (!dmSuccess) return unblockResponse.DM_ERROR; + if (dmSuccess === false) return unblockResponse.DmError; + } - return unblockResponse.SUCCESS; + return unblockResponse.Success; })(); if ( - !([unblockResponse.ACTION_ERROR, unblockResponse.MODLOG_ERROR, unblockResponse.ACTION_ERROR] as const).includes(ret) && + !([unblockResponse.ActionError, unblockResponse.ModlogError, unblockResponse.ActionError] as const).includes(ret) && !options.silent ) this.client.emit( @@ -789,7 +802,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { this.guild, options.reason ?? undefined, caseID!, - dmSuccessEvent!, + dmSuccess, channel, options.evidence ); @@ -797,16 +810,18 @@ export class ExtendedGuildMember extends GuildMember implements Extension { } public override async customTimeout(options: CustomTimeoutOptions): Promise<TimeoutResponse> { + options = this.#optionDefaults(options); + // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return timeoutResponse.MISSING_PERMISSIONS; + if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return timeoutResponse.MissingPermissions; const twentyEightDays = Time.Day * 28; - if (options.duration > twentyEightDays) return timeoutResponse.INVALID_DURATION; + if (options.duration > twentyEightDays) return timeoutResponse.InvalidDuration; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return timeoutResponse.CANNOT_RESOLVE_USER; + if (!moderator) return timeoutResponse.CannotResolveUser; const ret = await (async () => { // timeout @@ -814,7 +829,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { options.duration, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}` ).catch(() => false); - if (!timeoutSuccess) return timeoutResponse.ACTION_ERROR; + if (!timeoutSuccess) return timeoutResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -829,20 +844,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { hidden: options.silent ?? false }); - if (!modlog) return timeoutResponse.MODLOG_ERROR; + if (!modlog) return timeoutResponse.ModlogError; caseID = modlog.id; - if (!options.silent) { + if (!options.noDM) { // dm user - const dmSuccess = await this.customPunishDM(Action.Timeout, options.reason, options.duration, modlog.id); - dmSuccessEvent = dmSuccess; - if (!dmSuccess) return timeoutResponse.DM_ERROR; + dmSuccess = await this.customPunishDM(Action.Timeout, options.reason, options.duration, modlog.id); + + if (dmSuccess === false) return timeoutResponse.DmError; } - return timeoutResponse.SUCCESS; + return timeoutResponse.Success; })(); - if (!([timeoutResponse.ACTION_ERROR, timeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent) + if (!([timeoutResponse.ActionError, timeoutResponse.ModlogError] as const).includes(ret) && !options.silent) this.client.emit( TanzaniteEvent.Timeout, this, @@ -851,28 +866,30 @@ export class ExtendedGuildMember extends GuildMember implements Extension { options.reason ?? undefined, caseID!, options.duration ?? 0, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; } public override async customRemoveTimeout(options: CustomPunishmentOptions): Promise<RemoveTimeoutResponse> { + options = this.#optionDefaults(options); + // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) - return removeTimeoutResponse.MISSING_PERMISSIONS; + return removeTimeoutResponse.MissingPermissions; - let caseID: string | undefined = undefined; - let dmSuccessEvent: boolean | undefined = undefined; + let caseID: string | null = null; + let dmSuccess: boolean | null = null; const moderator = await this.client.utils.resolveNonCachedUser(options.moderator ?? this.guild.members.me); - if (!moderator) return removeTimeoutResponse.CANNOT_RESOLVE_USER; + if (!moderator) return removeTimeoutResponse.CannotResolveUser; const ret = await (async () => { // remove timeout const timeoutSuccess = await this.timeout(null, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`).catch( () => false ); - if (!timeoutSuccess) return removeTimeoutResponse.ACTION_ERROR; + if (!timeoutSuccess) return removeTimeoutResponse.ActionError; // add modlog entry const { log: modlog } = await createModLogEntry({ @@ -886,20 +903,20 @@ export class ExtendedGuildMember extends GuildMember implements Extension { hidden: options.silent ?? false }); - if (!modlog) return removeTimeoutResponse.MODLOG_ERROR; + if (!modlog) return removeTimeoutResponse.ModlogError; caseID = modlog.id; - if (!options.silent) { + if (!options.noDM) { // dm user - const dmSuccess = await this.customPunishDM(Action.Untimeout, options.reason, undefined, '', false); - dmSuccessEvent = dmSuccess; - if (!dmSuccess) return removeTimeoutResponse.DM_ERROR; + dmSuccess = await this.customPunishDM(Action.Untimeout, options.reason, undefined, '', false); + + if (dmSuccess === false) return removeTimeoutResponse.DmError; } - return removeTimeoutResponse.SUCCESS; + return removeTimeoutResponse.Success; })(); - if (!([removeTimeoutResponse.ACTION_ERROR, removeTimeoutResponse.MODLOG_ERROR] as const).includes(ret) && !options.silent) + if (!([removeTimeoutResponse.ActionError, removeTimeoutResponse.ModlogError] as const).includes(ret) && !options.silent) this.client.emit( TanzaniteEvent.RemoveTimeout, this, @@ -907,7 +924,7 @@ export class ExtendedGuildMember extends GuildMember implements Extension { this.guild, options.reason ?? undefined, caseID!, - dmSuccessEvent!, + dmSuccess, options.evidence ); return ret; @@ -945,6 +962,12 @@ export interface CustomPunishmentOptions { * Makes the punishment silent by not sending the user a punishment dm and not broadcasting the event to be logged. */ silent?: boolean; + + /** + * Don't send a dm to the user. + * @default silent + */ + noDM?: boolean; } /** @@ -960,7 +983,7 @@ export interface CustomTimedPunishmentOptions extends CustomPunishmentOptions { /** * Options for a role add punishment. */ -export interface AddRoleOptions extends CustomTimedPunishmentOptions { +export interface AddRoleOptions extends Omit<CustomTimedPunishmentOptions, 'noDM'> { /** * The role to add to the user. */ @@ -975,7 +998,7 @@ export interface AddRoleOptions extends CustomTimedPunishmentOptions { /** * Options for a role remove punishment. */ -export interface RemoveRoleOptions extends CustomTimedPunishmentOptions { +export interface RemoveRoleOptions extends Omit<CustomTimedPunishmentOptions, 'noDM'> { /** * The role to remove from the user. */ @@ -1028,75 +1051,64 @@ export interface CustomTimeoutOptions extends CustomPunishmentOptions { } export const basePunishmentResponse = Object.freeze({ - SUCCESS: 'success', - MODLOG_ERROR: 'error creating modlog entry', - ACTION_ERROR: 'error performing action', - CANNOT_RESOLVE_USER: 'cannot resolve user' + Success: 'success', + ModlogError: 'error creating modlog entry', + ActionError: 'error performing action', + CannotResolveUser: 'cannot resolve user' } as const); export const dmResponse = Object.freeze({ ...basePunishmentResponse, - DM_ERROR: 'failed to dm' + DmError: 'failed to dm' } as const); export const permissionsResponse = Object.freeze({ - MISSING_PERMISSIONS: 'missing permissions' + MissingPermissions: 'missing permissions' } as const); -export const punishmentEntryAdd = Object.freeze({ - PUNISHMENT_ENTRY_ADD_ERROR: 'error creating punishment entry' -} as const); - -export const punishmentEntryRemove = Object.freeze({ - PUNISHMENT_ENTRY_REMOVE_ERROR: 'error removing punishment entry' +export const punishmentEntryError = Object.freeze({ + PunishmentEntryError: 'punishment entry error' } as const); export const shouldAddRoleResponse = Object.freeze({ - USER_HIERARCHY: 'user hierarchy', - CLIENT_HIERARCHY: 'client hierarchy', - ROLE_MANAGED: 'role managed' + UserHierarchy: 'user hierarchy', + ClientHierarchy: 'client hierarchy', + RoleManaged: 'role managed' } as const); export const baseBlockResponse = Object.freeze({ - INVALID_CHANNEL: 'invalid channel' + InvalidChannel: 'invalid channel' } as const); export const baseMuteResponse = Object.freeze({ - NO_MUTE_ROLE: 'no mute role', - MUTE_ROLE_INVALID: 'invalid mute role', - MUTE_ROLE_NOT_MANAGEABLE: 'mute role not manageable' + NoMuteRole: 'no mute role', + MuteRoleInvalid: 'invalid mute role', + MuteRoleNotManageable: 'mute role not manageable' } as const); export const warnResponse = Object.freeze({ ...dmResponse } as const); -export const addRoleResponse = Object.freeze({ - ...basePunishmentResponse, - ...permissionsResponse, - ...shouldAddRoleResponse, - ...punishmentEntryAdd -} as const); - -export const removeRoleResponse = Object.freeze({ +export const roleResponse = Object.freeze({ ...basePunishmentResponse, ...permissionsResponse, ...shouldAddRoleResponse, - ...punishmentEntryRemove + ...punishmentEntryError } as const); export const muteResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, ...baseMuteResponse, - ...punishmentEntryAdd + ...punishmentEntryError } as const); export const unmuteResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, ...baseMuteResponse, - ...punishmentEntryRemove + ...punishmentEntryError } as const); export const kickResponse = Object.freeze({ @@ -1107,28 +1119,28 @@ export const kickResponse = Object.freeze({ export const banResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, - ...punishmentEntryAdd, - ALREADY_BANNED: 'already banned' + ...punishmentEntryError, + AlreadyBanned: 'already banned' } as const); export const blockResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, ...baseBlockResponse, - ...punishmentEntryAdd + ...punishmentEntryError }); export const unblockResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, ...baseBlockResponse, - ...punishmentEntryRemove + ...punishmentEntryError }); export const timeoutResponse = Object.freeze({ ...dmResponse, ...permissionsResponse, - INVALID_DURATION: 'duration too long' + InvalidDuration: 'duration too long' } as const); export const removeTimeoutResponse = Object.freeze({ @@ -1144,12 +1156,12 @@ export type WarnResponse = ValueOf<typeof warnResponse>; /** * Response returned when adding a role to a user. */ -export type AddRoleResponse = ValueOf<typeof addRoleResponse>; +export type AddRoleResponse = ValueOf<typeof roleResponse>; /** * Response returned when removing a role from a user. */ -export type RemoveRoleResponse = ValueOf<typeof removeRoleResponse>; +export type RemoveRoleResponse = ValueOf<typeof roleResponse>; /** * Response returned when muting a user. diff --git a/lib/utils/FormatResponse.ts b/lib/utils/FormatResponse.ts index 470fea7..14583b3 100644 --- a/lib/utils/FormatResponse.ts +++ b/lib/utils/FormatResponse.ts @@ -1,32 +1,275 @@ -import type { GuildMember } from 'discord.js'; -import { unmuteResponse, UnmuteResponse } from '../extensions/discord.js/ExtendedGuildMember.js'; +import { unbanResponse, type UnbanResponse } from '#lib/extensions/discord.js/ExtendedGuild.js'; +import { + AddRoleResponse, + banResponse, + blockResponse, + kickResponse, + muteResponse, + removeTimeoutResponse, + roleResponse, + timeoutResponse, + unblockResponse, + unmuteResponse, + warnResponse, + WarnResponse, + type BanResponse, + type BlockResponse, + type KickResponse, + type MuteResponse, + type TimeoutResponse, + type UnblockResponse, + type UnmuteResponse +} from '#lib/extensions/discord.js/ExtendedGuildMember.js'; +import type { GuildMember, Snowflake, User } from 'discord.js'; import { emojis } from './Constants.js'; import { input } from './Format.js'; -import { format } from './Utils.js'; +import { format, humanizeDuration, ordinal } from './Utils.js'; + +export function formatBanResponse(user: User, code: BanResponse): string { + const victim = format.input(user.tag); + switch (code) { + case banResponse.AlreadyBanned: + return `${emojis.error} ${victim} is already banned.`; + case banResponse.MissingPermissions: + return `${emojis.error} Could not ban ${victim} because I am missing the **Ban Members** permission.`; + case banResponse.ActionError: + return `${emojis.error} An error occurred while trying to ban ${victim}.`; + case banResponse.PunishmentEntryError: + return `${emojis.error} While banning ${victim}, there was an error creating a ban entry, please report this to my developers.`; + case banResponse.ModlogError: + return `${emojis.error} While banning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case banResponse.DmError: + return `${emojis.warn} Banned ${victim} however I could not send them a dm.`; + case banResponse.Success: + return `${emojis.success} Successfully banned ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatUnbanResponse(user: User, code: UnbanResponse): string { + const victim = format.input(user.tag); + switch (code) { + case unbanResponse.MissingPermissions: + return `${emojis.error} Could not unban ${victim} because I am missing the **Ban Members** permission.`; + case unbanResponse.ActionError: + return `${emojis.error} An error occurred while trying to unban ${victim}.`; + case unbanResponse.PunishmentEntryError: + return `${emojis.error} While unbanning ${victim}, there was an error removing their ban entry, please report this to my developers.`; + case unbanResponse.ModlogError: + return `${emojis.error} While unbanning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case unbanResponse.NotBanned: + return `${emojis.warn} ${victim} is not banned but I tried to unban them anyways.`; + case unbanResponse.DmError: + case unbanResponse.Success: + return `${emojis.success} Successfully unbanned ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatBlockResponse(member: GuildMember, code: BlockResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case blockResponse.MissingPermissions: + return `${emojis.error} Could not block ${victim} because I am missing the **Manage Channel** permission.`; + case blockResponse.InvalidChannel: + return `${emojis.error} Could not block ${victim}, you can only block users in text or thread channels.`; + case blockResponse.ActionError: + return `${emojis.error} An unknown error occurred while trying to block ${victim}.`; + case blockResponse.ModlogError: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case blockResponse.PunishmentEntryError: + return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; + case blockResponse.DmError: + return `${emojis.warn} Blocked ${victim} however I could not send them a dm.`; + case blockResponse.Success: + return `${emojis.success} Successfully blocked ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatUnblockResponse(member: GuildMember, code: UnblockResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case unblockResponse.MissingPermissions: + return `${emojis.error} Could not unblock ${victim} because I am missing the **Manage Channel** permission.`; + case unblockResponse.InvalidChannel: + return `${emojis.error} Could not unblock ${victim}, you can only unblock users in text or thread channels.`; + case unblockResponse.ActionError: + return `${emojis.error} An unknown error occurred while trying to unblock ${victim}.`; + case unblockResponse.ModlogError: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case unblockResponse.PunishmentEntryError: + return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; + case unblockResponse.DmError: + return `${emojis.warn} Unblocked ${victim} however I could not send them a dm.`; + case unblockResponse.Success: + return `${emojis.success} Successfully unblocked ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatMuteResponse(prefix: string, member: GuildMember, code: MuteResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case muteResponse.MissingPermissions: + return `${emojis.error} Could not mute ${victim} because I am missing the **Manage Roles** permission.`; + case muteResponse.NoMuteRole: + return `${emojis.error} Could not mute ${victim}, you must set a mute role with \`${prefix}config muteRole\`.`; + case muteResponse.MuteRoleInvalid: + return `${emojis.error} Could not mute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix}config muteRole\`.`; + case muteResponse.MuteRoleNotManageable: + return `${emojis.error} Could not mute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix}config muteRole\`.`; + case muteResponse.ActionError: + return `${emojis.error} Could not mute ${victim}, there was an error assigning them the mute role.`; + case muteResponse.ModlogError: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case muteResponse.PunishmentEntryError: + return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; + case muteResponse.DmError: + return `${emojis.warn} Muted ${victim} however I could not send them a dm.`; + case muteResponse.Success: + return `${emojis.success} Successfully muted ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} export function formatUnmuteResponse(prefix: string, member: GuildMember, code: UnmuteResponse): string { const error = emojis.error; const victim = input(member.user.tag); switch (code) { - case unmuteResponse.MISSING_PERMISSIONS: + case unmuteResponse.MissingPermissions: return `${error} Could not unmute ${victim} because I am missing the **Manage Roles** permission.`; - case unmuteResponse.NO_MUTE_ROLE: + case unmuteResponse.NoMuteRole: return `${error} Could not unmute ${victim}, you must set a mute role with \`${prefix}config muteRole\`.`; - case unmuteResponse.MUTE_ROLE_INVALID: + case unmuteResponse.MuteRoleInvalid: return `${error} Could not unmute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix}config muteRole\`.`; - case unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE: + case unmuteResponse.MuteRoleNotManageable: return `${error} Could not unmute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix}config muteRole\`.`; - case unmuteResponse.ACTION_ERROR: + case unmuteResponse.ActionError: return `${error} Could not unmute ${victim}, there was an error removing their mute role.`; - case unmuteResponse.MODLOG_ERROR: + case unmuteResponse.ModlogError: return `${error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: + case unmuteResponse.PunishmentEntryError: return `${error} While muting ${victim}, there was an error removing their mute entry, please report this to my developers.`; - case unmuteResponse.DM_ERROR: + case unmuteResponse.DmError: return `${emojis.warn} unmuted ${victim} however I could not send them a dm.`; - case unmuteResponse.SUCCESS: + case unmuteResponse.Success: return `${emojis.success} Successfully unmuted ${victim}.`; default: return `${emojis.error} An error occurred: ${format.input(code)}}`; } } + +export function formatTimeoutResponse(member: GuildMember, code: TimeoutResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case timeoutResponse.MissingPermissions: + return `${emojis.error} Could not timeout ${victim} because I am missing the **Timeout Members** permission.`; + case timeoutResponse.InvalidDuration: + return `${emojis.error} The duration you specified is too long, the longest you can timeout someone for is 28 days.`; + case timeoutResponse.ActionError: + return `${emojis.error} An unknown error occurred while trying to timeout ${victim}.`; + case timeoutResponse.ModlogError: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case timeoutResponse.DmError: + return `${emojis.warn} Timed out ${victim} however I could not send them a dm.`; + case timeoutResponse.Success: + return `${emojis.success} Successfully timed out ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatUntimeoutResponse(member: GuildMember, code: TimeoutResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case removeTimeoutResponse.MissingPermissions: + return `${emojis.error} Could not untimeout ${victim} because I am missing the **Timeout Members** permission.`; + case removeTimeoutResponse.ActionError: + return `${emojis.error} An unknown error occurred while trying to timeout ${victim}.`; + case removeTimeoutResponse.ModlogError: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case removeTimeoutResponse.DmError: + return `${emojis.warn} Removed ${victim}'s timeout however I could not send them a dm.`; + case removeTimeoutResponse.Success: + return `${emojis.success} Successfully removed ${victim}'s timeout.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatKickResponse(member: GuildMember, code: KickResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case kickResponse.MissingPermissions: + return `${emojis.error} Could not kick ${victim} because I am missing the **Kick Members** permission.`; + case kickResponse.ActionError: + return `${emojis.error} An error occurred while trying to kick ${victim}.`; + case kickResponse.ModlogError: + return `${emojis.error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case kickResponse.DmError: + return `${emojis.warn} Kicked ${victim} however I could not send them a dm.`; + case kickResponse.Success: + return `${emojis.success} Successfully kicked ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatWarnResponse(caseNum: number | null, member: GuildMember, code: WarnResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case warnResponse.ModlogError: + return `${emojis.error} While warning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case warnResponse.ActionError: + case warnResponse.DmError: + return `${emojis.warn} ${victim} has been warned for the ${ordinal( + caseNum ?? 0 + )} time, however I could not send them a dm.`; + case warnResponse.Success: + return `${emojis.success} Successfully warned ${victim} for the ${ordinal(caseNum ?? 0)} time.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} + +export function formatRoleResponse( + roleId: Snowflake, + action: 'add' | 'remove', + duration: number | null, + member: GuildMember, + code: AddRoleResponse +): string { + const victim = format.input(member.user.tag); + switch (code) { + case roleResponse.MissingPermissions: + return `${emojis.error} I don't have the **Manage Roles** permission.`; + case roleResponse.UserHierarchy: + return `${emojis.error} <@&${roleId}> is higher or equal to your highest role.`; + case roleResponse.RoleManaged: + return `${emojis.error} <@&${roleId}> is managed by an integration and cannot be managed.`; + case roleResponse.ClientHierarchy: + return `${emojis.error} <@&${roleId}> is higher or equal to my highest role.`; + case roleResponse.ModlogError: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case roleResponse.PunishmentEntryError: + return `${emojis.error} There was an error ${ + action === 'add' ? 'creating' : 'removing' + } a punishment entry, please report this to my developers.`; + case roleResponse.ActionError: + return `${emojis.error} An error occurred while trying to ${action} <@&${roleId}> ${ + action === 'add' ? 'to' : 'from' + } ${victim}.`; + case roleResponse.Success: + return `${emojis.success} Successfully ${action === 'add' ? 'added' : 'removed'} <@&${roleId}> ${ + action === 'add' ? 'to' : 'from' + } ${victim}${duration ? ` for ${humanizeDuration(duration)}` : ''}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } +} |