diff options
-rw-r--r-- | .eslintrc.cjs | 2 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/BotCommandHandler.ts | 6 | ||||
-rw-r--r-- | lib/extensions/discord.js/ExtendedGuild.ts | 276 | ||||
-rw-r--r-- | lib/extensions/discord.js/ExtendedGuildMember.ts | 262 | ||||
-rw-r--r-- | lib/extensions/discord.js/ExtendedUser.ts | 34 | ||||
-rw-r--r-- | lib/utils/Utils.ts | 30 | ||||
-rw-r--r-- | src/commands/config/config.ts | 6 | ||||
-rw-r--r-- | src/commands/config/log.ts | 4 | ||||
-rw-r--r-- | src/commands/info/userInfo.ts | 8 | ||||
-rw-r--r-- | src/commands/utilities/price.ts | 4 | ||||
-rw-r--r-- | src/commands/utilities/whoHasRole.ts | 4 | ||||
-rw-r--r-- | src/listeners/commands/commandBlocked.ts | 6 | ||||
-rw-r--r-- | src/listeners/commands/commandMissingPermissions.ts | 8 | ||||
-rw-r--r-- | src/listeners/interaction/interactionCreate.ts | 6 |
14 files changed, 256 insertions, 400 deletions
diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 550cb28..e7a6910 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -131,7 +131,7 @@ module.exports = { plugins: ['@typescript-eslint', 'deprecation'], rules: { 'no-return-await': 'off', - '@typescript-eslint/no-empty-interface': 'warn', + '@typescript-eslint/no-empty-interface': 'off', 'no-mixed-spaces-and-tabs': 'off', 'no-duplicate-imports': 'warn', 'no-empty-function': 'off', diff --git a/lib/extensions/discord-akairo/BotCommandHandler.ts b/lib/extensions/discord-akairo/BotCommandHandler.ts index 71d9ad4..e9b509f 100644 --- a/lib/extensions/discord-akairo/BotCommandHandler.ts +++ b/lib/extensions/discord-akairo/BotCommandHandler.ts @@ -55,12 +55,6 @@ export class BotCommandHandler extends CommandHandler { const appSlashPerms = slash ? (message as SlashMessage).interaction.appPermissions : null; const userSlashPerms = slash ? (message as SlashMessage).interaction.memberPermissions : null; - console.dir(message); - console.dir(appSlashPerms); - console.dir(userSlashPerms); - console.dir(event); - console.dir(command); - const noPerms = message.channel == null || (message.channel.isThread() && message.channel.parent == null); if (message.inGuild()) { diff --git a/lib/extensions/discord.js/ExtendedGuild.ts b/lib/extensions/discord.js/ExtendedGuild.ts index 6b69206..6bf81ee 100644 --- a/lib/extensions/discord.js/ExtendedGuild.ts +++ b/lib/extensions/discord.js/ExtendedGuild.ts @@ -1,4 +1,10 @@ -import * as Moderation from '#lib/common/Moderation.js'; +import { + createModLogEntry, + createModLogEntrySimple, + createPunishmentEntry, + punishDM, + removePunishmentEntry +} from '#lib/common/Moderation.js'; import { Guild as GuildDB, GuildFeatures, GuildLogType, GuildModel, ModLogType } from '#lib/models/index.js'; import { AllowedMentions } from '#lib/utils/AllowedMentions.js'; import { colors, emojis, TanzaniteEvent } from '#lib/utils/Constants.js'; @@ -34,98 +40,105 @@ import _ from 'lodash'; import { TanzaniteClient } from '../discord-akairo/TanzaniteClient.js'; import { banResponse, BanResponse, dmResponse, permissionsResponse, punishmentEntryRemove } from './ExtendedGuildMember.js'; +interface Extension { + /** + * Checks if the guild has a certain custom feature. + * @param feature The feature to check for + */ + hasFeature(feature: GuildFeatures): Promise<boolean>; + /** + * Adds a custom feature to the guild. + * @param feature The feature to add + * @param moderator The moderator responsible for adding a feature + */ + addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>; + /** + * Removes a custom feature from the guild. + * @param feature The feature to remove + * @param moderator The moderator responsible for removing a feature + */ + removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>; + /** + * Makes a custom feature the opposite of what it was before + * @param feature The feature to toggle + * @param moderator The moderator responsible for toggling a feature + */ + toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>; + /** + * Fetches a custom setting for the guild + * @param setting The setting to get + */ + getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]>; + /** + * Sets a custom setting for the guild + * @param setting The setting to change + * @param value The value to change the setting to + * @param moderator The moderator to responsible for changing the setting + */ + setSetting<K extends Exclude<keyof GuildModel, 'id'>>( + setting: K, + value: GuildModel[K], + moderator?: GuildMember + ): Promise<GuildModel>; + /** + * Get a the log channel configured for a certain log type. + * @param logType The type of log channel to get. + * @returns Either the log channel or undefined if not configured. + */ + getLogChannel(logType: GuildLogType): Promise<TextChannel | undefined>; + /** + * Sends a message to the guild's specified logging channel + * @param logType The corresponding channel that the message will be sent to + * @param message The parameters for {@link TextChannel.send} + */ + sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise<Message | null | undefined>; + /** + * Sends a formatted error message in a guild's error log channel + * @param title The title of the error embed + * @param message The description of the error embed + */ + error(title: string, message: string): Promise<void>; + /** + * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. + * @param options Options for banning the user. + * @returns A string status message of the ban. + */ + customBan(options: GuildCustomBanOptions): Promise<BanResponse>; + /** + * {@link customBan} with less resolving and checks + * @param options Options for banning the user. + * @returns A string status message of the ban. + * **Preconditions:** + * - {@link me} has the `BanMembers` permission + * **Warning:** + * - Doesn't emit customBan Event + */ + massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse>; + /** + * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry. + * @param options Options for unbanning the user. + * @returns A status message of the unban. + */ + customUnban(options: GuildCustomUnbanOptions): Promise<UnbanResponse>; + /** + * Denies send permissions in specified channels + * @param options The options for locking down the guild + */ + lockdown(options: LockdownOptions): Promise<LockdownResponse>; + /** + * Reposts a message with a webhook + * @param rawQuote The original message to repost + * @param channel The channel to repost the message in + */ + quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise<Message | null>; +} + declare module 'discord.js' { export interface BaseGuild { client: TanzaniteClient; } - export interface Guild { - /** - * Checks if the guild has a certain custom feature. - * @param feature The feature to check for - */ - hasFeature(feature: GuildFeatures): Promise<boolean>; - /** - * Adds a custom feature to the guild. - * @param feature The feature to add - * @param moderator The moderator responsible for adding a feature - */ - addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>; - /** - * Removes a custom feature from the guild. - * @param feature The feature to remove - * @param moderator The moderator responsible for removing a feature - */ - removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>; - /** - * Makes a custom feature the opposite of what it was before - * @param feature The feature to toggle - * @param moderator The moderator responsible for toggling a feature - */ - toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildDB['enabledFeatures']>; - /** - * Fetches a custom setting for the guild - * @param setting The setting to get - */ - getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]>; - /** - * Sets a custom setting for the guild - * @param setting The setting to change - * @param value The value to change the setting to - * @param moderator The moderator to responsible for changing the setting - */ - setSetting<K extends Exclude<keyof GuildModel, 'id'>>( - setting: K, - value: GuildModel[K], - moderator?: GuildMember - ): Promise<GuildModel>; - /** - * Get a the log channel configured for a certain log type. - * @param logType The type of log channel to get. - * @returns Either the log channel or undefined if not configured. - */ - getLogChannel(logType: GuildLogType): Promise<TextChannel | undefined>; - /** - * Sends a message to the guild's specified logging channel - * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link TextChannel.send} - */ - sendLogChannel(logType: GuildLogType, message: string | MessagePayload | MessageOptions): Promise<Message | null | undefined>; - /** - * Sends a formatted error message in a guild's error log channel - * @param title The title of the error embed - * @param message The description of the error embed - */ - error(title: string, message: string): Promise<void>; - /** - * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. - * @param options Options for banning the user. - * @returns A string status message of the ban. - */ - customBan(options: GuildCustomBanOptions): Promise<BanResponse>; - /** - * {@link customBan} with less resolving and checks - * @param options Options for banning the user. - * @returns A string status message of the ban. - * **Preconditions:** - * - {@link me} has the `BanMembers` permission - * **Warning:** - * - Doesn't emit customBan Event - */ - massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse>; - /** - * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry. - * @param options Options for unbanning the user. - * @returns A status message of the unban. - */ - customUnban(options: GuildCustomUnbanOptions): Promise<UnbanResponse>; - /** - * Denies send permissions in specified channels - * @param options The options for locking down the guild - */ - lockdown(options: LockdownOptions): Promise<LockdownResponse>; - quote(rawQuote: APIMessage, channel: GuildTextBasedChannel): Promise<Message | null>; - } + export interface Guild extends AnonymousGuild, Extension {} } /** @@ -133,53 +146,30 @@ declare module 'discord.js' { * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can * check this with {@link ExtendedGuild.available}.</info> */ -export class ExtendedGuild extends Guild { - /** - * Checks if the guild has a certain custom feature. - * @param feature The feature to check for - */ +export class ExtendedGuild extends Guild implements Extension { public override async hasFeature(feature: GuildFeatures): Promise<boolean> { const features = await this.getSetting('enabledFeatures'); return features.includes(feature); } - /** - * Adds a custom feature to the guild. - * @param feature The feature to add - * @param moderator The moderator responsible for adding a feature - */ public override async addFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildModel['enabledFeatures']> { const features = await this.getSetting('enabledFeatures'); const newFeatures = addOrRemoveFromArray('add', features, feature); return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; } - /** - * Removes a custom feature from the guild. - * @param feature The feature to remove - * @param moderator The moderator responsible for removing a feature - */ public override async removeFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildModel['enabledFeatures']> { const features = await this.getSetting('enabledFeatures'); const newFeatures = addOrRemoveFromArray('remove', features, feature); return (await this.setSetting('enabledFeatures', newFeatures, moderator)).enabledFeatures; } - /** - * Makes a custom feature the opposite of what it was before - * @param feature The feature to toggle - * @param moderator The moderator responsible for toggling a feature - */ public override async toggleFeature(feature: GuildFeatures, moderator?: GuildMember): Promise<GuildModel['enabledFeatures']> { return (await this.hasFeature(feature)) ? await this.removeFeature(feature, moderator) : await this.addFeature(feature, moderator); } - /** - * Fetches a custom setting for the guild - * @param setting The setting to get - */ public override async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> { return ( this.client.cache.guilds.get(this.id)?.[setting] ?? @@ -187,12 +177,6 @@ export class ExtendedGuild extends Guild { ); } - /** - * Sets a custom setting for the guild - * @param setting The setting to change - * @param value The value to change the setting to - * @param moderator The moderator to responsible for changing the setting - */ public override async setSetting<K extends Exclude<keyof GuildModel, 'id'>>( setting: K, value: GuildDB[K], @@ -206,11 +190,6 @@ export class ExtendedGuild extends Guild { return await row.save(); } - /** - * Get a the log channel configured for a certain log type. - * @param logType The type of log channel to get. - * @returns Either the log channel or undefined if not configured. - */ public override async getLogChannel(logType: GuildLogType): Promise<TextChannel | undefined> { const channelId = (await this.getSetting('logChannels'))[logType]; if (!channelId) return undefined; @@ -221,11 +200,6 @@ export class ExtendedGuild extends Guild { ); } - /** - * Sends a message to the guild's specified logging channel - * @param logType The corresponding channel that the message will be sent to - * @param message The parameters for {@link TextChannel.send} - */ public override async sendLogChannel( logType: GuildLogType, message: string | MessagePayload | MessageOptions @@ -245,21 +219,11 @@ export class ExtendedGuild extends Guild { return await logChannel.send(message).catch(() => null); } - /** - * Sends a formatted error message in a guild's error log channel - * @param title The title of the error embed - * @param message The description of the error embed - */ public override async error(title: string, message: string): Promise<void> { void this.client.console.info(_.camelCase(title), message.replace(/\*\*(.*?)\*\*/g, '<<$1>>')); void this.sendLogChannel('error', { embeds: [{ title: title, description: message, color: colors.error }] }); } - /** - * Bans a user, dms them, creates a mod log entry, and creates a punishment entry. - * @param options Options for banning the user. - * @returns A string status message of the ban. - */ public override async customBan(options: GuildCustomBanOptions): Promise<BanResponse> { // checks if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return banResponse.MISSING_PERMISSIONS; @@ -274,7 +238,7 @@ export class ExtendedGuild extends Guild { const ret = await (async () => { // add modlog entry - const { log: modlog } = await Moderation.createModLogEntry({ + const { log: modlog } = await createModLogEntry({ client: this.client, type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN, user: user, @@ -288,7 +252,7 @@ export class ExtendedGuild extends Guild { caseID = modlog.id; // dm user - dmSuccessEvent = await Moderation.punishDM({ + dmSuccessEvent = await punishDM({ client: this.client, modlog: modlog.id, guild: this, @@ -309,7 +273,7 @@ export class ExtendedGuild extends Guild { if (!banSuccess) return banResponse.ACTION_ERROR; // add punishment entry so they can be unbanned later - const punishmentEntrySuccess = await Moderation.createPunishmentEntry({ + const punishmentEntrySuccess = await createPunishmentEntry({ client: this.client, type: 'ban', user: user, @@ -338,21 +302,12 @@ export class ExtendedGuild extends Guild { return ret; } - /** - * {@link customBan} with less resolving and checks - * @param options Options for banning the user. - * @returns A string status message of the ban. - * **Preconditions:** - * - {@link me} has the `BanMembers` permission - * **Warning:** - * - Doesn't emit customBan Event - */ public override async massBanOne(options: GuildMassBanOneOptions): Promise<BanResponse> { if (this.bans.cache.has(options.user)) return banResponse.ALREADY_BANNED; const ret = await (async () => { // add modlog entry - const { log: modlog } = await Moderation.createModLogEntrySimple({ + const { log: modlog } = await createModLogEntrySimple({ client: this.client, type: ModLogType.PERM_BAN, user: options.user, @@ -366,7 +321,7 @@ export class ExtendedGuild extends Guild { let dmSuccessEvent: boolean | undefined = undefined; // dm user if (this.members.cache.has(options.user)) { - dmSuccessEvent = await Moderation.punishDM({ + dmSuccessEvent = await punishDM({ client: this.client, modlog: modlog.id, guild: this, @@ -388,7 +343,7 @@ export class ExtendedGuild extends Guild { if (!banSuccess) return banResponse.ACTION_ERROR; // add punishment entry so they can be unbanned later - const punishmentEntrySuccess = await Moderation.createPunishmentEntry({ + const punishmentEntrySuccess = await createPunishmentEntry({ client: this.client, type: 'ban', user: options.user, @@ -404,11 +359,6 @@ export class ExtendedGuild extends Guild { return ret; } - /** - * Unbans a user, dms them, creates a mod log entry, and destroys the punishment entry. - * @param options Options for unbanning the user. - * @returns A status message of the unban. - */ public override async customUnban(options: GuildCustomUnbanOptions): Promise<UnbanResponse> { // checks if (!this.members.me!.permissions.has(PermissionFlagsBits.BanMembers)) return unbanResponse.MISSING_PERMISSIONS; @@ -438,7 +388,7 @@ export class ExtendedGuild extends Guild { if (!unbanSuccess) return unbanResponse.ACTION_ERROR; // add modlog entry - const { log: modlog } = await Moderation.createModLogEntry({ + const { log: modlog } = await createModLogEntry({ client: this.client, type: ModLogType.UNBAN, user: user.id, @@ -451,7 +401,7 @@ export class ExtendedGuild extends Guild { caseID = modlog.id; // remove punishment entry - const removePunishmentEntrySuccess = await Moderation.removePunishmentEntry({ + const removePunishmentEntrySuccess = await removePunishmentEntry({ client: this.client, type: 'ban', user: user.id, @@ -460,7 +410,7 @@ export class ExtendedGuild extends Guild { if (!removePunishmentEntrySuccess) return unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR; // dm user - dmSuccessEvent = await Moderation.punishDM({ + dmSuccessEvent = await punishDM({ client: this.client, guild: this, user: user, @@ -490,10 +440,6 @@ export class ExtendedGuild extends Guild { return ret; } - /** - * Denies send permissions in specified channels - * @param options The options for locking down the guild - */ public override async lockdown(options: LockdownOptions): Promise<LockdownResponse> { if (!options.all && !options.channel) return 'all not chosen and no channel specified'; const channelIds = options.all ? await this.getSetting('lockdownChannels') : [options.channel!.id]; diff --git a/lib/extensions/discord.js/ExtendedGuildMember.ts b/lib/extensions/discord.js/ExtendedGuildMember.ts index 043cc1d..9ef45f1 100644 --- a/lib/extensions/discord.js/ExtendedGuildMember.ts +++ b/lib/extensions/discord.js/ExtendedGuildMember.ts @@ -21,110 +21,7 @@ import { formatError, ValueOf } from '../../utils/Utils.js'; import { TanzaniteClient } from '../discord-akairo/TanzaniteClient.js'; /* eslint-enable @typescript-eslint/no-unused-vars */ -declare module 'discord.js' { - export interface GuildMember { - client: TanzaniteClient; - - /** - * Send a punishment dm to the user. - * @param punishment The punishment that the user has received. - * @param reason The reason for the user's punishment. - * @param duration The duration of the punishment. - * @param modlog The modlog case id so the user can make an appeal. - * @param sendFooter Whether or not to send the guild's punishment footer with the dm. - * @returns Whether or not the dm was sent successfully. - */ - customPunishDM( - punishment: PunishmentTypeDM, - reason?: string | null, - duration?: number, - modlog?: string, - sendFooter?: boolean - ): Promise<boolean>; - /** - * 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} - */ - 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} - */ - 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} - */ - 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} - */ - 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} - */ - 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} - */ - 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} - */ - customBan(options: CustomBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>>; - /** - * Prevents a user from speaking in a channel. - * @param options Options for blocking the user. - */ - customBlock(options: BlockOptions): Promise<BlockResponse>; - /** - * Allows a user to speak in a channel. - * @param options Options for unblocking the user. - */ - customUnblock(options: UnblockOptions): Promise<UnblockResponse>; - /** - * Mutes a user using discord's timeout feature. - * @param options Options for timing out the user. - */ - customTimeout(options: CustomTimeoutOptions): Promise<TimeoutResponse>; - /** - * Removes a timeout from a user. - * @param options Options for removing the timeout. - */ - customRemoveTimeout(options: CustomPunishmentOptions): Promise<RemoveTimeoutResponse>; - /** - * Whether or not the user is an owner of the bot. - */ - isOwner(): boolean; - /** - * Whether or not the user is a super user of the bot. - */ - isSuperUser(): boolean; - } -} - -/** - * Represents a member of a guild on Discord. - */ -export class ExtendedGuildMember extends GuildMember { +interface Extension { /** * Send a punishment dm to the user. * @param punishment The punishment that the user has received. @@ -134,6 +31,99 @@ export class ExtendedGuildMember extends GuildMember { * @param sendFooter Whether or not to send the guild's punishment footer with the dm. * @returns Whether or not the dm was sent successfully. */ + customPunishDM( + punishment: PunishmentTypeDM, + reason?: string | null, + duration?: number, + modlog?: string, + sendFooter?: boolean + ): Promise<boolean>; + /** + * 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} + */ + 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} + */ + 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} + */ + 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} + */ + 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} + */ + 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} + */ + 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} + */ + customBan(options: CustomBanOptions): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>>; + /** + * Prevents a user from speaking in a channel. + * @param options Options for blocking the user. + */ + customBlock(options: BlockOptions): Promise<BlockResponse>; + /** + * Allows a user to speak in a channel. + * @param options Options for unblocking the user. + */ + customUnblock(options: UnblockOptions): Promise<UnblockResponse>; + /** + * Mutes a user using discord's timeout feature. + * @param options Options for timing out the user. + */ + customTimeout(options: CustomTimeoutOptions): Promise<TimeoutResponse>; + /** + * Removes a timeout from a user. + * @param options Options for removing the timeout. + */ + customRemoveTimeout(options: CustomPunishmentOptions): Promise<RemoveTimeoutResponse>; + /** + * Whether or not the user is an owner of the bot. + */ + isOwner(): boolean; + /** + * Whether or not the user is a super user of the bot. + */ + isSuperUser(): boolean; +} + +declare module 'discord.js' { + export interface GuildMember extends Extension { + readonly client: TanzaniteClient; + } +} + +export class ExtendedGuildMember extends GuildMember implements Extension { public override async customPunishDM( punishment: PunishmentTypeDM, reason?: string | null, @@ -153,12 +143,6 @@ export class ExtendedGuildMember extends GuildMember { }); } - /** - * 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} - */ public override async customWarn(options: CustomPunishmentOptions): Promise<{ result: WarnResponse; caseNum: number | null }> { let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; @@ -197,12 +181,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * 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} - */ public override async customAddRole(options: AddRoleOptions): Promise<AddRoleResponse> { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return addRoleResponse.MISSING_PERMISSIONS; @@ -270,12 +248,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * 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} - */ public override async customRemoveRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return removeRoleResponse.MISSING_PERMISSIONS; @@ -363,12 +335,6 @@ export class ExtendedGuildMember extends GuildMember { return true; } - /** - * 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} - */ public override async customMute(options: CustomTimedPunishmentOptions): Promise<MuteResponse> { // checks const checks = await checkMutePermissions(this.guild); @@ -449,12 +415,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * 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} - */ public override async customUnmute(options: CustomPunishmentOptions): Promise<UnmuteResponse> { // checks const checks = await checkMutePermissions(this.guild); @@ -532,12 +492,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * 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} - */ public override async customKick(options: CustomPunishmentOptions): Promise<KickResponse> { // checks if (!this.guild.members.me?.permissions.has(PermissionFlagsBits.KickMembers) || !this.kickable) @@ -587,12 +541,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * 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} - */ public override async customBan( options: CustomBanOptions ): Promise<Exclude<BanResponse, typeof banResponse['ALREADY_BANNED']>> { @@ -673,10 +621,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * Prevents a user from speaking in a channel. - * @param options Options for blocking the user. - */ public override async customBlock(options: BlockOptions): Promise<BlockResponse> { const channel = this.guild.channels.resolve(options.channel); if (!channel || (!channel.isTextBased() && !channel.isThread())) return blockResponse.INVALID_CHANNEL; @@ -766,10 +710,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * Allows a user to speak in a channel. - * @param options Options for unblocking the user. - */ public override async customUnblock(options: UnblockOptions): Promise<UnblockResponse> { const _channel = this.guild.channels.resolve(options.channel); if (!_channel || (_channel.type !== ChannelType.GuildText && !_channel.isThread())) return unblockResponse.INVALID_CHANNEL; @@ -856,10 +796,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * Mutes a user using discord's timeout feature. - * @param options Options for timing out the user. - */ public override async customTimeout(options: CustomTimeoutOptions): Promise<TimeoutResponse> { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) return timeoutResponse.MISSING_PERMISSIONS; @@ -921,10 +857,6 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * Removes a timeout from a user. - * @param options Options for removing the timeout. - */ public override async customRemoveTimeout(options: CustomPunishmentOptions): Promise<RemoveTimeoutResponse> { // checks if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ModerateMembers)) @@ -981,16 +913,10 @@ export class ExtendedGuildMember extends GuildMember { return ret; } - /** - * Whether or not the user is an owner of the bot. - */ public override isOwner(): boolean { return this.client.isOwner(this); } - /** - * Whether or not the user is a super user of the bot. - */ public override isSuperUser(): boolean { return this.client.isSuperUser(this); } diff --git a/lib/extensions/discord.js/ExtendedUser.ts b/lib/extensions/discord.js/ExtendedUser.ts index 65b14c7..7846a70 100644 --- a/lib/extensions/discord.js/ExtendedUser.ts +++ b/lib/extensions/discord.js/ExtendedUser.ts @@ -1,32 +1,28 @@ import { User } from 'discord.js'; +import type { TanzaniteClient } from '../discord-akairo/TanzaniteClient.js'; + +interface Extension { + /** + * Indicates whether the user is an owner of the bot. + */ + isOwner(): boolean; + /** + * Indicates whether the user is a superuser of the bot. + */ + isSuperUser(): boolean; +} declare module 'discord.js' { - export interface User { - /** - * Indicates whether the user is an owner of the bot. - */ - isOwner(): boolean; - /** - * Indicates whether the user is a superuser of the bot. - */ - isSuperUser(): boolean; + export interface User extends Extension { + readonly client: TanzaniteClient; } } -/** - * Represents a user on Discord. - */ -export class ExtendedUser extends User { - /** - * Indicates whether the user is an owner of the bot. - */ +export class ExtendedUser extends User implements Extension { public override isOwner(): boolean { return this.client.isOwner(this); } - /** - * Indicates whether the user is a superuser of the bot. - */ public override isSuperUser(): boolean { return this.client.isSuperUser(this); } diff --git a/lib/utils/Utils.ts b/lib/utils/Utils.ts index 9280e05..13806ec 100644 --- a/lib/utils/Utils.ts +++ b/lib/utils/Utils.ts @@ -165,24 +165,18 @@ export async function slashRespond( } /** - * Takes an array and combines the elements using the supplied conjunction. - * @param array The array to combine. - * @param conjunction The conjunction to use. - * @param ifEmpty What to return if the array is empty. - * @returns The combined elements or `ifEmpty`. - * - * @example - * const permissions = oxford(['Administrator', 'SendMessages', 'ManageMessages'], 'and', 'none'); - * console.log(permissions); // Administrator, SendMessages and ManageMessages + * A shortcut for {@link Intl.ListFormat.format} + * @param list The list to format. + * @param type The conjunction to use: `conjunction` 🡒 `and`, `disjunction` 🡒 `or` + * @returns The formatted list. */ -export function oxford(array: string[], conjunction: string, ifEmpty?: string): string | undefined { - const l = array.length; - if (!l) return ifEmpty; - if (l < 2) return array[0]; - if (l < 3) return array.join(` ${conjunction} `); - array = array.slice(); - array[l - 1] = `${conjunction} ${array[l - 1]}`; - return array.join(', '); +export function formatList(list: Iterable<string>, type: Intl.ListFormatType | 'and' | 'or'): string | undefined { + if (type === 'and') type = 'conjunction'; + if (type === 'or') type = 'disjunction'; + + const formatter = new Intl.ListFormat('en', { style: 'long', type }); + + return formatter.format(list); } /** @@ -221,7 +215,7 @@ export function addToArray<T>(array: T[], value: T): T[] { * @param surroundChar1 The character placed in the beginning of the element. * @param surroundChar2 The character placed in the end of the element. Defaults to `surroundChar1`. */ -export function surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] { +export function surroundEach(array: string[], surroundChar1: string, surroundChar2?: string): string[] { return array.map((a) => `${surroundChar1}${a}${surroundChar2 ?? surroundChar1}`); } diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index b923d56..adc41d8 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -3,9 +3,9 @@ import { BotCommand, colors, emojis, + formatList, GuildNoArraySetting, guildSettingsObj, - oxford, settingsArr, type ArgType, type CommandMessage, @@ -174,11 +174,11 @@ export default class ConfigCommand extends BotCommand { id: 'action', type: actionType, prompt: { - start: `Would you like to ${oxford( + start: `Would you like to ${formatList( actionType!.map((a) => `\`${a}\``), 'or' )} the \`${setting}\` setting?`, - retry: `{error} Choose one of the following actions to perform on the ${setting} setting: ${oxford( + retry: `{error} Choose one of the following actions to perform on the ${setting} setting: ${formatList( actionType!.map((a) => `\`${a}\``), 'or' )}`, diff --git a/src/commands/config/log.ts b/src/commands/config/log.ts index 493d486..0c74ce7 100644 --- a/src/commands/config/log.ts +++ b/src/commands/config/log.ts @@ -1,8 +1,8 @@ import { BotCommand, emojis, + formatList, guildLogsArr, - oxford, type ArgType, type CommandMessage, type GuildLogType, @@ -58,7 +58,7 @@ export default class LogCommand extends BotCommand { type: guildLogsArr, prompt: { start: 'What log type would you like to change?', - retry: `{error} Choose either ${oxford( + retry: `{error} Choose either ${formatList( guildLogsArr.map((l) => `\`${l}\``), 'or' )}`, diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts index 86132c7..25621fa 100644 --- a/src/commands/info/userInfo.ts +++ b/src/commands/info/userInfo.ts @@ -4,8 +4,8 @@ import { bots, colors, emojis, + formatList, mappings, - oxford, sleep, Time, timestampAndDelta, @@ -200,9 +200,9 @@ export default class UserInfoCommand extends BotCommand { const presenceInfo = []; if (member?.presence.status) presenceInfo.push(`**Status:** ${member.presence.status}`); if (devices && devices.length) - presenceInfo.push(`**${devices.length - 1 ? 'Devices' : 'Device'}:** ${oxford(devices, 'and', '')}`); + presenceInfo.push(`**${devices.length - 1 ? 'Devices' : 'Device'}:** ${formatList(devices, 'and')}`); if (activitiesNames.length) - presenceInfo.push(`**Activit${activitiesNames.length - 1 ? 'ies' : 'y'}:** ${oxford(activitiesNames, 'and', '')}`); + presenceInfo.push(`**Activit${activitiesNames.length - 1 ? 'ies' : 'y'}:** ${formatList(activitiesNames, 'and')}`); if (customStatus && customStatus.length) presenceInfo.push(`**Custom Status:** ${escapeMarkdown(customStatus)}`); embed.addFields({ name: title, value: presenceInfo.join('\n') }); @@ -245,7 +245,7 @@ export default class UserInfoCommand extends BotCommand { // Important Perms const perms = this.getImportantPermissions(member); - if (perms.length) embed.addFields({ name: title, value: this.getImportantPermissions(member).join(' ') }); + if (perms.length) embed.addFields({ name: title, value: perms.join(' ') }); } private static getImportantPermissions(member: GuildMember | undefined) { diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts index 06afe3b..6f08aaa 100644 --- a/src/commands/utilities/price.ts +++ b/src/commands/utilities/price.ts @@ -1,4 +1,4 @@ -import { ArgType, BotCommand, colors, emojis, format, oxford, type CommandMessage } from '#lib'; +import { ArgType, BotCommand, colors, emojis, format, formatList, type CommandMessage } from '#lib'; import assert from 'assert/strict'; import { ApplicationCommandOptionType, AutocompleteInteraction, EmbedBuilder } from 'discord.js'; import { default as Fuse } from 'fuse.js'; @@ -68,7 +68,7 @@ export default class PriceCommand extends BotCommand { if (bazaar?.success === false) errors.push('bazaar'); if (errors.length) { - priceEmbed.setFooter({ text: `Could not fetch data for ${oxford(errors, 'and')}` }); + priceEmbed.setFooter({ text: `Could not fetch data for ${formatList(errors, 'and')}` }); } // create a set from all the item names so that there are no duplicates for the fuzzy search diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts index 9b12a1c..c01a0c3 100644 --- a/src/commands/utilities/whoHasRole.ts +++ b/src/commands/utilities/whoHasRole.ts @@ -4,8 +4,8 @@ import { chunk, colors, emojis, + formatList, OptArgType, - oxford, type CommandMessage, type SlashMessage } from '#lib'; @@ -60,7 +60,7 @@ export default class WhoHasRoleCommand extends BotCommand { const title = `Members with ${ roles.length < 4 - ? oxford( + ? formatList( rawRoles.map((r) => r.name), 'and' ) diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts index 534e744..c81857c 100644 --- a/src/listeners/commands/commandBlocked.ts +++ b/src/listeners/commands/commandBlocked.ts @@ -4,8 +4,8 @@ import { Emitter, emojis, format, + formatList, InhibitorReason, - oxford, type BotCommandHandlerEvents, type CommandMessage } from '#lib'; @@ -95,7 +95,7 @@ export default class CommandBlockedListener extends BotListener { channels!.forEach((c) => { names.push(`<#${c}>`); }); - const pretty = oxford(names, 'and'); + const pretty = formatList(names, 'and'); return await respond({ content: `${emojis.error} ${format.input(command!.id)} can only be run in ${pretty}.`, ephemeral: true @@ -105,7 +105,7 @@ export default class CommandBlockedListener extends BotListener { if (!command) break; const guilds = command.restrictedGuilds; const names = guilds!.map((g) => format.input(client.guilds.cache.get(g)?.name ?? g)); - const pretty = oxford(names, 'and'); + const pretty = formatList(names, 'and'); return await respond({ content: `${emojis.error} ${format.input(command!.id)} can only be run in ${pretty}.`, ephemeral: true diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts index 9a59077..598b7d9 100644 --- a/src/listeners/commands/commandMissingPermissions.ts +++ b/src/listeners/commands/commandMissingPermissions.ts @@ -4,9 +4,9 @@ import { Emitter, emojis, format, + formatList, mappings, - oxford, - surroundArray, + surroundEach, type BotCommandHandlerEvents } from '#lib'; import { Client, type PermissionsString } from 'discord.js'; @@ -32,8 +32,8 @@ export default class CommandMissingPermissionsListener extends BotListener { (perm) => mappings.permissions[perm as PermissionsString]?.name ?? missing ); - const discordFormat = oxford(surroundArray(niceMissing, '**'), 'and', ''); - const consoleFormat = oxford(surroundArray(niceMissing, '<<', '>>'), 'and', ''); + const discordFormat = formatList(surroundEach(niceMissing, '**'), 'and'); + const consoleFormat = formatList(surroundEach(niceMissing, '<<', '>>'), 'and'); void client.console.info( 'commandMissingPermissions', `<<${message.author.tag}>> tried to run <<${ diff --git a/src/listeners/interaction/interactionCreate.ts b/src/listeners/interaction/interactionCreate.ts index c4c14c1..ced359c 100644 --- a/src/listeners/interaction/interactionCreate.ts +++ b/src/listeners/interaction/interactionCreate.ts @@ -3,9 +3,9 @@ import { Emitter, emojis, format, + formatList, handleAutomodInteraction, - oxford, - surroundArray, + surroundEach, type BotClientEvents } from '#lib'; import { Events, InteractionType } from 'discord.js'; @@ -68,7 +68,7 @@ export default class InteractionCreateListener extends BotListener { return await interaction.reply({ content: `You selected ${ Array.isArray(interaction.values) - ? oxford(surroundArray(interaction.values, '`'), 'and', '') + ? formatList(surroundEach(interaction.values, '`'), 'and') : format.input(interaction.values) }.`, ephemeral: true |