Skip to content
GitHub

HandlerRegistry

class in meocord/core Since 4.1.0

class HandlerRegistry

Lists every handler the app registered, with the metadata declared on it.

Inject it to build what reads the app's own handlers: a help command, an admin page or generated docs. To act on a handler's metadata while it runs, read it from ExecutionContext instead.

Examples

TypeScript
@Service()
export class HelpService {
  constructor(private readonly handlers: HandlerRegistry) {}

  // `!help` lists the message commands; `!help ban` shows one
  messageHelp(command?: string) {
    const commands = this.handlers.list({ kind: 'message' }).filter(entry => entry.command)
    const one = command ? commands.find(entry => entry.matches(command)) : undefined
    if (one) return [one.usage('!'), one.description].filter(Boolean).join('\n')
    return commands.map(entry => `${entry.usage('!')}: ${entry.description ?? ''}`).join('\n')
  }
}

Members

constructor

new HandlerRegistry(
  classes: readonly HandlerClass[],
  messages?: MessageCommandOptions,
  translator?: () => Translator<any> | undefined,
)

Parameters

NameTypeDefaultDescription
classesreadonly HandlerClass[]

The app's classes to read handlers from. The factory fills the list once the app is bound; entries are read on the first list.

messages?MessageCommandOptions

The app's messages options, whose caseSensitive message entries' matches follows.

messages.prefix?| MessagePrefix | (( message: Message, ) => | MessagePrefix | null | undefined | Promise< MessagePrefix | null | undefined >)

What a message starts with to reach a patterned handler: a prefix, a list of them, or a function of the message returning them, such as a server's own prefix. Without one, a pattern matches the message as it is. Where a function finds none, returning an empty list, undefined or null, no prefix starts a command for that message, though a mention still does when mention is on; only '' takes it as it is. A handler's own prefix replaces it.

messages.mention?boolean | 'only'false

Also accepts a mention of the bot, <@id> or <@!id>, where a prefix goes. 'only' accepts nothing else in a server, neither a prefix nor the message as it is, so a server's messages reach commands only when they mention the bot, which Discord delivers with their text even without the privileged MessageContent intent. A direct message, addressed to the bot already, starts as usual: after the prefix, or as it is without one.

messages.caseSensitive?booleanfalse

Matches the prefix, a pattern's literal words, its choice words and its flag names in the case written. A text param keeps the case the user typed either way.

messages.types?Record<string, MessageParamType>

Param types of the app's own, used in patterns as {name:type} by their key here. Declare each in MessageParamTypes as well, so a handler's params are typed from its pattern.

messages.deleteUsageRepliesAfter?number10

How long a reply showing a command's usage, or a guard's or validation's reason, stays before it is deleted, in seconds. 0 keeps it.

messages.replyEmoji?booleanfalse

Begins every text reply MeoCord sends to a message with the theme's emojis.warning: a command's usage, a guard's or validation's reason, a UserError's message, an @On listener's of a message event included, and the direct messages dmOnError and dmOnCooldown send. The built-in help's reply begins with emojis.info instead. The emoji is the call's resolved theme's, so it follows @UseTheme and themeFor.

messages.dmOnError?booleanfalse

Tells the author of a message command, in a direct message, when it fails with an error no filter handled, naming the command, the channel and the server. The error is logged as without it, and nothing is said in the channel: a message cannot be answered privately there. A command sent in a direct message is answered in it. The text is meocord.dm.error, around what the fallback answers the error with, such as the cooldown store's meocord.cooldown.storeDown, in the server's language. A member whose direct messages are closed is not told. Only patterned handlers are answered, not a listener for every message. A command refused because the cooldown store failed is told once per outage per author.

messages.dmOnCooldown?booleanfalse

Tells the author of a message command, in a direct message, when a @Cooldown refuses it, with how long to wait, once per wait: retrying before it ends sends nothing more. The notice is counted in the app's cooldown store, so it holds across shards with a shared store. The text is meocord.dm.cooldown, around the cooldown's own wait text, in the server's language. A command sent in a direct message is answered in it, and a member whose direct messages are closed is not told. Only patterned handlers are answered.

messages.help?boolean | MessageHelpOptionsfalse

Answers !help with the message commands that work here, leaving out hidden ones and those with guards of their own, since help runs only the app's guards, and !help <command> with the one it names, listed or not, from the description each handler gives. true uses the word help; { command, aliases } names other words. It answers only after a prefix or a mention, once the app's @MeoCord({ guards }) allow it, and an app's own handler for the word runs instead. The reply's text comes from the presenter's messageHelp when it has one.

translator?() => Translator<any> | undefined

The app's translator, read when messageHelp words its labels; none for English.

list

list<K extends HandlerKind = HandlerKind>(
  filter?: HandlerFilter<K>,
): Extract<HandlerEntry, { kind: K }>[]

Lists the registered handlers.

Parameters

NameTypeDescription
filter?HandlerFilter<K>

Narrows the list by kind, controller, or both.

Returns

Extract<HandlerEntry, { kind: K }>[]

The handlers, class by class in the order the app makes its classes, each after what it injects.

messageHelp

messageHelp(message: Message, query?: string): Promise<MessageHelp>

Works out what the built-in help would answer a message, for a help command of your own.

It lists the message commands of the app's controllers that work where the message was sent, or describes the one query names, with the same routes and rules the built-in follows: hidden and guarded handlers are left out of lists, and shown when named. It works whether messages.help is on or off.

Parameters

NameTypeDescription
messageMessage

The message asking for help; its start and where it was sent decide what is listed.

query?string

The command asked about, such as ban or config set; leave it out to list them all.

Returns

Promise<MessageHelp>

The help, as the presenter's messageHelp receives it.

Examples

TypeScript
@MessageHandler('help {command...?}')
async help(message: Message, { command }: { command?: string }) {
  const help = await this.handlers.messageHelp(message, command)
  if (help.kind === 'list') await message.reply(help.commands.map(entry => `**${entry.usage}** ${entry.description ?? ''}`).join('\n'))
  else await message.reply(help.kind === 'unknown' ? `No command is called ${help.query}.` : 'Ask a moderator.')
}

See also