aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.cjs2
-rw-r--r--lib/extensions/discord-akairo/BotCommandHandler.ts6
-rw-r--r--lib/extensions/discord.js/ExtendedGuild.ts276
-rw-r--r--lib/extensions/discord.js/ExtendedGuildMember.ts262
-rw-r--r--lib/extensions/discord.js/ExtendedUser.ts34
-rw-r--r--lib/utils/Utils.ts30
-rw-r--r--src/commands/config/config.ts6
-rw-r--r--src/commands/config/log.ts4
-rw-r--r--src/commands/info/userInfo.ts8
-rw-r--r--src/commands/utilities/price.ts4
-rw-r--r--src/commands/utilities/whoHasRole.ts4
-rw-r--r--src/listeners/commands/commandBlocked.ts6
-rw-r--r--src/listeners/commands/commandMissingPermissions.ts8
-rw-r--r--src/listeners/interaction/interactionCreate.ts6
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