UNPKG

@grammyjs/commands

Version:
273 lines (272 loc) 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandGroup = void 0; exports.commandNotFound = commandNotFound; const mod_js_1 = require("./mod.js"); const deps_node_js_1 = require("./deps.node.js"); const array_js_1 = require("./utils/array.js"); const set_bot_commands_js_1 = require("./utils/set-bot-commands.js"); const checks_js_1 = require("./utils/checks.js"); /** * Central class that manages all registered commands. * This is the starting point for the plugin, and this is what you should pass to `bot.use` so your commands get properly registered. * * @example * ```typescript * const myCommands = new CommandGroup() * commands.command("start", "start the bot configuration", (ctx) => ctx.reply("Hello there!")) * * // Registers the commands with the bot instance. * bot.use(myCommands) * ``` */ class CommandGroup { constructor(options = {}) { var _a; this._languages = new Set(); this._scopes = new Map(); this._commands = []; this._cachedComposer = new deps_node_js_1.Composer(); this._cachedComposerInvalidated = false; this._commandOptions = {}; this._commandOptions = options; if (((_a = this._commandOptions.prefix) === null || _a === void 0 ? void 0 : _a.trim()) === "") { this._commandOptions.prefix = "/"; } } _addCommandToScope(scope, command) { var _a; const commands = (_a = this._scopes.get(JSON.stringify(scope))) !== null && _a !== void 0 ? _a : []; this._scopes.set(JSON.stringify(scope), commands.concat([command])); } _populateMetadata() { this._languages.clear(); this._scopes.clear(); this._commands.forEach((command) => { for (const scope of command.scopes) { this._addCommandToScope(scope, command); } for (const language of command.languages.keys()) { this._languages.add(language); } }); } command(name, description, handlerOrOptions, _options) { const handler = (0, checks_js_1.isMiddleware)(handlerOrOptions) ? handlerOrOptions : undefined; const options = !handler && (0, checks_js_1.isCommandOptions)(handlerOrOptions) ? { ...this._commandOptions, ...handlerOrOptions } : { ...this._commandOptions, ..._options }; const command = new mod_js_1.Command(name, description, handler, options); this._commands.push(command); this._cachedComposerInvalidated = true; return command; } /** * Registers a Command that was created by it's own. * * @param command the command or list of commands being added to the group */ add(command) { this._commands.push(...(0, array_js_1.ensureArray)(command)); this._cachedComposerInvalidated = true; return this; } /** * Serializes the commands into multiple objects that can each be passed to a `setMyCommands` call. * * @returns One item for each combination of command + scope + language */ toArgs(chat_id) { this._populateMetadata(); const scopes = []; const uncompliantCommands = []; for (const [scope, commands] of this._scopes.entries()) { for (const language of this._languages) { const compliantScopedCommands = []; commands.forEach((command) => { const [isApiCompliant, ...reasons] = command.isApiCompliant(language); if (isApiCompliant) { return compliantScopedCommands.push(command); } uncompliantCommands.push({ name: command.stringName, reasons: reasons, language, }); }); if (compliantScopedCommands.length) { scopes.push({ scope: chat_id ? { ...JSON.parse(scope), chat_id } : { ...JSON.parse(scope) }, language_code: language === "default" ? undefined : language, commands: compliantScopedCommands.map((command) => command.toObject(language)), }); } } } return { scopes, uncompliantCommands, }; } /** * Serializes the commands of a single scope into objects that can each be passed to a `setMyCommands` call. * * @param scope Selected scope to be serialized * @returns One item per command per language */ toSingleScopeArgs(scope) { this._populateMetadata(); const commandParams = []; const uncompliantCommands = []; for (const language of this._languages) { const compliantCommands = []; this._commands.forEach((command) => { const [isApiCompliant, ...reasons] = command.isApiCompliant(language); if (!isApiCompliant) { return uncompliantCommands.push({ name: command.stringName, reasons: reasons, language, }); } if (command.scopes.length) compliantCommands.push(command); }); commandParams.push({ scope, language_code: language === "default" ? undefined : language, commands: compliantCommands.map((command) => command.toObject(language)), }); } return { commandParams, uncompliantCommands }; } /** * Registers all commands to be displayed by clients according to their scopes and languages * Calls `setMyCommands` for each language of each scope of each command. * * [!IMPORTANT] * Calling this method with upperCased command names registered, will throw * @see https://core.telegram.org/bots/api#botcommand * @see https://core.telegram.org/method/bots.setBotCommands * * @param Instance of `bot` or { api: bot.api } */ async setCommands({ api }, options) { const { scopes, uncompliantCommands } = this.toArgs(); await (0, set_bot_commands_js_1.setBotCommands)(api, scopes, uncompliantCommands, options); } /** * Serialize all register commands into a more detailed object * including it's name, prefix and language, and more data * * @param filterLanguage if undefined, it returns all names * else get only the locales for the given filterLanguage * fallbacks to "default" * * @returns an array of {@link BotCommandX} * * Note: mainly used to serialize for {@link FuzzyMatch} */ toElementals(filterLanguage) { this._populateMetadata(); return Array.from(this._scopes.values()) .flat() .flatMap((command) => { const elements = []; for (const [language, local] of command.languages.entries()) { elements.push({ command: local.name instanceof RegExp ? local.name.source : local.name, language, prefix: command.prefix, scopes: command.scopes, description: command.getLocalizedDescription(language), ...(command.hasHandler ? { hasHandler: true } : { hasHandler: false }), }); } if (filterLanguage) { const filtered = elements.filter((command) => command.language === filterLanguage); const defaulted = elements.filter((command) => command.language === "default"); return filtered.length ? filtered[0] : defaulted[0]; } else return elements; }); } /** * @returns A JSON serialized version of all the currently registered commands */ toString() { return JSON.stringify(this); } middleware() { if (this._cachedComposerInvalidated) { this._cachedComposer = new deps_node_js_1.Composer(...this._commands); this._cachedComposerInvalidated = false; } return this._cachedComposer.middleware(); } /** * @returns all {@link Command}s contained in the instance */ get commands() { return this._commands; } /** * @returns all prefixes registered in this instance */ get prefixes() { return [ ...new Set(this._commands.flatMap((command) => command.prefix)), ]; } /** * Replaces the `toString` method on Deno * * @see toString */ [Symbol.for("Deno.customInspect")]() { return this.toString(); } /** * Replaces the `toString` method on Node.js * * @see toString */ [Symbol.for("nodejs.util.inspect.custom")]() { return this.toString(); } } exports.CommandGroup = CommandGroup; function commandNotFound(commands, opts = {}) { return function (ctx) { if (containsCommands(ctx, commands)) { ctx .commandSuggestion = ctx .getNearestCommand(commands, opts); return true; } return false; }; } function containsCommands(ctx, commands) { let allPrefixes = [ ...new Set((0, array_js_1.ensureArray)(commands).flatMap((cmds) => cmds.prefixes)), ]; if (allPrefixes.length < 1) { allPrefixes = ["/"]; } for (const prefix of allPrefixes) { const regex = (0, array_js_1.getCommandsRegex)(prefix); if (ctx.hasText(regex)) return true; } return false; }