@grammyjs/commands
Version:
grammY Commands Plugin
177 lines (176 loc) • 7.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MyCommandParams = void 0;
exports.commands = commands;
const array_js_1 = require("./utils/array.js");
const jaro_winkler_js_1 = require("./utils/jaro-winkler.js");
const set_bot_commands_js_1 = require("./utils/set-bot-commands.js");
/**
* Installs the commands flavor into the context.
*/
function commands() {
return (ctx, next) => {
ctx.setMyCommands = async (commands, options) => {
if (!ctx.chat) {
throw new Error("cannot call `ctx.setMyCommands` on an update with no `chat` property");
}
const { uncompliantCommands, commandsParams: currentChatCommandParams, } = MyCommandParams.from((0, array_js_1.ensureArray)(commands), ctx.chat.id);
await (0, set_bot_commands_js_1.setBotCommands)(ctx.api, currentChatCommandParams, uncompliantCommands, options);
};
ctx.getNearestCommand = (commands, options) => {
if (!ctx.has(":text")) {
throw new Error("cannot call `ctx.getNearestCommand` on an update with no `text`");
}
const results = (0, array_js_1.ensureArray)(commands)
.map((commands) => {
var _a;
const firstMatch = ctx.getCommandEntities(commands)[0];
const commandLike = (firstMatch === null || firstMatch === void 0 ? void 0 : firstMatch.text.replace(firstMatch.prefix, "")) ||
"";
const result = (0, jaro_winkler_js_1.fuzzyMatch)(commandLike, commands, {
...options,
language: !(options === null || options === void 0 ? void 0 : options.ignoreLocalization)
? (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.language_code
: undefined,
});
return result;
}).sort((a, b) => { var _a, _b; return ((_a = b === null || b === void 0 ? void 0 : b.similarity) !== null && _a !== void 0 ? _a : 0) - ((_b = a === null || a === void 0 ? void 0 : a.similarity) !== null && _b !== void 0 ? _b : 0); });
const result = results[0];
if (!result || !result.command)
return null;
return result.command.prefix + result.command.name;
};
ctx.getCommandEntities = (commands) => {
if (!ctx.has(":text")) {
throw new Error("cannot call `ctx.commandEntities` on an update with no `text`");
}
const text = ctx.msg.text;
if (!text)
return [];
const prefixes = (0, array_js_1.ensureArray)(commands).flatMap((cmds) => cmds.prefixes);
if (!prefixes.length)
return [];
const regexes = prefixes.map((prefix) => (0, array_js_1.getCommandsRegex)(prefix));
const entities = regexes.flatMap((regex) => {
let match;
const matches = [];
while ((match = regex.exec(text)) !== null) {
const text = match[0].trim();
matches.push({
text,
offset: match.index,
prefix: match.groups.prefix,
type: "bot_command",
length: text.length,
});
}
return matches;
});
return entities;
};
return next();
};
}
/**
* Static class for getting and manipulating {@link SetMyCommandsParams}.
* The main function is {@link from}
*/
class MyCommandParams {
/**
* Merges and serialize one or more Commands instances into a single array
* of commands params that can be used to set the commands menu displayed to the user.
* @example
```ts
const adminCommands = new CommandGroup();
const userCommands = new CommandGroup();
adminCommands
.command("do a",
"a description",
(ctx) => ctx.doA());
userCommands
.command("do b",
"b description",
(ctx) => ctx.doB());
const mergedParams =
MyCommandParams.from([a, b], someChatId);
```
* @param commands An array of one or more Commands instances.
* @returns an array of {@link SetMyCommandsParams} grouped by language
*/
static from(commands, chat_id) {
const serializedCommands = this._serialize(commands, chat_id);
const commandsParams = serializedCommands
.map(({ commandParams }) => commandParams)
.flat();
const uncompliantCommands = serializedCommands
.map(({ uncompliantCommands }) => uncompliantCommands)
.flat();
return {
commandsParams: this.mergeByLanguage(commandsParams),
uncompliantCommands,
};
}
/**
* Serializes one or multiple {@link CommandGroup} instances, each one into their respective
* single scoped SetMyCommandsParams version.
* @example
```ts
const adminCommands = new CommandGroup();
// add to scope, localize, etc
const userCommands = new CommandGroup();
// add to scope, localize, etc
const [
singleScopedAdminParams,
singleScopedUserParams
] = MyCommandsParams.serialize([adminCommands,userCommands])
```
* @param commandsArr an array of one or more commands instances
* @param chat_id the chat id relative to the message update, coming from the ctx object.
* @returns an array of scoped {@link SetMyCommandsParams} mapped from their respective Commands instances
*/
static _serialize(commandsArr, chat_id) {
return commandsArr.map((commands) => commands.toSingleScopeArgs({
type: "chat",
chat_id,
}));
}
/**
* Lexicographically sorts commandParams based on their language code.
* @returns the sorted array
*/
static _sortByLanguage(params) {
return params.sort((a, b) => {
if (!a.language_code)
return -1;
if (!b.language_code)
return 1;
return a.language_code.localeCompare(b.language_code);
});
}
/**
* Iterates over an array of CommandsParams
* merging their respective {@link SetMyCommandsParams.commands}
* when they are from the same language, separating when they are not.
*
* @param params a flattened array of commands params coming from one or more Commands instances
* @returns an array containing all commands grouped by language
*/
static mergeByLanguage(params) {
if (!params.length)
return [];
const sorted = this._sortByLanguage(params);
return sorted.reduce((result, current, i, arr) => {
if (i === 0 || current.language_code !== arr[i - 1].language_code) {
result.push(current);
return result;
}
else {
result[result.length - 1].commands = result[result.length - 1]
.commands
.concat(current.commands);
return result;
}
}, []);
}
}
exports.MyCommandParams = MyCommandParams;