diff options
31 files changed, 158 insertions, 533 deletions
diff --git a/src/commands/admin/channelPermissions.ts b/src/commands/admin/channelPermissions.ts index 8c7c540..026f1a6 100644 --- a/src/commands/admin/channelPermissions.ts +++ b/src/commands/admin/channelPermissions.ts @@ -62,7 +62,7 @@ export default class ChannelPermissionsCommand extends BushCommand { state: 'true' | 'false' | 'neutral'; } ) { - if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); + if (!message.inGuild()) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); if (!message.member!.permissions.has('ADMINISTRATOR') && !message.member!.user.isOwner()) return await message.util.reply(`${util.emojis.error} You must have admin perms to use this command.`); if (message.util.isSlashMessage(message)) await message.interaction.deferReply(); diff --git a/src/commands/admin/roleAll.ts b/src/commands/admin/roleAll.ts index 5ef379a..6e82011 100644 --- a/src/commands/admin/roleAll.ts +++ b/src/commands/admin/roleAll.ts @@ -37,8 +37,8 @@ export default class RoleAllCommand extends BushCommand { }); } - public override async exec(message: BushMessage | BushSlashMessage, args: { role: ArgType<'role'>; bots: boolean }) { - if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); + public override async exec(message: BushMessage | BushSlashMessage, args: { role: ArgType<'role'>; bots: ArgType<'boolean'> }) { + if (!message.inGuild()) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`); if (!message.member!.permissions.has('ADMINISTRATOR') && !message.member!.user.isOwner()) return await message.util.reply(`${util.emojis.error} You must have admin perms to use this command.`); if (message.util.isSlashMessage(message)) await message.interaction.deferReply(); diff --git a/src/commands/config/customAutomodPhrases.ts b/src/commands/config/_customAutomodPhrases.ts index cf37595..cd59c9a 100644 --- a/src/commands/config/customAutomodPhrases.ts +++ b/src/commands/config/_customAutomodPhrases.ts @@ -12,37 +12,22 @@ // { // id: 'required_argument', // type: 'string', -// prompt: { -// start: 'What would you like to set your first argument to be?', -// retry: '{error} Pick a valid argument.', -// optional: false -// } +// description: 'This is the first argument.', +// prompt: 'What would you like to set your first argument to be?', +// retry: '{error} Pick a valid argument.', +// slashType: 'STRING' // }, // { // id: 'optional_argument', // type: 'string', -// prompt: { -// start: 'What would you like to set your second argument to be?', -// retry: '{error} Pick a valid argument.', -// optional: true -// } +// description: 'This is the second argument.', +// prompt: 'What would you like to set your second argument to be?', +// retry: '{error} Pick a valid argument.', +// optional: true, +// slashType: 'STRING' // } // ], // slash: true, -// slashOptions: [ -// { -// name: 'required_argument', -// description: 'What would you like to set your first argument to be?', -// type: 'STRING', -// required: true -// }, -// { -// name: 'optional_argument', -// description: 'What would you like to set your second argument to be?', -// type: 'STRING', -// required: false -// } -// ], // channel: 'guild', // clientPermissions: (m) => util.clientSendAndPermCheck(m), // userPermissions: ['MANAGE_GUILD'] @@ -52,8 +37,5 @@ // public override async exec( // message: BushMessage | BushSlashMessage, // args: { required_argument: string; optional_argument: string } -// ) { -// return await message.util.reply(`${util.emojis.error} Do not use the template command.`); -// args; -// } +// ) {} // } diff --git a/src/commands/config/levelRoles.ts b/src/commands/config/_levelRoles.ts index 7f99580..af7e637 100644 --- a/src/commands/config/levelRoles.ts +++ b/src/commands/config/_levelRoles.ts @@ -10,39 +10,24 @@ // examples: ['level-role 1 2'], // args: [ // { -// id: 'action', -// customType: ['add', 'remove'] -// }, -// { -// id: 'role', -// type: 'role', +// id: 'required_argument', +// type: 'string', +// description: 'This is the first argument.', // prompt: 'What would you like to set your first argument to be?', // retry: '{error} Pick a valid argument.', -// optional: false +// slashType: 'STRING' // }, // { -// id: 'level', -// type: 'integer', +// id: 'optional_argument', +// type: 'string', +// description: 'This is the second argument.', // prompt: 'What would you like to set your second argument to be?', // retry: '{error} Pick a valid argument.', -// optional: false +// optional: true, +// slashType: 'STRING' // } // ], // slash: true, -// slashOptions: [ -// { -// name: 'role', -// description: 'What would you like to set your first argument to be?', -// type: 'STRING', -// required: true -// }, -// { -// name: 'level', -// description: 'What would you like to set your second argument to be?', -// type: 'STRING', -// required: true -// } -// ], // channel: 'guild', // clientPermissions: (m) => util.clientSendAndPermCheck(m), // userPermissions: ['MANAGE_GUILD', 'MANAGE_ROLES'] @@ -52,8 +37,5 @@ // public override async exec( // message: BushMessage | BushSlashMessage, // args: { required_argument: string; optional_argument: string } -// ) { -// return await message.util.reply(`${util.emojis.error} Do not use the template command.`); -// args; -// } +// ) {} // } diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts index d119774..d15b8d7 100644 --- a/src/commands/config/blacklist.ts +++ b/src/commands/config/blacklist.ts @@ -1,4 +1,4 @@ -import { AllowedMentions, ArgType, BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; +import { AllowedMentions, BushCommand, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; import { User } from 'discord.js'; export default class BlacklistCommand extends BushCommand { @@ -16,10 +16,7 @@ export default class BlacklistCommand extends BushCommand { readableType: "'blacklist'|'unblacklist'", prompt: 'Would you like to add or remove someone or something from/to the blacklist?', slashType: 'STRING', - choices: [ - { name: 'blacklist', value: 'blacklist' }, - { name: 'unblacklist', value: 'unblacklist' } - ], + choices: ['blacklist', 'unblacklist'].map((c) => ({ name: c, value: c })), only: 'slash' }, { @@ -51,7 +48,11 @@ export default class BlacklistCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - args: { action?: 'blacklist' | 'unblacklist'; target: ArgType<'channel'> | ArgType<'user'> | string; global: boolean } + args: { + action?: 'blacklist' | 'unblacklist'; + target: ArgType<'channel'> | ArgType<'user'> | string; // there is no way to combine channel and user in slash commands without making subcommands + global: ArgType<'boolean'>; + } ) { let action: 'blacklist' | 'unblacklist' | 'toggle' = args.action ?? (message?.util?.parsed?.alias as 'blacklist' | 'unblacklist' | undefined) ?? 'toggle'; diff --git a/src/commands/dev/__template.ts b/src/commands/dev/__template.ts index 4341198..11d9649 100644 --- a/src/commands/dev/__template.ts +++ b/src/commands/dev/__template.ts @@ -1,4 +1,4 @@ -import { BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; +import { BushCommand, type ArgType, type BushMessage, type BushSlashMessage, type OptionalArgType } from '#lib'; export default class TemplateCommand extends BushCommand { public constructor() { @@ -39,7 +39,7 @@ export default class TemplateCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - args: { required_argument: string; optional_argument: string } + args: { required_argument: ArgType<'string'>; optional_argument: OptionalArgType<'string'> } ) { return await message.util.reply(`${util.emojis.error} Do not use the template command.`); args; diff --git a/src/commands/dev/dm.ts b/src/commands/dev/dm.ts index 4133b96..d702875 100644 --- a/src/commands/dev/dm.ts +++ b/src/commands/dev/dm.ts @@ -1,4 +1,4 @@ -import { ArgType, BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; +import { BushCommand, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; export default class DMCommand extends BushCommand { public constructor() { @@ -21,10 +21,9 @@ export default class DMCommand extends BushCommand { id: 'content', type: 'string', match: 'rest', - description: 'This is the second argument.', - prompt: 'What would you like to set your second argument to be?', - retry: '{error} Pick a valid argument.', - optional: true, + description: 'The content to send to the user.', + prompt: 'What would you like to send to the user?', + retry: '{error} Pick something to send the user.', slashType: 'STRING' } ], @@ -35,10 +34,12 @@ export default class DMCommand extends BushCommand { userPermissions: [] }); } - public override async exec(message: BushMessage | BushSlashMessage, args: { user: ArgType<'user'>; content: string }) { + public override async exec( + message: BushMessage | BushSlashMessage, + args: { user: ArgType<'user'>; content: ArgType<'string'> } + ) { try { - const u = await client.users.fetch(args.user.id); - await u.send(args.content); + await client.users.send(args.user.id, args.content); } catch (e) { return message.util.reply(`${util.emojis.error} There was an error sending ${util.format.input(args.user.tag)} a dm.`); } diff --git a/src/commands/dev/say.ts b/src/commands/dev/say.ts index 80cbbe2..2c8151f 100644 --- a/src/commands/dev/say.ts +++ b/src/commands/dev/say.ts @@ -26,7 +26,7 @@ export default class SayCommand extends BushCommand { }); } - public override async exec(message: BushMessage, args: { content: string }) { + public override async exec(message: BushMessage | BushSlashMessage, args: { content: string }) { if (!message.author.isOwner()) return await message.util.reply(`${util.emojis.error} Only my developers can run this command.`); diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts index 35e98aa..3a6406d 100644 --- a/src/commands/dev/superUser.ts +++ b/src/commands/dev/superUser.ts @@ -1,4 +1,4 @@ -import { ArgType, BushCommand, Global, type BushMessage, type BushSlashMessage } from '#lib'; +import { BushCommand, Global, type ArgType, type BushMessage } from '#lib'; import { type ArgumentOptions, type Flag } from 'discord-akairo'; export default class SuperUserCommand extends BushCommand { @@ -53,10 +53,7 @@ export default class SuperUserCommand extends BushCommand { return { action, user }; } - public override async exec( - message: BushMessage | BushSlashMessage, - { action, user }: { action: 'add' | 'remove'; user: ArgType<'user'> } - ) { + public override async exec(message: BushMessage, { action, user }: { action: 'add' | 'remove'; user: ArgType<'user'> }) { if (!message.author.isOwner()) return await message.util.reply(`${util.emojis.error} Only my developers can run this command.`); diff --git a/src/commands/moderation/activePunishments.ts b/src/commands/moderation/_activePunishments.ts index d40f2ba..d40f2ba 100644 --- a/src/commands/moderation/activePunishments.ts +++ b/src/commands/moderation/_activePunishments.ts diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts index bede2cf..919ee40 100644 --- a/src/commands/moderation/removeReactionEmoji.ts +++ b/src/commands/moderation/removeReactionEmoji.ts @@ -1,4 +1,5 @@ -import { ArgType, BushCommand, type BushMessage } from '#lib'; +import { BushCommand, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import assert from 'assert'; import { Message, type Emoji } from 'discord.js'; export default class RemoveReactionEmojiCommand extends BushCommand { @@ -37,9 +38,10 @@ export default class RemoveReactionEmojiCommand extends BushCommand { } public override async exec( - message: BushMessage, + message: BushMessage | BushSlashMessage, args: { message: ArgType<'guildMessage'> | string; emoji: ArgType<'emoji'> | ArgType<'snowflake'> } ) { + assert(message.channel); const resolvedMessage = args.message instanceof Message ? args.message : await message.channel.messages.fetch(args.message); const id = !(['string'] as const).includes(typeof args.emoji); diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index d36a5db..e7a19d6 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -1,12 +1,12 @@ import { AllowedMentions, - ArgType, BushCommand, Moderation, - OptionalArgType, + type ArgType, type BushGuildMember, type BushMessage, - type BushSlashMessage + type BushSlashMessage, + type OptionalArgType } from '#lib'; export default class UnmuteCommand extends BushCommand { @@ -56,7 +56,7 @@ export default class UnmuteCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - { user, reason, force }: { user: ArgType<'user'>; reason: OptionalArgType<'string'>; force: boolean } + { user, reason, force = false }: { user: ArgType<'user'>; reason: OptionalArgType<'string'>; force?: boolean } ) { const error = util.emojis.error; const member = message.guild!.members.cache.get(user.id) as BushGuildMember; diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index 95e409d..5093c9b 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -1,12 +1,12 @@ import { AllowedMentions, - ArgType, BushCommand, Moderation, - OptionalArgType, + type ArgType, type BushGuildMember, type BushMessage, - type BushSlashMessage + type BushSlashMessage, + type OptionalArgType } from '#lib'; export default class WarnCommand extends BushCommand { @@ -55,7 +55,7 @@ export default class WarnCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - { user, reason, force }: { user: ArgType<'user'>; reason: OptionalArgType<'string'>; force?: boolean } + { user, reason, force = false }: { user: ArgType<'user'>; reason: OptionalArgType<'string'>; force?: boolean } ) { const member = message.guild!.members.cache.get(user.id) as BushGuildMember; if (!member) return message.util.reply(`${util.emojis.error} I cannot warn users that are not in the server.`); diff --git a/src/commands/utilities/remind.ts b/src/commands/utilities/remind.ts index 8df24c1..6339343 100644 --- a/src/commands/utilities/remind.ts +++ b/src/commands/utilities/remind.ts @@ -1,4 +1,4 @@ -import { ArgType, BushCommand, Reminder, type BushMessage, type BushSlashMessage } from '#lib'; +import { BushCommand, Reminder, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; export default class RemindCommand extends BushCommand { public constructor() { diff --git a/src/commands/utilities/reminders.ts b/src/commands/utilities/reminders.ts index 7180aa9..34034d8 100644 --- a/src/commands/utilities/reminders.ts +++ b/src/commands/utilities/reminders.ts @@ -1,5 +1,5 @@ import { BushCommand, ButtonPaginator, Reminder, type BushMessage, type BushSlashMessage } from '#lib'; -import { MessageEmbedOptions } from 'discord.js'; +import { type MessageEmbedOptions } from 'discord.js'; import { Op } from 'sequelize'; export default class RemindersCommand extends BushCommand { diff --git a/src/commands/utilities/steal.ts b/src/commands/utilities/steal.ts index 480201a..67b8382 100644 --- a/src/commands/utilities/steal.ts +++ b/src/commands/utilities/steal.ts @@ -1,5 +1,5 @@ -import { ArgType, BushCommand, BushSlashMessage, type BushMessage } from '#lib'; -import { ArgumentOptions, ArgumentType, ArgumentTypeCaster, Flag } from 'discord-akairo'; +import { BushCommand, type ArgType, type BushMessage, type BushSlashMessage } from '#lib'; +import { type ArgumentOptions, type ArgumentType, type ArgumentTypeCaster, type Flag } from 'discord-akairo'; import _ from 'lodash'; import { URL } from 'url'; diff --git a/src/commands/utilities/suicide.ts b/src/commands/utilities/suicide.ts index 641f7ec..f0d75a2 100644 --- a/src/commands/utilities/suicide.ts +++ b/src/commands/utilities/suicide.ts @@ -1,5 +1,5 @@ import { AllowedMentions, BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; -import { Message, MessageEmbed } from 'discord.js'; +import { MessageEmbed, type Message } from 'discord.js'; export default class TemplateCommand extends BushCommand { public constructor() { diff --git a/src/commands/utilities/uuid.ts b/src/commands/utilities/uuid.ts index 6195edd..d842b58 100644 --- a/src/commands/utilities/uuid.ts +++ b/src/commands/utilities/uuid.ts @@ -1,4 +1,4 @@ -import { BushCommand, type BushMessage } from '#lib'; +import { BushCommand, type BushMessage, type BushSlashMessage } from '#lib'; export default class UuidCommand extends BushCommand { public constructor() { @@ -35,7 +35,7 @@ export default class UuidCommand extends BushCommand { } public override async exec( - message: BushMessage, + message: BushMessage | BushSlashMessage, { ign, dashed }: { ign: { match: RegExpMatchArray; matches: any[] }; dashed: boolean } ) { if (!ign) return await message.util.reply(`${util.emojis.error} Please enter a valid ign.`); diff --git a/src/lib/common/Format.ts b/src/lib/common/Format.ts deleted file mode 100644 index 6cb6edc..0000000 --- a/src/lib/common/Format.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { type CodeBlockLang } from '#lib'; -import { EscapeMarkdownOptions, Formatters, Util } from 'discord.js'; - -/** - * Formats and escapes content for formatting - */ -export class Format { - /** - * Wraps the content inside a codeblock with no language. - * @param content The content to wrap. - */ - public static codeBlock(content: string): string; - - /** - * Wraps the content inside a codeblock with the specified language. - * @param language The language for the codeblock. - * @param content The content to wrap. - */ - public static codeBlock(language: CodeBlockLang, content: string): string; - public static codeBlock(languageOrContent: string, content?: string): string { - return typeof content === 'undefined' - ? Formatters.codeBlock(Util.escapeCodeBlock(`${languageOrContent}`)) - : Formatters.codeBlock(`${languageOrContent}`, Util.escapeCodeBlock(`${content}`)); - } - - /** - * Wraps the content inside \`backticks\`, which formats it as inline code. - * @param content The content to wrap. - */ - public static inlineCode(content: string): string { - return Formatters.inlineCode(Util.escapeInlineCode(`${content}`)); - } - - /** - * Formats the content into italic text. - * @param content The content to wrap. - */ - public static italic(content: string): string { - return Formatters.italic(Util.escapeItalic(`${content}`)); - } - - /** - * Formats the content into bold text. - * @param content The content to wrap. - */ - public static bold(content: string): string { - return Formatters.bold(Util.escapeBold(`${content}`)); - } - - /** - * Formats the content into underscored text. - * @param content The content to wrap. - */ - public static underscore(content: string): string { - return Formatters.underscore(Util.escapeUnderline(`${content}`)); - } - - /** - * Formats the content into strike-through text. - * @param content The content to wrap. - */ - public static strikethrough(content: string): string { - return Formatters.strikethrough(Util.escapeStrikethrough(`${content}`)); - } - - /** - * Wraps the content inside spoiler (hidden text). - * @param content The content to wrap. - */ - public static spoiler(content: string): string { - return Formatters.spoiler(Util.escapeSpoiler(`${content}`)); - } - - /** - * Escapes any Discord-flavour markdown in a string. - * @param text Content to escape - * @param options Options for escaping the markdown - */ - public static escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string { - return Util.escapeMarkdown(`${text}`, options); - } - - /** - * Formats input: makes it bold and escapes any other markdown - * @param text The input - */ - public static input(text: string): string { - return this.bold(this.escapeMarkdown(this.sanitizeWtlAndControl(`${text}`))); - } - - /** - * Formats input for logs: makes it highlighted - * @param text The input - */ - public static inputLog(text: string): string { - return `<<${this.sanitizeWtlAndControl(`${text}`)}>>`; - } - - /** - * Removes all characters in a string that are either control characters or change the direction of text etc. - * @param str The string you would like sanitized - */ - public static sanitizeWtlAndControl(str: string) { - // eslint-disable-next-line no-control-regex - return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, ''); - } -} diff --git a/src/lib/common/Moderation.ts b/src/lib/common/Moderation.ts deleted file mode 100644 index ab2943b..0000000 --- a/src/lib/common/Moderation.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { - ActivePunishment, - ActivePunishmentType, - Guild, - ModLog, - ModLogType, - type BushGuildMember, - type BushGuildMemberResolvable, - type BushGuildResolvable -} from '#lib'; -import { type Snowflake } from 'discord.js'; - -/** - * A utility class with moderation-related methods. - */ -export class Moderation { - /** - * Checks if a moderator can perform a moderation action on another user. - * @param moderator The person trying to perform the action. - * @param victim The person getting punished. - * @param type The type of punishment - used to format the response. - * @param checkModerator Whether or not to check if the victim is a moderator. - * @param force Override permissions checks. - * @returns `true` if the moderator can perform the action otherwise a reason why they can't. - */ - public static async permissionCheck( - moderator: BushGuildMember, - victim: BushGuildMember, - type: 'mute' | 'unmute' | 'warn' | 'kick' | 'ban' | 'unban' | 'add a punishment role to' | 'remove a punishment role from', - checkModerator = true, - force = false - ): Promise<true | string> { - if (force) return true; - - // If the victim is not in the guild anymore it will be undefined - if ((!victim || !victim.guild) && !['ban', 'unban'].includes(type)) return true; - - if (moderator.guild.id !== victim.guild.id) { - throw new Error('moderator and victim not in same guild'); - } - - const isOwner = moderator.guild.ownerId === moderator.id; - if (moderator.id === victim.id && !type.startsWith('un')) { - return `${util.emojis.error} You cannot ${type} yourself.`; - } - if ( - moderator.roles.highest.position <= victim.roles.highest.position && - !isOwner && - !(type.startsWith('un') && moderator.id === victim.id) - ) { - return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`; - } - if ( - victim.roles.highest.position >= victim.guild.me!.roles.highest.position && - !(type.startsWith('un') && moderator.id === victim.id) - ) { - return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`; - } - if (checkModerator && victim.permissions.has('MANAGE_MESSAGES') && !(type.startsWith('un') && moderator.id === victim.id)) { - if (await moderator.guild.hasFeature('modsCanPunishMods')) { - return true; - } else { - return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`; - } - } - return true; - } - - /** - * Creates a modlog entry for a punishment. - * @param options Options for creating a modlog entry. - * @param getCaseNumber Whether or not to get the case number of the entry. - * @returns An object with the modlog and the case number. - */ - public static async createModLogEntry( - options: CreateModLogEntryOptions, - getCaseNumber = false - ): Promise<{ log: ModLog | null; caseNum: number | null }> { - const user = (await util.resolveNonCachedUser(options.user))!.id; - const moderator = (await util.resolveNonCachedUser(options.moderator))!.id; - const guild = client.guilds.resolveId(options.guild)!; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const duration = options.duration || undefined; - - // If guild does not exist create it so the modlog can reference a guild. - await Guild.findOrCreate({ - where: { - id: guild - }, - defaults: { - id: guild - } - }); - - const modLogEntry = ModLog.build({ - type: options.type, - user, - moderator, - reason: options.reason, - duration: duration, - guild, - pseudo: options.pseudo ?? false, - evidence: options.evidence - }); - const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => { - await util.handleError('createModLogEntry', e); - return null; - }); - - if (!getCaseNumber) return { log: saveResult, caseNum: null }; - - const caseNum = (await ModLog.findAll({ where: { type: options.type, user: user, guild: guild, hidden: 'false' } }))?.length; - return { log: saveResult, caseNum }; - } - - /** - * Creates a punishment entry. - * @param options Options for creating the punishment entry. - * @returns The database entry, or null if no entry is created. - */ - public static async createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> { - const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined; - const user = (await util.resolveNonCachedUser(options.user))!.id; - const guild = client.guilds.resolveId(options.guild)!; - const type = this.findTypeEnum(options.type)!; - - const entry = ActivePunishment.build( - options.extraInfo - ? { user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo } - : { user, type, guild, expires, modlog: options.modlog } - ); - return await entry.save().catch(async (e) => { - await util.handleError('createPunishmentEntry', e); - return null; - }); - } - - /** - * Destroys a punishment entry. - * @param options Options for destroying the punishment entry. - * @returns Whether or not the entry was destroyed. - */ - public static async removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> { - const user = await util.resolveNonCachedUser(options.user); - const guild = client.guilds.resolveId(options.guild); - const type = this.findTypeEnum(options.type); - - if (!user || !guild) return false; - - let success = true; - - const entries = await ActivePunishment.findAll({ - // finding all cases of a certain type incase there were duplicates or something - where: options.extraInfo - ? { user: user.id, guild: guild, type, extraInfo: options.extraInfo } - : { user: user.id, guild: guild, type } - }).catch(async (e) => { - await util.handleError('removePunishmentEntry', e); - success = false; - }); - if (entries) { - const promises = entries.map(async (entry) => - entry.destroy().catch(async (e) => { - await util.handleError('removePunishmentEntry', e); - success = false; - }) - ); - - await Promise.all(promises); - } - return success; - } - - /** - * Returns the punishment type enum for the given type. - * @param type The type of the punishment. - * @returns The punishment type enum. - */ - private static findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') { - const typeMap = { - ['mute']: ActivePunishmentType.MUTE, - ['ban']: ActivePunishmentType.BAN, - ['role']: ActivePunishmentType.ROLE, - ['block']: ActivePunishmentType.BLOCK - }; - return typeMap[type]; - } -} - -/** - * Options for creating a modlog entry. - */ -export interface CreateModLogEntryOptions { - /** - * The type of modlog entry. - */ - type: ModLogType; - - /** - * The user that a modlog entry is created for. - */ - user: BushGuildMemberResolvable; - - /** - * The moderator that created the modlog entry. - */ - moderator: BushGuildMemberResolvable; - - /** - * The reason for the punishment. - */ - reason: string | undefined | null; - - /** - * The duration of the punishment. - */ - duration?: number; - - /** - * The guild that the punishment is created for. - */ - guild: BushGuildResolvable; - - /** - * Whether the punishment is a pseudo punishment. - */ - pseudo?: boolean; - - /** - * The evidence for the punishment. - */ - evidence?: string; -} - -/** - * Options for creating a punishment entry. - */ -export interface CreatePunishmentEntryOptions { - /** - * The type of punishment. - */ - type: 'mute' | 'ban' | 'role' | 'block'; - - /** - * The user that the punishment is created for. - */ - user: BushGuildMemberResolvable; - - /** - * The length of time the punishment lasts for. - */ - duration: number | undefined; - - /** - * The guild that the punishment is created for. - */ - guild: BushGuildResolvable; - - /** - * The id of the modlog that is linked to the punishment entry. - */ - modlog: string; - - /** - * The role id if the punishment is a role punishment. - */ - extraInfo?: Snowflake; -} - -/** - * Options for removing a punishment entry. - */ -export interface RemovePunishmentEntryOptions { - /** - * The type of punishment. - */ - type: 'mute' | 'ban' | 'role' | 'block'; - - /** - * The user that the punishment is destroyed for. - */ - user: BushGuildMemberResolvable; - - /** - * The guild that the punishment was in. - */ - guild: BushGuildResolvable; - - /** - * The role id if the punishment is a role punishment. - */ - extraInfo?: Snowflake; -} diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index f44c80d..f20a53e 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -6,13 +6,10 @@ import { type BushClient, type BushInspectOptions, type BushMessage, - type BushNewsChannel, type BushSlashEditMessageType, type BushSlashMessage, type BushSlashSendMessageType, - type BushTextChannel, type BushUser, - type BushUserResolvable, type CodeBlockLang, type Pronoun, type PronounCode @@ -42,7 +39,7 @@ import got from 'got'; import _ from 'lodash'; import { inspect, promisify } from 'util'; import CommandErrorListener from '../../../listeners/commands/commandError.js'; -import { Format } from '../../common/Format.js'; +import { Format } from '../../common/util/Format.js'; export class BushClientUtil extends ClientUtil { /** @@ -599,12 +596,6 @@ export class BushClientUtil extends ClientUtil { return `${arrByte[1]}, ${arrByte[2]}, ${arrByte[3]}`; } - /* eslint-disable @typescript-eslint/no-unused-vars */ - public async lockdownChannel(options: { channel: BushTextChannel | BushNewsChannel; moderator: BushUserResolvable }) { - // todo: implement lockdowns - } - /* eslint-enable @typescript-eslint/no-unused-vars */ - /** * Capitalize the first letter of a string. * @param string The string to capitalize the first letter of. diff --git a/src/lib/extensions/discord-akairo/BushSlashMessage.ts b/src/lib/extensions/discord-akairo/BushSlashMessage.ts index 3cd167d..cefd360 100644 --- a/src/lib/extensions/discord-akairo/BushSlashMessage.ts +++ b/src/lib/extensions/discord-akairo/BushSlashMessage.ts @@ -20,12 +20,7 @@ export class BushSlashMessage extends AkairoMessage { } export interface BushSlashMessage extends AkairoMessage { - /** - * The channel that the interaction was sent in. - */ get channel(): BushTextBasedChannel | null; -} - -export interface BushSlashMessage extends AkairoMessage { get guild(): BushGuild | null; + inGuild(): this is this & { guild: BushGuild; member: BushGuildMember }; } diff --git a/src/lib/models/ActivePunishment.ts b/src/lib/models/ActivePunishment.ts index 4512101..5fae2ac 100644 --- a/src/lib/models/ActivePunishment.ts +++ b/src/lib/models/ActivePunishment.ts @@ -70,6 +70,10 @@ export class ActivePunishment */ public declare modlog: string; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { ActivePunishment.init( { diff --git a/src/lib/models/Global.ts b/src/lib/models/Global.ts index e0cd7f9..30a9d38 100644 --- a/src/lib/models/Global.ts +++ b/src/lib/models/Global.ts @@ -54,6 +54,10 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes */ public declare blacklistedChannels: Snowflake[]; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { Global.init( { diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index 50113bf..dfee37b 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -1,4 +1,5 @@ -import { type Snowflake } from 'discord.js'; +import { ExcludeEnum, type Snowflake } from 'discord.js'; +import { ChannelTypes } from 'discord.js/typings/enums'; import { type Sequelize } from 'sequelize'; import { type BadWords } from '../common/AutoMod.js'; import { type BushClient } from '../extensions/discord-akairo/BushClient.js'; @@ -140,6 +141,10 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i */ public declare levelUpChannel: Snowflake; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize, client: BushClient): void { Guild.init( { @@ -154,7 +159,10 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i disabledCommands: jsonArray('disabledCommands'), lockdownChannels: jsonArray('lockdownChannels'), autoModPhases: jsonObject('autoModPhases'), - enabledFeatures: jsonArray('enabledFeatures'), + enabledFeatures: jsonArray( + 'enabledFeatures', + Object.keys(guildFeaturesObj).filter((key) => guildFeaturesObj[key as keyof typeof guildFeaturesObj].default) + ), joinRoles: jsonArray('joinRoles'), logChannels: jsonObject('logChannels'), bypassChannelBlacklist: jsonArray('bypassChannelBlacklist'), @@ -167,10 +175,14 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i } } +export type BaseGuildSetting = 'channel' | 'role' | 'user'; +export type GuildSettingType = 'string' | 'custom' | BaseGuildSetting | `${BaseGuildSetting}-array`; + export interface GuildSetting { name: string; description: string; - type: 'string' | 'custom' | 'channel' | 'role' | 'user' | 'channel-array' | 'role-array' | 'user-array'; + type: GuildSettingType; + subType: ExcludeEnum<typeof ChannelTypes, 'UNKNOWN'>[] | undefined; configurable: boolean; } const asGuildSetting = <T>(et: { [K in keyof T]: GuildSetting }) => et; @@ -180,78 +192,91 @@ export const guildSettingsObj = asGuildSetting({ name: 'Prefix', description: 'The phrase required to trigger text commands in this server.', type: 'string', + subType: undefined, configurable: true }, autoPublishChannels: { name: 'Auto Publish Channels', description: 'Channels were every message is automatically published.', type: 'channel-array', + subType: ['GUILD_NEWS'], configurable: true }, welcomeChannel: { name: 'Welcome Channel', description: 'The channel where the bot will send join and leave message.', type: 'channel', + subType: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'], configurable: true }, muteRole: { name: 'Mute Role', description: 'The role assigned when muting someone.', type: 'role', + subType: undefined, configurable: true }, punishmentEnding: { name: 'Punishment Ending', description: 'The message after punishment information to a user in a dm.', type: 'string', + subType: undefined, configurable: true }, lockdownChannels: { name: 'Lockdown Channels', description: 'Channels that are locked down when a mass lockdown is specified.', type: 'channel-array', - configurable: false // not implemented yet + subType: ['GUILD_TEXT'], + configurable: true }, joinRoles: { name: 'Join Roles', description: 'Roles assigned to users on join who do not have sticky role information.', type: 'role-array', + subType: undefined, configurable: true }, bypassChannelBlacklist: { name: 'Bypass Channel Blacklist', description: 'These users will be able to use commands in channels blacklisted.', type: 'user-array', + subType: undefined, configurable: true }, logChannels: { name: 'Log Channels', description: 'The channel were logs are sent.', type: 'custom', + subType: ['GUILD_TEXT'], configurable: false }, autoModPhases: { name: 'Automod Phases', description: 'Custom phrases to be detected by automod.', type: 'custom', + subType: undefined, configurable: false }, noXpChannels: { name: 'No Xp Channels', description: 'Channels where users will not earn xp for leveling.', type: 'channel-array', + subType: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'], configurable: true }, levelRoles: { name: 'Level Roles', description: 'What roles get given to users when they reach certain levels.', type: 'custom', + subType: undefined, configurable: false }, levelUpChannel: { name: 'Level Up Channel', description: 'The channel to send level up messages in instead of last channel.', type: 'channel', + subType: ['GUILD_TEXT', 'GUILD_NEWS', 'GUILD_NEWS_THREAD', 'GUILD_PUBLIC_THREAD', 'GUILD_PRIVATE_THREAD'], configurable: true } }); @@ -264,61 +289,81 @@ export const settingsArr = Object.keys(guildSettingsObj).filter( interface GuildFeature { name: string; description: string; + default: boolean; } const asGuildFeature = <T>(gf: { [K in keyof T]: GuildFeature }) => gf; export const guildFeaturesObj = asGuildFeature({ automod: { name: 'Automod', - description: 'Deletes offensive content as well as phishing links.' + description: 'Deletes offensive content as well as phishing links.', + default: false }, excludeDefaultAutomod: { name: 'Exclude Default Automod', - description: 'Opt out of using the default automod options.' + description: 'Opt out of using the default automod options.', + default: false }, excludeAutomodScamLinks: { name: 'Exclude Automod Scam Links', - description: 'Opt out of having automod delete scam links.' + description: 'Opt out of having automod delete scam links.', + default: false }, delScamMentions: { name: 'Delete Scam Mentions', - description: 'Deletes messages with @everyone and @here mentions that have common scam phrases.' + description: 'Deletes messages with @everyone and @here mentions that have common scam phrases.', + default: false }, autoPublish: { name: 'Auto Publish', - description: 'Publishes messages in configured announcement channels.' + description: 'Publishes messages in configured announcement channels.', + default: false }, + // todo implement a better auto thread system // autoThread: { // name: 'Auto Thread', - // description: 'Creates a new thread for messages in configured channels.' + // description: 'Creates a new thread for messages in configured channels.', + // default: false // }, blacklistedFile: { name: 'Blacklisted File', - description: 'Automatically deletes malicious files.' + description: 'Automatically deletes malicious files.', + default: false }, boosterMessageReact: { name: 'Booster Message React', - description: 'Reacts to booster messages with the boost emoji.' + description: 'Reacts to booster messages with the boost emoji.', + default: false }, leveling: { name: 'Leveling', - description: "Tracks users' messages and assigns them xp." + description: "Tracks users' messages and assigns them xp.", + default: false }, stickyRoles: { name: 'Sticky Roles', - description: 'Restores past roles to a user when they rejoin.' + description: 'Restores past roles to a user when they rejoin.', + default: false }, reporting: { name: 'Reporting', - description: 'Allow users to make reports.' + description: 'Allow users to make reports.', + default: false }, modsCanPunishMods: { name: 'Mods Can Punish Mods', - description: 'Allow moderators to punish other moderators.' + description: 'Allow moderators to punish other moderators.', + default: false }, sendLevelUpMessages: { name: 'Send Level Up Messages', - description: 'Send a message when a user levels up.' + description: 'Send a message when a user levels up.', + default: true + }, + logManualPunishments: { + name: 'Log Manual Punishments', + description: "Adds manual punishment to the user's modlogs and the logging channels.", + default: true } }); @@ -340,6 +385,7 @@ export const guildLogsObj = { configurable: true } }; + export type GuildLogType = keyof typeof guildLogsObj; export const guildLogsArr = Object.keys(guildLogsObj).filter( (s) => guildLogsObj[s as GuildLogType].configurable diff --git a/src/lib/models/Level.ts b/src/lib/models/Level.ts index baf359b..2ed787d 100644 --- a/src/lib/models/Level.ts +++ b/src/lib/models/Level.ts @@ -39,6 +39,10 @@ export class Level extends BaseModel<LevelModel, LevelModelCreationAttributes> i return Level.convertXpToLevel(this.xp); } + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { Level.init( { diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts index 7d656a4..e19ddd4 100644 --- a/src/lib/models/ModLog.ts +++ b/src/lib/models/ModLog.ts @@ -99,6 +99,10 @@ export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes */ public declare hidden: boolean; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { ModLog.init( { diff --git a/src/lib/models/Reminder.ts b/src/lib/models/Reminder.ts index b8cd669..d1460bf 100644 --- a/src/lib/models/Reminder.ts +++ b/src/lib/models/Reminder.ts @@ -60,6 +60,10 @@ export class Reminder extends BaseModel<ReminderModel, ReminderModelCreationAttr */ public declare notified: boolean; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { Reminder.init( { diff --git a/src/lib/models/Stat.ts b/src/lib/models/Stat.ts index 4ecabb8..06ee21f 100644 --- a/src/lib/models/Stat.ts +++ b/src/lib/models/Stat.ts @@ -26,6 +26,10 @@ export class Stat extends BaseModel<StatModel, StatModelCreationAttributes> impl */ public declare commandsUsed: bigint; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { Stat.init( { diff --git a/src/lib/models/StickyRole.ts b/src/lib/models/StickyRole.ts index 64f0b4d..bbe81ae 100644 --- a/src/lib/models/StickyRole.ts +++ b/src/lib/models/StickyRole.ts @@ -38,6 +38,10 @@ export class StickyRole extends BaseModel<StickyRoleModel, StickyRoleModelCreati */ public declare nickname: string; + /** + * Initializes the model. + * @param sequelize The sequelize instance. + */ public static initModel(sequelize: Sequelize): void { StickyRole.init( { diff --git a/src/lib/models/__helpers.ts b/src/lib/models/__helpers.ts index 75737c5..049dc00 100644 --- a/src/lib/models/__helpers.ts +++ b/src/lib/models/__helpers.ts @@ -23,7 +23,7 @@ export function jsonObject(key: string): any { }; } -export function jsonArray(key: string): any { +export function jsonArray(key: string, defaultValue: string[] = []): any { return { type: DataTypes.TEXT, get: function (): string[] { @@ -33,7 +33,7 @@ export function jsonArray(key: string): any { return jsonParseSet.call(this, key, val); }, allowNull: false, - defaultValue: '[]' + defaultValue: JSON.stringify(defaultValue) }; } |