diff options
Diffstat (limited to 'lib/extensions/discord-akairo')
-rw-r--r-- | lib/extensions/discord-akairo/BotArgumentTypeCaster.ts | 2 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/BotCommand.ts | 92 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/BotCommandHandler.ts | 107 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/BotInhibitor.ts | 33 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/BotListener.ts | 50 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/BotListenerHandler.ts | 16 | ||||
-rw-r--r-- | lib/extensions/discord-akairo/TanzaniteClient.ts | 21 |
7 files changed, 270 insertions, 51 deletions
diff --git a/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts b/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts index 5f4f32f..d7943fc 100644 --- a/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts +++ b/lib/extensions/discord-akairo/BotArgumentTypeCaster.ts @@ -1,3 +1,3 @@ -import { type CommandMessage } from '#lib'; +import type { CommandMessage } from '#lib'; export type BotArgumentTypeCaster<R = unknown> = (message: CommandMessage, phrase: string) => R; diff --git a/lib/extensions/discord-akairo/BotCommand.ts b/lib/extensions/discord-akairo/BotCommand.ts index abd945e..11a8bad 100644 --- a/lib/extensions/discord-akairo/BotCommand.ts +++ b/lib/extensions/discord-akairo/BotCommand.ts @@ -1,17 +1,16 @@ import { type DiscordEmojiInfo, type RoleWithDuration } from '#args'; -import { - type BotArgumentTypeCaster, - type BotCommandHandler, - type BotInhibitor, - type BotListener, - type BotTask, - type ParsedDuration, - type TanzaniteClient +import type { + BotArgumentTypeCaster, + BotCommandHandler, + BotInhibitor, + BotListener, + BotTask, + ParsedDuration, + TanzaniteClient } from '#lib'; import { - ArgumentMatch, Command, - CommandUtil, + CommandArguments, type AkairoApplicationCommandAutocompleteOption, type AkairoApplicationCommandChannelOptionData, type AkairoApplicationCommandChoicesData, @@ -20,26 +19,25 @@ import { type AkairoApplicationCommandOptionData, type AkairoApplicationCommandSubCommandData, type AkairoApplicationCommandSubGroupData, + type ArgumentMatch, type ArgumentOptions, type ArgumentType, type ArgumentTypeCaster, type BaseArgumentType, type CommandOptions, + type CommandUtil, type ContextMenuCommand, - type MissingPermissionSupplier, type SlashOption, type SlashResolveType } from 'discord-akairo'; import { - Message, PermissionsBitField, - User, type ApplicationCommandOptionChoiceData, - // eslint-disable-next-line @typescript-eslint/no-unused-vars type ApplicationCommandOptionType, - type PermissionResolvable, + type Message, type PermissionsString, - type Snowflake + type Snowflake, + type User } from 'discord.js'; import _ from 'lodash'; import { SlashMessage } from './SlashMessage.js'; @@ -219,6 +217,7 @@ export type CustomMissingPermissionSupplier = (message: CommandMessage | SlashMe interface ExtendedCommandOptions { /** * Whether the command is hidden from the help command. + * @default false */ hidden?: boolean; @@ -244,11 +243,13 @@ interface ExtendedCommandOptions { /** * A fake command, completely hidden from the help command. + * @default false */ pseudo?: boolean; /** * Allow this command to be run in channels that are blacklisted. + * @default false */ bypassChannelBlacklist?: boolean; @@ -261,6 +262,24 @@ interface ExtendedCommandOptions { * Extra information about the command, displayed in the help command. */ note?: string; + + /** + * Whether to check for channel overrides when considering client permissions. + * @default false + */ + clientCheckChannel?: boolean; + + /** + * Whether to check for channel overrides when considering user permissions. + * @default false + */ + userCheckChannel?: boolean; + + /** + * **Text Command Only**: Don't check if the user has send permissions in the channel. + * @default false + */ + skipSendCheck?: boolean; } export interface BaseBotCommandOptions @@ -281,12 +300,12 @@ export interface BaseBotCommandOptions /** * Permissions required by the client to run this command. */ - clientPermissions: bigint | bigint[] | CustomMissingPermissionSupplier; + clientPermissions: PermissionsString[]; /** * Permissions required by the user to run this command. */ - userPermissions: bigint | bigint[] | CustomMissingPermissionSupplier; + userPermissions: PermissionsString[]; /** * Whether the argument is only accessible to the owners. @@ -364,6 +383,8 @@ export abstract class BotCommand extends Command { public declare client: TanzaniteClient; public declare handler: BotCommandHandler; public declare description: string; + public declare userPermissions: PermissionsString[]; + public declare clientPermissions: PermissionsString[]; /** * Show how to use the command. @@ -411,7 +432,7 @@ export abstract class BotCommand extends Command { public bypassChannelBlacklist: boolean; /** - * Info about the arguments for the help command. + * Information about the arguments for the help command. */ public argsInfo?: ArgsInfo[]; @@ -420,6 +441,24 @@ export abstract class BotCommand extends Command { */ public note?: string; + /** + * Whether to check for channel overrides when considering client permissions. + * @default true + */ + public clientCheckChannel: boolean; + + /** + * Whether to check for channel overrides when considering user permissions. + * @default true + */ + public userCheckChannel: boolean; + + /** + * **Text Command Only**: Don't check if the user has send permissions in the channel. + * @default false + */ + public skipSendCheck: boolean; + public constructor(id: string, options: CustomCommandOptions) { const options_ = options as BaseBotCommandOptions; @@ -490,7 +529,7 @@ export abstract class BotCommand extends Command { if (newTextArgs.length > 0) newOptions.args = newTextArgs; if (newSlashArgs.length > 0) newOptions.slashOptions = options_.slashOptions ?? newSlashArgs; } else if (key === 'clientPermissions' || key === 'userPermissions') { - newOptions[key] = options_[key] as PermissionResolvable | PermissionResolvable[] | MissingPermissionSupplier; + newOptions[key] = options_[key]; } else { newOptions[key] = options_[key]; } @@ -543,12 +582,15 @@ export abstract class BotCommand extends Command { this.examples = options_.examples; this.options = options_; this.parsedOptions = newOptions; - this.hidden = !!options_.hidden; + this.hidden = options_.hidden ?? false; this.restrictedChannels = options_.restrictedChannels; this.restrictedGuilds = options_.restrictedGuilds; - this.pseudo = !!options_.pseudo; - this.bypassChannelBlacklist = !!options_.bypassChannelBlacklist; + this.pseudo = options_.pseudo ?? false; + this.bypassChannelBlacklist = options_.bypassChannelBlacklist ?? false; this.note = options_.note; + this.clientCheckChannel = options_.clientCheckChannel ?? false; + this.userCheckChannel = options_.userCheckChannel ?? false; + this.skipSendCheck = options_.skipSendCheck ?? false; } /** @@ -556,13 +598,13 @@ export abstract class BotCommand extends Command { * @param message - Message that triggered the command. * @param args - Evaluated arguments. */ - public abstract override exec(message: CommandMessage, args: any): any; + public abstract override exec(message: CommandMessage, args: CommandArguments): any; /** * Executes the command. * @param message - Message that triggered the command. * @param args - Evaluated arguments. */ - public abstract override exec(message: CommandMessage | SlashMessage, args: any): any; + public abstract override exec(message: CommandMessage | SlashMessage, args: CommandArguments): any; } type SlashOptionKeys = diff --git a/lib/extensions/discord-akairo/BotCommandHandler.ts b/lib/extensions/discord-akairo/BotCommandHandler.ts index 8a4fe60..71d9ad4 100644 --- a/lib/extensions/discord-akairo/BotCommandHandler.ts +++ b/lib/extensions/discord-akairo/BotCommandHandler.ts @@ -1,6 +1,7 @@ -import { type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -import { CommandHandler, type Category, type CommandHandlerEvents, type CommandHandlerOptions } from 'discord-akairo'; -import { type Collection, type Message, type PermissionsString } from 'discord.js'; +import type { BotCommand, CommandMessage, SlashMessage } from '#lib'; +import { CommandHandler, CommandHandlerEvents, type Category, type CommandHandlerOptions } from 'discord-akairo'; +import { GuildMember, PermissionResolvable, type Collection, type Message, type PermissionsString } from 'discord.js'; +import { CommandHandlerEvent } from '../../utils/Constants.js'; export type CustomCommandHandlerOptions = CommandHandlerOptions; @@ -18,20 +19,116 @@ export interface BotCommandHandlerEvents extends CommandHandlerEvents { load: [command: BotCommand, isReload: boolean]; messageBlocked: [message: /* no util */ Message | CommandMessage | SlashMessage, reason: string]; messageInvalid: [message: CommandMessage]; - missingPermissions: [message: CommandMessage, command: BotCommand, type: 'client' | 'user', missing: PermissionsString[]]; + missingPermissions: [ + message: CommandMessage, + command: BotCommand, + type: 'client' | 'user', + // fix: this is jank + missing: (PermissionsString | '[[UnsupportedChannel]]')[] + ]; remove: [command: BotCommand]; slashBlocked: [message: SlashMessage, command: BotCommand, reason: string]; slashError: [error: Error, message: SlashMessage, command: BotCommand]; slashFinished: [message: SlashMessage, command: BotCommand, args: any, returnValue: any]; - slashMissingPermissions: [message: SlashMessage, command: BotCommand, type: 'client' | 'user', missing: PermissionsString[]]; + slashMissingPermissions: [ + message: SlashMessage, + command: BotCommand, + type: 'client' | 'user', + // fix: this is jank + missing: (PermissionsString | '[[UnsupportedChannel]]')[] + ]; slashStarted: [message: SlashMessage, command: BotCommand, args: any]; } export class BotCommandHandler extends CommandHandler { public declare modules: Collection<string, BotCommand>; public declare categories: Collection<string, Category<string, BotCommand>>; + + //! this is a simplified version of the original + public override async runPermissionChecks( + message: Message | SlashMessage, + command: BotCommand, + slash: boolean = false + ): Promise<boolean> { + const event = slash ? CommandHandlerEvent.SlashMissingPermissions : CommandHandlerEvent.MissingPermissions; + + 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()) { + if (noPerms && command.clientCheckChannel && appSlashPerms == null) { + this.emit(event, message, command, 'client', ['[[UnsupportedChannel]]']); + return true; + } + if (message.channel?.isDMBased()) return false; + + const missing = command.clientCheckChannel + ? (appSlashPerms ?? message.channel?.permissionsFor(message.guild.members.me!))?.missing(command.clientPermissions) + : message.guild?.members.me?.permissions.missing(command.clientPermissions); + + if (missing?.length) { + this.emit(event, message, command, 'client', missing); + return true; + } + } + + if (command.userPermissions) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const ignorer = command.ignorePermissions || this.ignorePermissions; + const isIgnored = Array.isArray(ignorer) + ? ignorer.includes(message.author.id) + : typeof ignorer === 'function' + ? ignorer(message, command) + : message.author.id === ignorer; + + if (!isIgnored) { + if (message.inGuild()) { + if (noPerms && command.userCheckChannel && userSlashPerms == null) { + this.emit(event, message, command, 'user', ['[[UnsupportedChannel]]']); + return true; + } + if (message.channel?.isDMBased()) return false; + + const missing = command.userCheckChannel + ? (userSlashPerms ?? message.channel?.permissionsFor(message.author))?.missing(command.userPermissions) + : message.member?.permissions.missing(command.userPermissions); + + if (missing?.length) { + this.emit(event, message, command, 'user', missing); + return true; + } + } + } + } + + return false; + } } export interface BotCommandHandler extends CommandHandler { findCommand(name: string): BotCommand; } + +export function permissionCheck( + message: CommandMessage | SlashMessage, + check: GuildMember, + perms: PermissionResolvable, + useChannel: boolean +): boolean { + if (message.inGuild()) { + if (!message.channel || message.channel.isDMBased()) return true; + + const missing = useChannel ? message.channel.permissionsFor(check)?.missing(perms) : check.permissions.missing(perms); + + if (missing?.length) return false; + } + return true; +} diff --git a/lib/extensions/discord-akairo/BotInhibitor.ts b/lib/extensions/discord-akairo/BotInhibitor.ts index d134eab..8892b8b 100644 --- a/lib/extensions/discord-akairo/BotInhibitor.ts +++ b/lib/extensions/discord-akairo/BotInhibitor.ts @@ -1,8 +1,12 @@ -import { type BotCommand, type CommandMessage, type SlashMessage } from '#lib'; -import { Inhibitor } from 'discord-akairo'; +import type { BotCommand, CommandMessage, InhibitorReason, InhibitorType, SlashMessage } from '#lib'; +import { Inhibitor, InhibitorOptions } from 'discord-akairo'; import { Message } from 'discord.js'; export abstract class BotInhibitor extends Inhibitor { + public constructor(id: InhibitorReason, options?: BotInhibitorOptions) { + super(id, options); + } + /** * Checks if message should be blocked. * A return value of true will block the message. @@ -16,3 +20,28 @@ export abstract class BotInhibitor extends Inhibitor { public abstract override exec(message: CommandMessage, command: BotCommand): any; public abstract override exec(message: CommandMessage | SlashMessage, command: BotCommand): any; } + +/** + * Options to use for inhibitor execution behavior. + */ +export interface BotInhibitorOptions extends InhibitorOptions { + /** + * Reason emitted when command or message is blocked. + * @default "" + */ + reason: InhibitorReason; + + /** + * - {@link InhibitorType.All} run on all messages + * - {@link InhibitorType.Pre} run on messages not blocked by the built-in inhibitors + * - {@link InhibitorType.Post} run on messages that are commands + */ + type: InhibitorType; + + /** + * Priority for the inhibitor for when more than one inhibitors block a message. + * The inhibitor with the highest priority is the one that is used for the block reason. + * @default 0 + */ + priority?: number; +} diff --git a/lib/extensions/discord-akairo/BotListener.ts b/lib/extensions/discord-akairo/BotListener.ts index f4bfd6c..4f760e2 100644 --- a/lib/extensions/discord-akairo/BotListener.ts +++ b/lib/extensions/discord-akairo/BotListener.ts @@ -1,3 +1,49 @@ -import { Listener } from 'discord-akairo'; +import { Listener, type ListenerOptions } from 'discord-akairo'; -export abstract class BotListener extends Listener {} +export abstract class BotListener extends Listener { + public constructor(id: string, options: BotListenerOptions) { + super(id, options); + } +} + +/** + * Options to use for listener execution behavior. + */ +export interface BotListenerOptions extends ListenerOptions { + /** + * The event emitter, either a key from `ListenerHandler#emitters` or an EventEmitter. + */ + emitter: Emitter; + + /** + * Event name to listen to. + */ + event: string; + + /** + * Type of listener, either 'on' or 'once'. + * @default "on" + */ + type?: ListenerType; +} + +export const enum Emitter { + Client = 'client', + CommandHandler = 'commandHandler', + InhibitorHandler = 'inhibitorHandler', + ListenerHandler = 'listenerHandler', + TaskHandler = 'taskHandler', + ContextMenuCommandHandler = 'contextMenuCommandHandler', + Process = 'process', + Stdin = 'stdin', + Gateway = 'gateway', + Rest = 'rest', + Ws = 'ws' +} + +export const enum ListenerType { + On = 'on', + Once = 'once', + PrependListener = 'prependListener', + PrependOnceListener = 'prependOnceListener' +} diff --git a/lib/extensions/discord-akairo/BotListenerHandler.ts b/lib/extensions/discord-akairo/BotListenerHandler.ts index 9b3b525..bc14a53 100644 --- a/lib/extensions/discord-akairo/BotListenerHandler.ts +++ b/lib/extensions/discord-akairo/BotListenerHandler.ts @@ -1,3 +1,19 @@ import { ListenerHandler } from 'discord-akairo'; +import type readline from 'readline'; +import { TanzaniteClient } from './TanzaniteClient.js'; export class BotListenerHandler extends ListenerHandler {} + +export interface Emitters { + client: TanzaniteClient; + commandHandler: TanzaniteClient['commandHandler']; + inhibitorHandler: TanzaniteClient['inhibitorHandler']; + listenerHandler: TanzaniteClient['listenerHandler']; + taskHandler: TanzaniteClient['taskHandler']; + contextMenuCommandHandler: TanzaniteClient['contextMenuCommandHandler']; + process: NodeJS.Process; + stdin: readline.Interface; + gateway: TanzaniteClient['ws']; + rest: TanzaniteClient['rest']; + ws: TanzaniteClient['ws']; +} diff --git a/lib/extensions/discord-akairo/TanzaniteClient.ts b/lib/extensions/discord-akairo/TanzaniteClient.ts index fe34b58..ac09aea 100644 --- a/lib/extensions/discord-akairo/TanzaniteClient.ts +++ b/lib/extensions/discord-akairo/TanzaniteClient.ts @@ -11,7 +11,6 @@ import { snowflake } from '#args'; import type { Config } from '#config'; -import { BotClientEvents, emojis, formatError, inspect, updateEveryCache } from '#lib'; import { patch, type PatchedElements } from '@notenoughupdates/events-intercept'; import * as Sentry from '@sentry/node'; import { @@ -62,14 +61,18 @@ import { } from '../../models/index.js'; import { AllowedMentions } from '../../utils/AllowedMentions.js'; import { BotClientUtils } from '../../utils/BotClientUtils.js'; +import { emojis } from '../../utils/Constants.js'; import { Logger } from '../../utils/Logger.js'; +import { updateEveryCache } from '../../utils/UpdateCache.js'; +import { formatError, inspect } from '../../utils/Utils.js'; +import { BotClientEvents } from '../discord.js/BotClientEvents.js'; import { ExtendedGuild } from '../discord.js/ExtendedGuild.js'; import { ExtendedGuildMember } from '../discord.js/ExtendedGuildMember.js'; import { ExtendedMessage } from '../discord.js/ExtendedMessage.js'; import { ExtendedUser } from '../discord.js/ExtendedUser.js'; import { BotCommandHandler } from './BotCommandHandler.js'; import { BotInhibitorHandler } from './BotInhibitorHandler.js'; -import { BotListenerHandler } from './BotListenerHandler.js'; +import { BotListenerHandler, Emitters } from './BotListenerHandler.js'; import { BotTaskHandler } from './BotTaskHandler.js'; declare module 'discord.js' { @@ -578,17 +581,3 @@ export interface BotStats { */ slashCommandsUsed: bigint; } - -export interface Emitters { - client: TanzaniteClient; - commandHandler: TanzaniteClient['commandHandler']; - inhibitorHandler: TanzaniteClient['inhibitorHandler']; - listenerHandler: TanzaniteClient['listenerHandler']; - taskHandler: TanzaniteClient['taskHandler']; - contextMenuCommandHandler: TanzaniteClient['contextMenuCommandHandler']; - process: NodeJS.Process; - stdin: readline.Interface; - gateway: TanzaniteClient['ws']; - rest: TanzaniteClient['rest']; - ws: TanzaniteClient['ws']; -} |