From 1705dd850ecec313a76eb15c7c9e6fdd2ea89d06 Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Thu, 21 Mar 2024 18:09:27 +0100 Subject: [PATCH 01/59] Draft commands plugin page --- site/docs/plugins/commands.md | 35 +++++++++++++++++++++++++++++++++-- site/modules.ts | 10 ++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index e8525a963..b9e658eb8 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -5,10 +5,41 @@ next: false # Commands (`commands`) -Coming soon, please come back later. +Command handling on steroids. + +This plugin provides various features related to command handling that are not contained in the [command handling done by the grammY core library](../guide/commands). +Here is a quick overview of what you get with this plugin: + +- Better code structure by attaching middleware to command definitions +- Automatic synchronization via `setMyCommands` +- Defining command translations +- Command scope handling +- "Did you mean ...?" feature that automatically finds the nearest existing command + +All of these features are made possible because you will define one or more central command structures that you define for your commands. + +## Basic Usage + +todo simple example with no features + +## Automatic Command Setting + +todo how to make `setMyCommands` work + +## Scoped Commands + +todo command scopes from context + +## Command Translations + +todo localize commands + +## Finding the Nearest Command + +todo jaro winkler ## Plugin Summary - Name: `commands` - [Source](https://github.com/grammyjs/commands) -- Reference +- [Reference](/ref/commands) diff --git a/site/modules.ts b/site/modules.ts index c94a974c3..9cff1f263 100644 --- a/site/modules.ts +++ b/site/modules.ts @@ -124,6 +124,16 @@ export const modules: ModuleConfig[] = [ ), shortdescription: sdesc("the [internationalization plugin](/plugins/i18n)"), }, + { + repo: "commands", + slug: "commands", + name: "Commands", + description: desc( + "the [commands plugin](/plugins/commands)", + "the commands plugin", + ), + shortdescription: sdesc("the [commands plugin](/plugins/commands)"), + }, { repo: "router", slug: "router", From 3ae0dba1762a5e92a50561ec0a81eabd17b5866e Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Fri, 22 Mar 2024 23:32:53 +0100 Subject: [PATCH 02/59] Update site/docs/plugins/commands.md --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index b9e658eb8..e9a6f7770 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -16,7 +16,7 @@ Here is a quick overview of what you get with this plugin: - Command scope handling - "Did you mean ...?" feature that automatically finds the nearest existing command -All of these features are made possible because you will define one or more central command structures that you define for your commands. +All of these features are made possible because you will define one or more central command structures that define your bot's commands. ## Basic Usage From 8f1736f9112754217b773ea2e0dd4ad079d94ad9 Mon Sep 17 00:00:00 2001 From: Roz Date: Sat, 27 Apr 2024 17:01:15 -0300 Subject: [PATCH 03/59] feat: add complete docs for command plugin --- package-lock.json | 6 + site/docs/plugins/commands.md | 285 ++- site/package-lock.json | 3301 +++++++++++++++++---------------- 3 files changed, 1936 insertions(+), 1656 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..4ff32e61b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "website", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index e9a6f7770..a5d9c0e12 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -12,31 +12,302 @@ Here is a quick overview of what you get with this plugin: - Better code structure by attaching middleware to command definitions - Automatic synchronization via `setMyCommands` -- Defining command translations - Command scope handling -- "Did you mean ...?" feature that automatically finds the nearest existing command +- Defining command translations +- "Did you mean ...?" feature that finds the nearest existing command +- Custom command prefixes +- Specify what to do with commands that mention your bot's user +- Support for commands that are not in the beggining of the message +- RegEx support for command names All of these features are made possible because you will define one or more central command structures that define your bot's commands. ## Basic Usage -todo simple example with no features +Before we dive in, take a look at how you can register and handle a command with the plugin: + +```js +const myCommands = new Commands(); + +myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); + +bot.use(myCommands); +``` + +This registers a new "/start" command to your bot that will be handled by the given middleware. + +Now, let's get into some of the extra tools this plugin has to offer. + +## Importing + +First of all, we need to import the `Commands` class. + +::: code-group + +```ts [TypeScript] +import { Commands, commands, type CommandsFlavor } from "@grammyjs/commands"; +``` + +```js [JavaScript] +const { Commands, commands } = require("@grammyjs/commands"); +``` + +```ts [Deno] +import { + Commands, + commands, + type CommandsFlavor, +} from "https://deno.land/x/grammy_commands/mod.ts"; +``` + +::: + +Now that that's settled, let's see how we can make our commands visible to our users. ## Automatic Command Setting -todo how to make `setMyCommands` work +Once you defined your commands with an instance of the `Commands` class, you can call the `setCommands` method, which will register all the defined commands to your bot. + +```js +const myCommands = new Commands() + +myCommands.command("hello", "say hello", (ctx) => ctx.reply("Hi there!")) +myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting..."))\ + +bot.use(myCommands) + +await myCommands.setCommands(bot) +``` + +This will make it so every command you registered is displayed on the menu of a private chat with your bot, or whenever users type `/` on a chat your bot is a member of. ## Scoped Commands -todo command scopes from context +Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? +That's what Telegram call **Command Scopes**. + +The `Command` class returned by the `command` method exposes a method called `addToScope`. +This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. + +You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. + +Here's an example of a scoped command: + +```js +const myCommands = new Commands(); + +myCommands + .command("start", "Initializes bot configuration") + .addToScope({ type: "all_private_chats" }, (ctx) => + ctx.reply(`Hello, ${ctx.chat.first_name}!`) + ) + .addToScope({ type: "all_group_chats" }, (ctx) => + ctx.reply(`Hello, members of ${ctx.chat.title}!`) + ); +``` + +The "start" command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. +Now if you call `myCommands.setCommands`, the "start" command will be registered to both private and group chats. + +### Context shortcut + +What if you want some commands to be displayed only for certain users. +For example, imagine you have a "login" and a "logout" command. +The "login" command should only appear for logged out users, and vice versa. +This is how you can do that with the Commands plugin: + +::: code-group + +```ts [TypeScript] +// Use the flavor to create a custom context +type MyContext = Context & CommandsFlavor; + +// Use the new context to instantiate your bot +const bot = new Bot("token"); + +// Register the context shortcut +bot.use(commands()); + +const loggedOutCommands = new Commands(); +const loggedInCommands = new Commands(); + +loggedOutCommands.command( + "login", + "Start your session with the bot", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Welcome! Session started!"); + } +); + +loggedInCommands.command( + "logout", + "End your session with the bot", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Goodbye :)"); + } +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// by default, users are not logged in, +// so you can set the logged out commands for everyone +await loggedOutCommands.setCommands(bot); +``` + +```js [JavaScript] +// Register the context shortcut +bot.use(commands()); + +const loggedOutCommands = new Commands(); +const loggedInCommands = new Commands(); + +loggedOutCommands.command( + "login", + "Start your session with the bot", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Welcome! Session started!"); + } +); + +loggedInCommands.command( + "logout", + "End your session with the bot", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Goodbye :)"); + } +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// by default, users are not logged in, +// so you can set the logged out commands for everyone +await loggedOutCommands.setCommands(bot); +``` + +::: + +This way when a user calls `/login`, they'll have their commands list changed to contain only the "logout" command. +Neat, right? ## Command Translations -todo localize commands +Another extremely useful possibility is setting commands to have different names and descriptions based on the user language. +The Commands plugin makes that easy by providing the `localize` method. +Check it out: + +```js +myCommands + // You need to set a default name and description + .command("hello", "Say hello") + // And then you can set the localized ones + .localize("pt", "ola", "Dizer olá"); +``` + +Add as many as you want! +The plugin will take care of registering them for you when you call `myCommands.setCommands`. ## Finding the Nearest Command -todo jaro winkler +Even though Telegram it capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. +The Commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. +Usage is quite straight-forward: + +::: code-group + +```ts [TypeScript] +// Use the flavor to create a custom context +type MyContext = Context & CommandsFlavor; + +// Use the new context to instantiate your bot +const bot = new Bot("token"); + +// Register the context shortcut +bot.use(commands()); + +const myCommands = new Commands(); + +// ... Register the commands + +bot + // Check if there is a command + .filter(Context.has.filterQuery("::bot_command")) + // If so, that means it wasn't handled by any of our commands. + // Let's try and guess what the user meant. + .use(async (ctx) => { + const suggestedCommand = ctx.getNearestCommand(myCommands); + + // We found a potential match + if (suggestedCommand) + return ctx.reply( + `Hm... I don't know that command. Did you mean ${suggestedCommand}?` + ); + + // Nothing seems to come close to what the user typed + await ctx.reply("Ops... I don't know that command :/"); + }); +``` + +```js [JavaScript] +// Register the context shortcut +bot.use(commands()); + +const myCommands = new Commands(); + +// ... Register the commands + +bot + // Check if there is a command + .filter(Context.has.filterQuery("::bot_command")) + // If so, that means it wasn't handled by any of our commands. + // Let's try and guess what the user meant. + .use(async (ctx) => { + const suggestedCommand = ctx.getNearestCommand(myCommands); + + // We found a potential match + if (suggestedCommand) + return ctx.reply( + `Hm... I don't know that command. Did you mean ${suggestedCommand}?` + ); + + // Nothing seems to come close to what the user typed + await ctx.reply("Ops... I don't know that command :/"); + }); +``` + +::: + +## Command Options + +There are a few options that can be specified per command, per scope, or globally for a `Commands` instance. +These options allow you to further customize how your bot handles commands, giving you more flexibility. + +### `targetedCommands` + +When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. +You can decide what to do with these commands by using the `targetedCommands` config option. +With it you can coose between three different behaviors: + +- `ignored`: Ignores commands that mention your bot's user +- `optional`: Handles both commands that do and that don't mention the bot's user +- `required`: Only handles commands that mention the bot's user + +### `prefix` + +Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). +In some occasions, you might want to change that and use a custom prefix for your bot. +That is made possible by the `prefix` option, which will tell the Commands plugin to look for that prefix when trying to identify a command + +### `matchOnlyAtStart` + +When [handling commands](../guide/commands.md), the grammY core library will only recognize commands that start on the first carachter of a message. +The Commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! +All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. ## Plugin Summary diff --git a/site/package-lock.json b/site/package-lock.json index 84db6f91b..cd4d5575d 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1,1651 +1,1654 @@ { - "name": "site", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "license": "MIT", - "devDependencies": { - "@types/markdown-it": "13.0.7", - "@types/node": "20.11.28", - "deno-bin": "1.40.5", - "lazy-lottie-player": "0.0.1", - "markdownlint-cli2": "0.12.1", - "sass": "1.71.0", - "vitepress": "1.0.0-rc.43" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", - "dev": true, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz", - "integrity": "sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.20.0" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.20.0.tgz", - "integrity": "sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ==", - "dev": true - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz", - "integrity": "sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.20.0" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.20.0.tgz", - "integrity": "sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/client-search": "4.20.0", - "@algolia/transporter": "4.20.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.20.0.tgz", - "integrity": "sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/client-search": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.20.0.tgz", - "integrity": "sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.20.0.tgz", - "integrity": "sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.20.0.tgz", - "integrity": "sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg==", - "dev": true, - "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" - } - }, - "node_modules/@algolia/logger-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.20.0.tgz", - "integrity": "sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ==", - "dev": true - }, - "node_modules/@algolia/logger-console": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.20.0.tgz", - "integrity": "sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA==", - "dev": true, - "dependencies": { - "@algolia/logger-common": "4.20.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz", - "integrity": "sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.20.0" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.20.0.tgz", - "integrity": "sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng==", - "dev": true - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz", - "integrity": "sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng==", - "dev": true, - "dependencies": { - "@algolia/requester-common": "4.20.0" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.20.0.tgz", - "integrity": "sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg==", - "dev": true, - "dependencies": { - "@algolia/cache-common": "4.20.0", - "@algolia/logger-common": "4.20.0", - "@algolia/requester-common": "4.20.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", - "dev": true - }, - "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", - "dev": true, - "dependencies": { - "@docsearch/react": "3.5.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", - "dev": true, - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", - "algoliasearch": "^4.19.1" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", - "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", - "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@shikijs/core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.1.3.tgz", - "integrity": "sha512-1QWSvWcPbvZXsDxB1F7ejW+Kuxp3z/JHs944hp/f8BYOlFd5gplzseFIkE/GTu/qytFef3zNME4qw1oHbQ0j2A==", - "dev": true - }, - "node_modules/@shikijs/transformers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.1.3.tgz", - "integrity": "sha512-jv71dQFTucv2RK2pafAxca4hgKP6Uv5ukKrVjH/vGZ8jGH0j2AcLVCcM76ieamwJ1p5WkZcA0X/Bq2qpjhEUSg==", - "dev": true, - "dependencies": { - "shiki": "1.1.3" - } - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", - "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, - "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "dev": true - }, - "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", - "dev": true, - "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.11.28", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", - "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "dev": true - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", - "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", - "dev": true, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", - "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.19", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", - "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", - "dev": true, - "dependencies": { - "@vue/compiler-core": "3.4.19", - "@vue/shared": "3.4.19" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz", - "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.19", - "@vue/compiler-dom": "3.4.19", - "@vue/compiler-ssr": "3.4.19", - "@vue/shared": "3.4.19", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.6", - "postcss": "^8.4.33", - "source-map-js": "^1.0.2" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", - "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.4.19", - "@vue/shared": "3.4.19" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.14.tgz", - "integrity": "sha512-TluWR9qZ6aO11bwtYK8+fzXxBqLfsE0mWZz1q/EQBmO9k82Cm6deieLwNNXjNFJz7xutazoia5Qa+zTYkPPOfw==", - "dev": true, - "dependencies": { - "@vue/devtools-kit": "^7.0.14" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.14.tgz", - "integrity": "sha512-wAAJazr4hI0aVRpgWOCVPw+NzMQdthhnprHHIg4njp1MkKrpCNGQ7MtQbZF1AltAA7xpMCGyyt+0kYH0FqTiPg==", - "dev": true, - "dependencies": { - "@vue/devtools-schema": "^7.0.14", - "@vue/devtools-shared": "^7.0.14", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1" - } - }, - "node_modules/@vue/devtools-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-schema/-/devtools-schema-7.0.14.tgz", - "integrity": "sha512-tpUeCLVrdHX+KzWMLTAwx/vAPFbo6jAUi7sr6Q+0mBIqIVSSIxNr5wEhegiFvYva+OtDeM2OrT+f7/X/5bvZNg==", - "dev": true - }, - "node_modules/@vue/devtools-shared": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.14.tgz", - "integrity": "sha512-79RP1NDakBVWou9rDpVnT1WMjTbL1lJKm6YEOodjQ0dq5ehf0wsRbeYDhgAlnjehWRzTq5GAYFBFUPYBs0/QpA==", - "dev": true, - "dependencies": { - "rfdc": "^1.3.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz", - "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==", - "dev": true, - "dependencies": { - "@vue/shared": "3.4.19" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz", - "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==", - "dev": true, - "dependencies": { - "@vue/reactivity": "3.4.19", - "@vue/shared": "3.4.19" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz", - "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==", - "dev": true, - "dependencies": { - "@vue/runtime-core": "3.4.19", - "@vue/shared": "3.4.19", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz", - "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==", - "dev": true, - "dependencies": { - "@vue/compiler-ssr": "3.4.19", - "@vue/shared": "3.4.19" - }, - "peerDependencies": { - "vue": "3.4.19" - } - }, - "node_modules/@vue/shared": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", - "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", - "dev": true - }, - "node_modules/@vueuse/core": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", - "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", - "dev": true, - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", - "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", - "dev": true, - "dependencies": { - "@vueuse/core": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "*", - "axios": "*", - "change-case": "*", - "drauu": "*", - "focus-trap": "*", - "fuse.js": "*", - "idb-keyval": "*", - "jwt-decode": "*", - "nprogress": "*", - "qrcode": "*", - "sortablejs": "*", - "universal-cookie": "*" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", - "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", - "dev": true, - "dependencies": { - "vue-demi": ">=0.14.6" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/algoliasearch": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.20.0.tgz", - "integrity": "sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.20.0", - "@algolia/cache-common": "4.20.0", - "@algolia/cache-in-memory": "4.20.0", - "@algolia/client-account": "4.20.0", - "@algolia/client-analytics": "4.20.0", - "@algolia/client-common": "4.20.0", - "@algolia/client-personalization": "4.20.0", - "@algolia/client-search": "4.20.0", - "@algolia/logger-common": "4.20.0", - "@algolia/logger-console": "4.20.0", - "@algolia/requester-browser-xhr": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/requester-node-http": "4.20.0", - "@algolia/transporter": "4.20.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - }, - "node_modules/deno-bin": { - "version": "1.40.5", - "resolved": "https://registry.npmjs.org/deno-bin/-/deno-bin-1.40.5.tgz", - "integrity": "sha512-iAiXs+twAp4EZiT9jUZ0J22Z3DfzGFbvnO1VGLP8canhQ/UJCBuUz0DHyQp6rDxZ67VaS/ouRWgVK8ELQWLCcw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "adm-zip": "^0.5.4" - }, - "bin": { - "deno": "bin/deno.js", - "deno-bin": "bin/deno.js" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", - "dev": true, - "dependencies": { - "tabbable": "^6.2.0" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globby": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", - "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", - "dev": true, - "dependencies": { - "@sindresorhus/merge-streams": "^1.0.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true - }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true - }, - "node_modules/lazy-lottie-player": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/lazy-lottie-player/-/lazy-lottie-player-0.0.1.tgz", - "integrity": "sha512-DSRUFZLZe+bnUFf+End4DZEwSqIxo0a71zr10m+7hCqAkvztgIYF/oX5a7HJWYnWipPn8gAqD78KTWTjPPcZLw==", - "dev": true, - "funding": { - "url": "https://PonomareVlad.ru/donate" - } - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true - }, - "node_modules/markdown-it": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", - "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.0.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdownlint": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.33.0.tgz", - "integrity": "sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==", - "dev": true, - "dependencies": { - "markdown-it": "14.0.0", - "markdownlint-micromark": "0.1.8" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/DavidAnson" - } - }, - "node_modules/markdownlint-cli2": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.12.1.tgz", - "integrity": "sha512-RcK+l5FjJEyrU3REhrThiEUXNK89dLYNJCYbvOUKypxqIGfkcgpz8g08EKqhrmUbYfYoLC5nEYQy53NhJSEtfQ==", - "dev": true, - "dependencies": { - "globby": "14.0.0", - "jsonc-parser": "3.2.0", - "markdownlint": "0.33.0", - "markdownlint-cli2-formatter-default": "0.0.4", - "micromatch": "4.0.5", - "yaml": "2.3.4" - }, - "bin": { - "markdownlint-cli2": "markdownlint-cli2.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/DavidAnson" - } - }, - "node_modules/markdownlint-cli2-formatter-default": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.4.tgz", - "integrity": "sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==", - "dev": true, - "peerDependencies": { - "markdownlint-cli2": ">=0.0.4" - } - }, - "node_modules/markdownlint-micromark": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.8.tgz", - "integrity": "sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/DavidAnson" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minisearch": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", - "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", - "dev": true - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.19.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", - "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", - "dev": true - }, - "node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", - "dev": true, - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/sass": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", - "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/search-insights": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.11.0.tgz", - "integrity": "sha512-Uin2J8Bpm3xaZi9Y8QibSys6uJOFZ+REMrf42v20AA3FUDUrshKkMEP6liJbMAHCm71wO6ls4mwAf7a3gFVxLw==", - "dev": true, - "peer": true - }, - "node_modules/shiki": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.1.3.tgz", - "integrity": "sha512-k/B4UvtWmGcHMLp6JnQminlex3Go5MHKXEiormmzTJECAiSQiwSon6USuwTyto8EMUQc9aYRJ7HojkfVLbBk+g==", - "dev": true, - "dependencies": { - "@shikijs/core": "1.1.3" - } - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/uc.micro": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", - "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==", - "dev": true - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vite": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", - "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vitepress": { - "version": "1.0.0-rc.43", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.43.tgz", - "integrity": "sha512-XZ9xaN76/LxCBqvk6U+3ne3T60JOavdOlk+FMQBlXYK/9pyyKGfjnEra4yKYvOdZdStoTg8VXTAj4wcsCTlJaQ==", - "dev": true, - "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@shikijs/core": "^1.1.3", - "@shikijs/transformers": "^1.1.3", - "@types/markdown-it": "^13.0.7", - "@vitejs/plugin-vue": "^5.0.4", - "@vue/devtools-api": "^7.0.14", - "@vueuse/core": "^10.7.2", - "@vueuse/integrations": "^10.7.2", - "focus-trap": "^7.5.4", - "mark.js": "8.11.1", - "minisearch": "^6.3.0", - "shiki": "^1.1.3", - "vite": "^5.1.3", - "vue": "^3.4.19" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.35" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", - "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==", - "dev": true, - "dependencies": { - "@vue/compiler-dom": "3.4.19", - "@vue/compiler-sfc": "3.4.19", - "@vue/runtime-dom": "3.4.19", - "@vue/server-renderer": "3.4.19", - "@vue/shared": "3.4.19" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "dev": true, - "engines": { - "node": ">= 14" - } - } - } + "name": "site", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "license": "MIT", + "devDependencies": { + "@types/markdown-it": "13.0.7", + "@types/node": "20.11.28", + "deno-bin": "1.40.5", + "lazy-lottie-player": "0.0.1", + "markdownlint-cli2": "0.12.1", + "sass": "1.71.0", + "vitepress": "1.0.0-rc.43" + }, + "engines": { + "node": ">=20.11.0 <21 || >=21.2.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "dev": true, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz", + "integrity": "sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.20.0" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.20.0.tgz", + "integrity": "sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ==", + "dev": true + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz", + "integrity": "sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.20.0" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.20.0.tgz", + "integrity": "sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/client-search": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.20.0.tgz", + "integrity": "sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/client-search": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.20.0.tgz", + "integrity": "sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.20.0.tgz", + "integrity": "sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.20.0.tgz", + "integrity": "sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg==", + "dev": true, + "dependencies": { + "@algolia/client-common": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.20.0.tgz", + "integrity": "sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ==", + "dev": true + }, + "node_modules/@algolia/logger-console": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.20.0.tgz", + "integrity": "sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA==", + "dev": true, + "dependencies": { + "@algolia/logger-common": "4.20.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz", + "integrity": "sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.20.0" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.20.0.tgz", + "integrity": "sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng==", + "dev": true + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz", + "integrity": "sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng==", + "dev": true, + "dependencies": { + "@algolia/requester-common": "4.20.0" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.20.0.tgz", + "integrity": "sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg==", + "dev": true, + "dependencies": { + "@algolia/cache-common": "4.20.0", + "@algolia/logger-common": "4.20.0", + "@algolia/requester-common": "4.20.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", + "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", + "dev": true + }, + "node_modules/@docsearch/js": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", + "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "dev": true, + "dependencies": { + "@docsearch/react": "3.5.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", + "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.5.2", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", + "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", + "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.1.3.tgz", + "integrity": "sha512-1QWSvWcPbvZXsDxB1F7ejW+Kuxp3z/JHs944hp/f8BYOlFd5gplzseFIkE/GTu/qytFef3zNME4qw1oHbQ0j2A==", + "dev": true + }, + "node_modules/@shikijs/transformers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.1.3.tgz", + "integrity": "sha512-jv71dQFTucv2RK2pafAxca4hgKP6Uv5ukKrVjH/vGZ8jGH0j2AcLVCcM76ieamwJ1p5WkZcA0X/Bq2qpjhEUSg==", + "dev": true, + "dependencies": { + "shiki": "1.1.3" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "13.0.7", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", + "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", + "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/shared": "3.4.19", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", + "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.4.19", + "@vue/shared": "3.4.19" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz", + "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.23.9", + "@vue/compiler-core": "3.4.19", + "@vue/compiler-dom": "3.4.19", + "@vue/compiler-ssr": "3.4.19", + "@vue/shared": "3.4.19", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.6", + "postcss": "^8.4.33", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", + "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.19", + "@vue/shared": "3.4.19" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.0.14.tgz", + "integrity": "sha512-TluWR9qZ6aO11bwtYK8+fzXxBqLfsE0mWZz1q/EQBmO9k82Cm6deieLwNNXjNFJz7xutazoia5Qa+zTYkPPOfw==", + "dev": true, + "dependencies": { + "@vue/devtools-kit": "^7.0.14" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.0.14.tgz", + "integrity": "sha512-wAAJazr4hI0aVRpgWOCVPw+NzMQdthhnprHHIg4njp1MkKrpCNGQ7MtQbZF1AltAA7xpMCGyyt+0kYH0FqTiPg==", + "dev": true, + "dependencies": { + "@vue/devtools-schema": "^7.0.14", + "@vue/devtools-shared": "^7.0.14", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1" + } + }, + "node_modules/@vue/devtools-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-schema/-/devtools-schema-7.0.14.tgz", + "integrity": "sha512-tpUeCLVrdHX+KzWMLTAwx/vAPFbo6jAUi7sr6Q+0mBIqIVSSIxNr5wEhegiFvYva+OtDeM2OrT+f7/X/5bvZNg==", + "dev": true + }, + "node_modules/@vue/devtools-shared": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.0.14.tgz", + "integrity": "sha512-79RP1NDakBVWou9rDpVnT1WMjTbL1lJKm6YEOodjQ0dq5ehf0wsRbeYDhgAlnjehWRzTq5GAYFBFUPYBs0/QpA==", + "dev": true, + "dependencies": { + "rfdc": "^1.3.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz", + "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==", + "dev": true, + "dependencies": { + "@vue/shared": "3.4.19" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz", + "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.4.19", + "@vue/shared": "3.4.19" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz", + "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==", + "dev": true, + "dependencies": { + "@vue/runtime-core": "3.4.19", + "@vue/shared": "3.4.19", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz", + "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.4.19", + "@vue/shared": "3.4.19" + }, + "peerDependencies": { + "vue": "3.4.19" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", + "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==", + "dev": true + }, + "node_modules/@vueuse/core": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", + "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.7.2", + "@vueuse/shared": "10.7.2", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", + "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", + "dev": true, + "dependencies": { + "@vueuse/core": "10.7.2", + "@vueuse/shared": "10.7.2", + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "*", + "axios": "*", + "change-case": "*", + "drauu": "*", + "focus-trap": "*", + "fuse.js": "*", + "idb-keyval": "*", + "jwt-decode": "*", + "nprogress": "*", + "qrcode": "*", + "sortablejs": "*", + "universal-cookie": "*" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", + "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", + "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "dev": true, + "dependencies": { + "vue-demi": ">=0.14.6" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/algoliasearch": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.20.0.tgz", + "integrity": "sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g==", + "dev": true, + "dependencies": { + "@algolia/cache-browser-local-storage": "4.20.0", + "@algolia/cache-common": "4.20.0", + "@algolia/cache-in-memory": "4.20.0", + "@algolia/client-account": "4.20.0", + "@algolia/client-analytics": "4.20.0", + "@algolia/client-common": "4.20.0", + "@algolia/client-personalization": "4.20.0", + "@algolia/client-search": "4.20.0", + "@algolia/logger-common": "4.20.0", + "@algolia/logger-console": "4.20.0", + "@algolia/requester-browser-xhr": "4.20.0", + "@algolia/requester-common": "4.20.0", + "@algolia/requester-node-http": "4.20.0", + "@algolia/transporter": "4.20.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/deno-bin": { + "version": "1.40.5", + "resolved": "https://registry.npmjs.org/deno-bin/-/deno-bin-1.40.5.tgz", + "integrity": "sha512-iAiXs+twAp4EZiT9jUZ0J22Z3DfzGFbvnO1VGLP8canhQ/UJCBuUz0DHyQp6rDxZ67VaS/ouRWgVK8ELQWLCcw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "adm-zip": "^0.5.4" + }, + "bin": { + "deno": "bin/deno.js", + "deno-bin": "bin/deno.js" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/focus-trap": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "dev": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/lazy-lottie-player": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/lazy-lottie-player/-/lazy-lottie-player-0.0.1.tgz", + "integrity": "sha512-DSRUFZLZe+bnUFf+End4DZEwSqIxo0a71zr10m+7hCqAkvztgIYF/oX5a7HJWYnWipPn8gAqD78KTWTjPPcZLw==", + "dev": true, + "funding": { + "url": "https://PonomareVlad.ru/donate" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true + }, + "node_modules/markdown-it": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", + "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.33.0.tgz", + "integrity": "sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==", + "dev": true, + "dependencies": { + "markdown-it": "14.0.0", + "markdownlint-micromark": "0.1.8" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.12.1.tgz", + "integrity": "sha512-RcK+l5FjJEyrU3REhrThiEUXNK89dLYNJCYbvOUKypxqIGfkcgpz8g08EKqhrmUbYfYoLC5nEYQy53NhJSEtfQ==", + "dev": true, + "dependencies": { + "globby": "14.0.0", + "jsonc-parser": "3.2.0", + "markdownlint": "0.33.0", + "markdownlint-cli2-formatter-default": "0.0.4", + "micromatch": "4.0.5", + "yaml": "2.3.4" + }, + "bin": { + "markdownlint-cli2": "markdownlint-cli2.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli2-formatter-default": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.4.tgz", + "integrity": "sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==", + "dev": true, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" + } + }, + "node_modules/markdownlint-micromark": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.8.tgz", + "integrity": "sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minisearch": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", + "dev": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz", + "integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/search-insights": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.11.0.tgz", + "integrity": "sha512-Uin2J8Bpm3xaZi9Y8QibSys6uJOFZ+REMrf42v20AA3FUDUrshKkMEP6liJbMAHCm71wO6ls4mwAf7a3gFVxLw==", + "dev": true, + "peer": true + }, + "node_modules/shiki": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.1.3.tgz", + "integrity": "sha512-k/B4UvtWmGcHMLp6JnQminlex3Go5MHKXEiormmzTJECAiSQiwSon6USuwTyto8EMUQc9aYRJ7HojkfVLbBk+g==", + "dev": true, + "dependencies": { + "@shikijs/core": "1.1.3" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/uc.micro": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", + "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.3.tgz", + "integrity": "sha512-UfmUD36DKkqhi/F75RrxvPpry+9+tTkrXfMNZD+SboZqBCMsxKtO52XeGzzuh7ioz+Eo/SYDBbdb0Z7vgcDJew==", + "dev": true, + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.0.0-rc.43", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.43.tgz", + "integrity": "sha512-XZ9xaN76/LxCBqvk6U+3ne3T60JOavdOlk+FMQBlXYK/9pyyKGfjnEra4yKYvOdZdStoTg8VXTAj4wcsCTlJaQ==", + "dev": true, + "dependencies": { + "@docsearch/css": "^3.5.2", + "@docsearch/js": "^3.5.2", + "@shikijs/core": "^1.1.3", + "@shikijs/transformers": "^1.1.3", + "@types/markdown-it": "^13.0.7", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/devtools-api": "^7.0.14", + "@vueuse/core": "^10.7.2", + "@vueuse/integrations": "^10.7.2", + "focus-trap": "^7.5.4", + "mark.js": "8.11.1", + "minisearch": "^6.3.0", + "shiki": "^1.1.3", + "vite": "^5.1.3", + "vue": "^3.4.19" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4.3.2", + "postcss": "^8.4.35" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", + "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.4.19", + "@vue/compiler-sfc": "3.4.19", + "@vue/runtime-dom": "3.4.19", + "@vue/server-renderer": "3.4.19", + "@vue/shared": "3.4.19" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + } + } } From 8ef037b8472a84cc75f4016eace4a7451118e69e Mon Sep 17 00:00:00 2001 From: Roz Date: Sat, 27 Apr 2024 17:03:37 -0300 Subject: [PATCH 04/59] chore: deno fmt --- site/docs/plugins/commands.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index a5d9c0e12..63e4beadd 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -98,11 +98,13 @@ const myCommands = new Commands(); myCommands .command("start", "Initializes bot configuration") - .addToScope({ type: "all_private_chats" }, (ctx) => - ctx.reply(`Hello, ${ctx.chat.first_name}!`) + .addToScope( + { type: "all_private_chats" }, + (ctx) => ctx.reply(`Hello, ${ctx.chat.first_name}!`), ) - .addToScope({ type: "all_group_chats" }, (ctx) => - ctx.reply(`Hello, members of ${ctx.chat.title}!`) + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply(`Hello, members of ${ctx.chat.title}!`), ); ``` @@ -137,7 +139,7 @@ loggedOutCommands.command( async (ctx) => { await ctx.setMyCommands(loggedInCommands); await ctx.reply("Welcome! Session started!"); - } + }, ); loggedInCommands.command( @@ -146,7 +148,7 @@ loggedInCommands.command( async (ctx) => { await ctx.setMyCommands(loggedOutCommands); await ctx.reply("Goodbye :)"); - } + }, ); bot.use(loggedInCommands); @@ -170,7 +172,7 @@ loggedOutCommands.command( async (ctx) => { await ctx.setMyCommands(loggedInCommands); await ctx.reply("Welcome! Session started!"); - } + }, ); loggedInCommands.command( @@ -179,7 +181,7 @@ loggedInCommands.command( async (ctx) => { await ctx.setMyCommands(loggedOutCommands); await ctx.reply("Goodbye :)"); - } + }, ); bot.use(loggedInCommands); @@ -243,10 +245,11 @@ bot const suggestedCommand = ctx.getNearestCommand(myCommands); // We found a potential match - if (suggestedCommand) + if (suggestedCommand) { return ctx.reply( - `Hm... I don't know that command. Did you mean ${suggestedCommand}?` + `Hm... I don't know that command. Did you mean ${suggestedCommand}?`, ); + } // Nothing seems to come close to what the user typed await ctx.reply("Ops... I don't know that command :/"); @@ -270,10 +273,11 @@ bot const suggestedCommand = ctx.getNearestCommand(myCommands); // We found a potential match - if (suggestedCommand) + if (suggestedCommand) { return ctx.reply( - `Hm... I don't know that command. Did you mean ${suggestedCommand}?` + `Hm... I don't know that command. Did you mean ${suggestedCommand}?`, ); + } // Nothing seems to come close to what the user typed await ctx.reply("Ops... I don't know that command :/"); From baad642aff7b8cadcd49e1c9a6f45b1dd8509530 Mon Sep 17 00:00:00 2001 From: Roz Date: Sat, 27 Apr 2024 17:23:54 -0300 Subject: [PATCH 05/59] fix: broken link --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 63e4beadd..96eb91ae6 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -317,4 +317,4 @@ All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest - Name: `commands` - [Source](https://github.com/grammyjs/commands) -- [Reference](/ref/commands) +- [Reference](/ref/commands/) From 1901b7a8d4349d3fe00a05262c325a3c4c23578b Mon Sep 17 00:00:00 2001 From: Roz Date: Sat, 27 Apr 2024 17:29:38 -0300 Subject: [PATCH 06/59] feat: add commands plugin page to navigation menu --- site/docs/.vitepress/configs/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/site/docs/.vitepress/configs/locales/en.ts b/site/docs/.vitepress/configs/locales/en.ts index 24e944144..79aa292b2 100644 --- a/site/docs/.vitepress/configs/locales/en.ts +++ b/site/docs/.vitepress/configs/locales/en.ts @@ -243,6 +243,12 @@ const pluginOfficial = { // do not add the following line to translations: activeMatch: "^(/plugins/chat-members|/ref/chat-members/)$", }, + { + text: "Commands (commands)", + link: "/plugins/commands", + // do not add the following line to translations: + activeMatch: "^(/plugins/commands|/ref/commands/)$", + }, ], }; From cd12907ccda07c726cd8dac020d46a7b23c5f094 Mon Sep 17 00:00:00 2001 From: Qz Date: Sun, 28 Apr 2024 10:34:56 +0700 Subject: [PATCH 07/59] Add grammy_commands to modules.json --- site/docs/.vitepress/plugins/current-versions/modules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/docs/.vitepress/plugins/current-versions/modules.json b/site/docs/.vitepress/plugins/current-versions/modules.json index 31c076fa9..e065fa19c 100644 --- a/site/docs/.vitepress/plugins/current-versions/modules.json +++ b/site/docs/.vitepress/plugins/current-versions/modules.json @@ -14,6 +14,7 @@ "grammy_storages", "grammy_conversations", "grammy_autoquote", - "grammy_i18n" + "grammy_i18n", + "grammy_commands" ] } From 3cc79c619eba8f551543179ada284b9ac6fa2f95 Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:28:27 -0300 Subject: [PATCH 08/59] Apply suggestions from code review Co-authored-by: Qz --- site/docs/plugins/commands.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 96eb91ae6..878fcefd2 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -71,8 +71,8 @@ Once you defined your commands with an instance of the `Commands` class, you can ```js const myCommands = new Commands() -myCommands.command("hello", "say hello", (ctx) => ctx.reply("Hi there!")) -myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting..."))\ +myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hi there!")) +myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting...")); bot.use(myCommands) From 8e784226a5572e5f3008b2aebd25f82abbd37e61 Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:29:01 -0300 Subject: [PATCH 09/59] Apply suggestions from code review Co-authored-by: Qz --- site/docs/plugins/commands.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 878fcefd2..99e851be7 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -34,7 +34,7 @@ myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); bot.use(myCommands); ``` -This registers a new "/start" command to your bot that will be handled by the given middleware. +This registers a new `/start` command to your bot that will be handled by the given middleware. Now, let's get into some of the extra tools this plugin has to offer. @@ -108,14 +108,14 @@ myCommands ); ``` -The "start" command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. -Now if you call `myCommands.setCommands`, the "start" command will be registered to both private and group chats. +The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. +Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. -### Context shortcut +### Context Shortcut What if you want some commands to be displayed only for certain users. -For example, imagine you have a "login" and a "logout" command. -The "login" command should only appear for logged out users, and vice versa. +For example, imagine you have a `login` and a `logout` command. +The `login` command should only appear for logged out users, and vice versa. This is how you can do that with the Commands plugin: ::: code-group @@ -154,7 +154,7 @@ loggedInCommands.command( bot.use(loggedInCommands); bot.use(loggedOutCommands); -// by default, users are not logged in, +// By default, users are not logged in, // so you can set the logged out commands for everyone await loggedOutCommands.setCommands(bot); ``` @@ -187,14 +187,14 @@ loggedInCommands.command( bot.use(loggedInCommands); bot.use(loggedOutCommands); -// by default, users are not logged in, +// By default, users are not logged in, // so you can set the logged out commands for everyone await loggedOutCommands.setCommands(bot); ``` ::: -This way when a user calls `/login`, they'll have their commands list changed to contain only the "logout" command. +This way when a user calls `/login`, they'll have their commands list changed to contain only the `logout` command. Neat, right? ## Command Translations @@ -247,12 +247,12 @@ bot // We found a potential match if (suggestedCommand) { return ctx.reply( - `Hm... I don't know that command. Did you mean ${suggestedCommand}?`, + `Hmm... I don't know that command. Did you mean ${suggestedCommand}?`, ); } // Nothing seems to come close to what the user typed - await ctx.reply("Ops... I don't know that command :/"); + await ctx.reply("Oops... I don't know that command :/"); }); ``` @@ -275,12 +275,12 @@ bot // We found a potential match if (suggestedCommand) { return ctx.reply( - `Hm... I don't know that command. Did you mean ${suggestedCommand}?`, + `Hmm... I don't know that command. Did you mean ${suggestedCommand}?`, ); } // Nothing seems to come close to what the user typed - await ctx.reply("Ops... I don't know that command :/"); + await ctx.reply("Oops... I don't know that command :/"); }); ``` @@ -305,11 +305,11 @@ With it you can coose between three different behaviors: Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). In some occasions, you might want to change that and use a custom prefix for your bot. -That is made possible by the `prefix` option, which will tell the Commands plugin to look for that prefix when trying to identify a command +That is made possible by the `prefix` option, which will tell the Commands plugin to look for that prefix when trying to identify a command. ### `matchOnlyAtStart` -When [handling commands](../guide/commands.md), the grammY core library will only recognize commands that start on the first carachter of a message. +When [handling commands](../guide/commands), the grammY core library will only recognize commands that start on the first character of a message. The Commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. From c5b62b8e8c8e9f2659b0c0bfc9fcc91108cc4b8a Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Mon, 17 Jun 2024 23:58:39 -0400 Subject: [PATCH 10/59] - restructure read flow to focus first on a full working example - add examples on command scoping - add examples on command grouping - add regExp section - add reference to telegram docs on commands - add mention to languageCode obj utility - typos --- site/docs/plugins/commands.md | 266 ++++++++++++++++++++++++++++------ 1 file changed, 220 insertions(+), 46 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 99e851be7..a3987dce6 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -11,13 +11,14 @@ This plugin provides various features related to command handling that are not c Here is a quick overview of what you get with this plugin: - Better code structure by attaching middleware to command definitions -- Automatic synchronization via `setMyCommands` -- Command scope handling +- User command menu synchronization via `setMyCommands` +- Improved command organization with the ability to group them into different Commands instances +- Ability to scope command reach, e.g: only accessible to group admins or channels, etc - Defining command translations - "Did you mean ...?" feature that finds the nearest existing command -- Custom command prefixes -- Specify what to do with commands that mention your bot's user -- Support for commands that are not in the beggining of the message +- Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` +- Set custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` +- Support for commands that are not in the beginning of the message - RegEx support for command names All of these features are made possible because you will define one or more central command structures that define your bot's commands. @@ -69,48 +70,18 @@ Now that that's settled, let's see how we can make our commands visible to our u Once you defined your commands with an instance of the `Commands` class, you can call the `setCommands` method, which will register all the defined commands to your bot. ```js -const myCommands = new Commands() +const myCommands = new Commands(); -myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hi there!")) +myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hi there!")); myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting...")); -bot.use(myCommands) +bot.use(myCommands); -await myCommands.setCommands(bot) +await myCommands.setCommands(bot); ``` This will make it so every command you registered is displayed on the menu of a private chat with your bot, or whenever users type `/` on a chat your bot is a member of. -## Scoped Commands - -Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? -That's what Telegram call **Command Scopes**. - -The `Command` class returned by the `command` method exposes a method called `addToScope`. -This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. - -You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. - -Here's an example of a scoped command: - -```js -const myCommands = new Commands(); - -myCommands - .command("start", "Initializes bot configuration") - .addToScope( - { type: "all_private_chats" }, - (ctx) => ctx.reply(`Hello, ${ctx.chat.first_name}!`), - ) - .addToScope( - { type: "all_group_chats" }, - (ctx) => ctx.reply(`Hello, members of ${ctx.chat.title}!`), - ); -``` - -The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. -Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. - ### Context Shortcut What if you want some commands to be displayed only for certain users. @@ -194,12 +165,90 @@ await loggedOutCommands.setCommands(bot); ::: -This way when a user calls `/login`, they'll have their commands list changed to contain only the `logout` command. -Neat, right? +This way when a user calls `/login`, they'll have their commands list changed to contain only the `logout` command. Neat, right? + +If you want to prevent, for example, the commands contained in `loggedInCommands` from being callable after the user called `/logout`, you must implement it in your handlers with your own business logic. + +Be aware that `SetMyCommands` only affects the commands displayed in the user's commands menu. + +It's is also possible to stack commands instances into `SetMyCommands` + +```js +userCommands.command("start", "Init bot", async (ctx) => { + await ctx.setMyCommands(userCommands); +}); +adminCommands.command("admin", "Give me the power!", async (ctx) => { + // valid + await ctx.setMyCommands(userCommands, adminCommands); + // also valid + await ctx.setMyCommands([userCommands, adminCommands, hiddenCommands]); +}); +``` + +You will learn how to implement restricted command access, like the above but not just for displayed commands, in the next section. + +## Scoped Commands + +Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? +That's what Telegram call **Command Scopes**. + +The `Command` class returned by the `command` method exposes a method called `addToScope`. +This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. + +You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. + +Here's an example of a scoped command: + +```js +const myCommands = new Commands(); + +myCommands + .command("start", "Initializes bot configuration") + .addToScope( + { type: "all_private_chats" }, + (ctx) => ctx.reply(`Hello, ${ctx.chat.first_name}!`), + ) + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply(`Hello, members of ${ctx.chat.title}!`), + ); +``` + +The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. +Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. + +Heres an example of a command that it's only accesible to group admins + +```js +adminCommands + .command("secret", "Admin only") + .addToScope({ + type: "all_chat_administrators", + }, async (c) => { + await c.reply("Free cake!"); + }); +``` + +Note: if you only want a command to be accesible on certain scopes, make sure you do not add a handler in the first `MyCommands.command` call. Doing that will automatically add it to all private chats, including groups. + +Here is an example of a command that it's only accesible in groups + +```js +myCommands + .command( + "fun", + "Laugh", + /** skip this handler */ + ).addToScope({ + type: "all_group_chats", + }, async (ctx) => { + await ctx.reply("Haha"); + }); +``` ## Command Translations -Another extremely useful possibility is setting commands to have different names and descriptions based on the user language. +Another powerful feature is the ability to set different names for the same command, and their respective descriptions based on the user language. The Commands plugin makes that easy by providing the `localize` method. Check it out: @@ -214,11 +263,76 @@ myCommands Add as many as you want! The plugin will take care of registering them for you when you call `myCommands.setCommands`. +For convenience the types package exports a `LanguageCodes` enum-like object, that you can use for a more idiomatic approach: + +::: code-group + +```ts [TypeScript] +import { LanguageCodes } from "@grammyjs/types"; + +myCommands.command( + "chef", + "Steak delivery", + async (ctx) => await ctx.reply("Steak on the plate!"), +).localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + async (ctx) => await ctx.reply("Bife al plato!"), +).localize( + LanguageCodes.Croatian, + "kuhar", + "Dostava bifteka", + async (ctx) => await ctx.reply("Odrezak na tanjuru!"), +); +``` + +```js [JavaScript] +const { LanguageCodes } = require("@grammyjs/types"); + +myCommands.command( + "chef", + "Steak delivery", + async (ctx) => await ctx.reply("Steak on the plate!"), +).localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + async (ctx) => await ctx.reply("Bife al plato!"), +).localize( + LanguageCodes.Croatian, + "kuhar", + "Dostava bifteka", + async (ctx) => await ctx.reply("Odrezak na tanjuru!"), +); +``` + +```ts [Deno] +import { LanguageCodes } from "https://deno.land/x/grammy_types/mod.ts"; + +myCommands.command( + "chef", + "Steak delivery", + async (ctx) => await ctx.reply("Steak on the plate!"), +).localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + async (ctx) => await ctx.reply("Bife al plato!"), +).localize( + LanguageCodes.Croatian, + "kuhar", + "Dostava bifteka", + async (ctx) => await ctx.reply("Odrezak na tanjuru!"), +); +``` + +::: + ## Finding the Nearest Command -Even though Telegram it capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. -The Commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. -Usage is quite straight-forward: +Even though Telegram it's capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. +The Commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. It is compatible with custom prefixes, so you don't have to worry about that, and it's usage is quite straight-forward: ::: code-group @@ -286,6 +400,49 @@ bot ::: +By default the `getNearestCommand` method will prioritize commands that correspond to the user language, if you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. + +It is possible to search across multiple Commands instances, and the `getNearestCommand` method will return the most similar command across them. + +Heres is an example demonstrating both things: + +```js +bot.use(commands()); + +const myCommands = new Commands(); +myCommands.command("dad", "_", () => {}) + .localize("es", "papa", "_") + .localize("fr", "pere", "_"); + +const otherCommands = new Commands(); +otherCommands.command("bread", "_", () => {}) + .localize("es", "pan", "_") + .localize("fr", "pain", "_"); + +bot.use(myCommands); +bot.use(otherCommands); + +// let says the user is french and typed '/papi' +bot + .filter(Context.has.filterQuery("::bot_command")) + .use(async (ctx) => { + const suggestedCommandLocal = ctx.getNearestCommand([ + myCommands, + otherCommands, + ]); + suggestedCommandLocal === "/pain"; // true + + const suggestedCommandGlobal = ctx.getNearestCommand([ + myCommands, + otherCommands, + ], { ignoreLocalization: true }); + + suggestedCommandGlobal === "/papa"; // true + }); +``` + +It also allows to set the `ignoreCase` flag, which is self-explanatory, and the `similarityThreshold` flag, which controls how similar a command name has to be in comparison to the user input for it to be recommended. + ## Command Options There are a few options that can be specified per command, per scope, or globally for a `Commands` instance. @@ -295,7 +452,7 @@ These options allow you to further customize how your bot handles commands, givi When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. -With it you can coose between three different behaviors: +With it you can choose between three different behaviors: - `ignored`: Ignores commands that mention your bot's user - `optional`: Handles both commands that do and that don't mention the bot's user @@ -313,8 +470,25 @@ When [handling commands](../guide/commands), the grammY core library will only r The Commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. +## RegExp Commands + +This feature is really for those ones who are really looking to go wild, it allows you to create command handlers based on Regular Expressions instead of static strings, a basic example would look like: + +```js +myCommands + .command( + /delete_([a-zA-Z]{1,})/, + (ctx) => ctx.reply(`Deleting ${ctx.message?.text?.split("_")[1]}`), + ); +``` + +This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply `Deleting me` in the first case and `Deleting you` in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing trough as if it was no there. + +You can use custom prefixes and localize them as usual. + ## Plugin Summary - Name: `commands` - [Source](https://github.com/grammyjs/commands) - [Reference](/ref/commands/) +- [Telegram Docs commands reference](https://core.telegram.org/bots/features#commands) From 1fd817d02db1bd8d7588a315c5aa8092c00083ed Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Tue, 18 Jun 2024 00:10:26 -0400 Subject: [PATCH 11/59] - reduce example length about LanguageCode utility obj --- site/docs/plugins/commands.md | 54 ++++++++++++++--------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index a3987dce6..e9ecf095c 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -274,17 +274,13 @@ myCommands.command( "chef", "Steak delivery", async (ctx) => await ctx.reply("Steak on the plate!"), -).localize( - LanguageCodes.Spanish, - "cocinero", - "Bife a domicilio", - async (ctx) => await ctx.reply("Bife al plato!"), -).localize( - LanguageCodes.Croatian, - "kuhar", - "Dostava bifteka", - async (ctx) => await ctx.reply("Odrezak na tanjuru!"), -); +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + async (ctx) => await ctx.reply("Bife al plato!"), + ); ``` ```js [JavaScript] @@ -294,17 +290,13 @@ myCommands.command( "chef", "Steak delivery", async (ctx) => await ctx.reply("Steak on the plate!"), -).localize( - LanguageCodes.Spanish, - "cocinero", - "Bife a domicilio", - async (ctx) => await ctx.reply("Bife al plato!"), -).localize( - LanguageCodes.Croatian, - "kuhar", - "Dostava bifteka", - async (ctx) => await ctx.reply("Odrezak na tanjuru!"), -); +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + async (ctx) => await ctx.reply("Bife al plato!"), + ); ``` ```ts [Deno] @@ -314,17 +306,13 @@ myCommands.command( "chef", "Steak delivery", async (ctx) => await ctx.reply("Steak on the plate!"), -).localize( - LanguageCodes.Spanish, - "cocinero", - "Bife a domicilio", - async (ctx) => await ctx.reply("Bife al plato!"), -).localize( - LanguageCodes.Croatian, - "kuhar", - "Dostava bifteka", - async (ctx) => await ctx.reply("Odrezak na tanjuru!"), -); +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + async (ctx) => await ctx.reply("Bife al plato!"), + ); ``` ::: From fb7ee75f417648ecf3e619141d4b951ed51e0046 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Tue, 18 Jun 2024 14:08:16 -0400 Subject: [PATCH 12/59] - add note on upperCased command names - remove wrong code on localization example --- site/docs/plugins/commands.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index e9ecf095c..c72bc47de 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -84,7 +84,7 @@ This will make it so every command you registered is displayed on the menu of a ### Context Shortcut -What if you want some commands to be displayed only for certain users. +What if you want some commands to be displayed only to certain users. For example, imagine you have a `login` and a `logout` command. The `login` command should only appear for logged out users, and vice versa. This is how you can do that with the Commands plugin: @@ -169,7 +169,15 @@ This way when a user calls `/login`, they'll have their commands list changed to If you want to prevent, for example, the commands contained in `loggedInCommands` from being callable after the user called `/logout`, you must implement it in your handlers with your own business logic. -Be aware that `SetMyCommands` only affects the commands displayed in the user's commands menu. +::: danger +As stated in the [Telegram API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: + +> 1-32 characters. Can contain only lowercase English letters, digits and underscores. + +Therefore calling `setCommands` or `setMyCommands` on upperCased commands will throw an exception. They can still be registered and used, but will never be displayed on the user menu as such. + +**Setting UpperCased command names is heavily discourage** +::: It's is also possible to stack commands instances into `SetMyCommands` @@ -185,7 +193,7 @@ adminCommands.command("admin", "Give me the power!", async (ctx) => { }); ``` -You will learn how to implement restricted command access, like the above but not just for displayed commands, in the next section. +**Be aware** that `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the next section. ## Scoped Commands @@ -224,8 +232,8 @@ adminCommands .command("secret", "Admin only") .addToScope({ type: "all_chat_administrators", - }, async (c) => { - await c.reply("Free cake!"); + }, async (ctx) => { + await ctx.reply("Free cake!"); }); ``` @@ -279,7 +287,6 @@ myCommands.command( LanguageCodes.Spanish, "cocinero", "Bife a domicilio", - async (ctx) => await ctx.reply("Bife al plato!"), ); ``` @@ -295,7 +302,6 @@ myCommands.command( LanguageCodes.Spanish, "cocinero", "Bife a domicilio", - async (ctx) => await ctx.reply("Bife al plato!"), ); ``` @@ -311,7 +317,6 @@ myCommands.command( LanguageCodes.Spanish, "cocinero", "Bife a domicilio", - async (ctx) => await ctx.reply("Bife al plato!"), ); ``` From 5099a4a53f0dd6a9653da9538c62dc19cd65ea9a Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 20 Jun 2024 12:45:20 -0400 Subject: [PATCH 13/59] - add section for improving file structure related to command organization --- site/docs/plugins/commands.md | 99 +++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index c72bc47de..e5c027091 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -10,16 +10,16 @@ Command handling on steroids. This plugin provides various features related to command handling that are not contained in the [command handling done by the grammY core library](../guide/commands). Here is a quick overview of what you get with this plugin: -- Better code structure by attaching middleware to command definitions +- Better code readability by encapsulating middleware with command definitions - User command menu synchronization via `setMyCommands` -- Improved command organization with the ability to group them into different Commands instances +- Improved command grouping and organization - Ability to scope command reach, e.g: only accessible to group admins or channels, etc - Defining command translations -- "Did you mean ...?" feature that finds the nearest existing command -- Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` +- "Did you mean ...?" feature that finds the nearest existing command to a given user miss-input - Set custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` +- Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` - Support for commands that are not in the beginning of the message -- RegEx support for command names +- RegExp Commands! All of these features are made possible because you will define one or more central command structures that define your bot's commands. @@ -65,7 +65,7 @@ import { Now that that's settled, let's see how we can make our commands visible to our users. -## Automatic Command Setting +## User Command Menu Setting Once you defined your commands with an instance of the `Commands` class, you can call the `setCommands` method, which will register all the defined commands to your bot. @@ -179,21 +179,88 @@ Therefore calling `setCommands` or `setMyCommands` on upperCased commands will t **Setting UpperCased command names is heavily discourage** ::: -It's is also possible to stack commands instances into `SetMyCommands` +**Be aware** that `SetCommands` and `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the [Scoped Commands](#scoped-commands) section. -```js -userCommands.command("start", "Init bot", async (ctx) => { +### Grouping Commands + +Since we can split and group our commands into different instances, it allows for a much more idiomatic command organization. + +Let say we want to have developer only commands and we have the following code structure in Typescript: + +```ascii +src/ +├─ commands/ +│ ├─ users.ts +│ ├─ admin.ts +├─ bot.ts +├─ types.d.ts +tsconfig.json +``` + +::: tip +We are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every necessary import. +::: + +With the following code, we should have functional developer only command group that is also shown in the Telegram client Command menu. Make sure you inspect the `admin.ts` and `user.ts` file-tabs. + +::: code-group + +```ts [bot.ts] +import { devCommands } from "./commands/admin.ts"; +import { userCommands } from "./commands/users.ts"; + +export const bot = new Bot("MyBotToken"); + +bot.use(commands()); + +bot.use(userCommands); +bot.use(devCommands); +``` + +```ts [admin.ts] +import { userCommands } from './users.ts' + +export const devCommands = new Commands() + +devCommands.command('devlogin', 'Greetings', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Hi to me') + await ctx.setMyCommands(userCommands, devCommands) + } else next() +}) + +devCommands.command('usercount', 'Greetings', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply( + `Active users: ${/** Your business logic */}` + ) + } else next() +}) + +devCommands.command('devlogout', 'Greetings', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Bye to me') + await ctx.setMyCommands(userCommands) + } else next() + }) +``` + +```ts [users.ts] +export const userCommands = new Commands(); + +userCommands.command("start", "Greetings", async (ctx) => { + await ctx.reply("Hello user"); await ctx.setMyCommands(userCommands); }); -adminCommands.command("admin", "Give me the power!", async (ctx) => { - // valid - await ctx.setMyCommands(userCommands, adminCommands); - // also valid - await ctx.setMyCommands([userCommands, adminCommands, hiddenCommands]); -}); ``` -**Be aware** that `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the next section. +```ts [types.d.ts] +type MyContext = Context & CommandsFlavor; +``` + +::: + +Combining this knowledge with the following section will get your Command-game to the next level. ## Scoped Commands From 4e3c5c7b122c7c1a62d5b43a3611b7c0bcc299ba Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 20 Jun 2024 13:07:50 -0400 Subject: [PATCH 14/59] remove repetitio --- site/docs/plugins/commands.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index e5c027091..55b90b48a 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -183,9 +183,9 @@ Therefore calling `setCommands` or `setMyCommands` on upperCased commands will t ### Grouping Commands -Since we can split and group our commands into different instances, it allows for a much more idiomatic command organization. +Since we can split and group our commands into different instances, it allows for a much more idiomatic command file organization. -Let say we want to have developer only commands and we have the following code structure in Typescript: +Let say we want to have developer-only commands. We can achieve that with the the following code structure: ```ascii src/ @@ -532,7 +532,7 @@ All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest ## RegExp Commands -This feature is really for those ones who are really looking to go wild, it allows you to create command handlers based on Regular Expressions instead of static strings, a basic example would look like: +This feature is for those who are really looking to go wild, it allows you to create command handlers based on Regular Expressions instead of static strings, a basic example would look like: ```js myCommands From 67b0b0b3409b4d6641ad9b507e7414b29dd0d98d Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Sat, 22 Jun 2024 21:01:15 -0400 Subject: [PATCH 15/59] change txt to tip --- site/docs/plugins/commands.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 55b90b48a..4ab6822a5 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -201,7 +201,7 @@ tsconfig.json We are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every necessary import. ::: -With the following code, we should have functional developer only command group that is also shown in the Telegram client Command menu. Make sure you inspect the `admin.ts` and `user.ts` file-tabs. +The following code group exemplify how we could implement a developer only command group, and update our Telegram client Command menu. Make sure you inspect the `admin.ts` and `user.ts` file-tabs. ::: code-group @@ -304,7 +304,9 @@ adminCommands }); ``` -Note: if you only want a command to be accesible on certain scopes, make sure you do not add a handler in the first `MyCommands.command` call. Doing that will automatically add it to all private chats, including groups. +::: tip +If you only want a command to be accesible on certain scopes, make sure you do not add a handler in the first `MyCommands.command` call. Doing that will automatically add it to all private chats, including groups. +::: Here is an example of a command that it's only accesible in groups From 1280c04241178efb1689b2795445ef016b2b8ed6 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Wed, 24 Jul 2024 19:05:57 -0400 Subject: [PATCH 16/59] - update Commands class name onto CommandGroup - add ignoreCase option - update `did you mean` section to use the `commandNotFound` helper function - remove backticks on command options for better in-document linking --- site/docs/plugins/commands.md | 149 ++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 68 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 4ab6822a5..4e6e0956f 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -15,7 +15,8 @@ Here is a quick overview of what you get with this plugin: - Improved command grouping and organization - Ability to scope command reach, e.g: only accessible to group admins or channels, etc - Defining command translations -- "Did you mean ...?" feature that finds the nearest existing command to a given user miss-input +- `Did you mean ...?` feature that finds the nearest existing command to a given user miss-input +- Allow for commands to match in a case-insensitive manner - Set custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` - Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` - Support for commands that are not in the beginning of the message @@ -28,7 +29,7 @@ All of these features are made possible because you will define one or more cent Before we dive in, take a look at how you can register and handle a command with the plugin: ```js -const myCommands = new Commands(); +const myCommands = new CommandGroup(); myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); @@ -41,21 +42,21 @@ Now, let's get into some of the extra tools this plugin has to offer. ## Importing -First of all, we need to import the `Commands` class. +First of all, we need to import the `CommandGroup` class. ::: code-group ```ts [TypeScript] -import { Commands, commands, type CommandsFlavor } from "@grammyjs/commands"; +import { CommandGroup commands, type CommandsFlavor } from "@grammyjs/commands"; ``` ```js [JavaScript] -const { Commands, commands } = require("@grammyjs/commands"); +const { CommandGroup commands } = require("@grammyjs/commands"); ``` ```ts [Deno] import { - Commands, + CommandGroup commands, type CommandsFlavor, } from "https://deno.land/x/grammy_commands/mod.ts"; @@ -67,10 +68,10 @@ Now that that's settled, let's see how we can make our commands visible to our u ## User Command Menu Setting -Once you defined your commands with an instance of the `Commands` class, you can call the `setCommands` method, which will register all the defined commands to your bot. +Once you defined your commands with an instance of the `CommandGroup` class, you can call the `setCommands` method, which will register all the defined commands to your bot. ```js -const myCommands = new Commands(); +const myCommands = new CommandGroup(); myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hi there!")); myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting...")); @@ -101,8 +102,8 @@ const bot = new Bot("token"); // Register the context shortcut bot.use(commands()); -const loggedOutCommands = new Commands(); -const loggedInCommands = new Commands(); +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); loggedOutCommands.command( "login", @@ -134,8 +135,8 @@ await loggedOutCommands.setCommands(bot); // Register the context shortcut bot.use(commands()); -const loggedOutCommands = new Commands(); -const loggedInCommands = new Commands(); +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); loggedOutCommands.command( "login", @@ -174,9 +175,7 @@ As stated in the [Telegram API documentation](https://core.telegram.org/bots/api > 1-32 characters. Can contain only lowercase English letters, digits and underscores. -Therefore calling `setCommands` or `setMyCommands` on upperCased commands will throw an exception. They can still be registered and used, but will never be displayed on the user menu as such. - -**Setting UpperCased command names is heavily discourage** +Therefore calling `setCommands` or `setMyCommands` with anything but lower_case-commands will throw an exception. Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. ::: **Be aware** that `SetCommands` and `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the [Scoped Commands](#scoped-commands) section. @@ -220,7 +219,7 @@ bot.use(devCommands); ```ts [admin.ts] import { userCommands } from './users.ts' -export const devCommands = new Commands() +export const devCommands = new CommandGroup() devCommands.command('devlogin', 'Greetings', async (ctx, next) => { if (ctx.from?.id === ctx.env.DEVELOPER_ID) { @@ -246,7 +245,7 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { ``` ```ts [users.ts] -export const userCommands = new Commands(); +export const userCommands = new CommandGroup(); userCommands.command("start", "Greetings", async (ctx) => { await ctx.reply("Hello user"); @@ -275,7 +274,7 @@ You don't even need to worry about calling `filter`, the `addToScope` method wil Here's an example of a scoped command: ```js -const myCommands = new Commands(); +const myCommands = new CommandGroup(); myCommands .command("start", "Initializes bot configuration") @@ -399,31 +398,30 @@ The Commands plugin helps you deal with that by allowing you to suggest a comman ::: code-group ```ts [TypeScript] +import { commandNotFound } from "@grammyjs/commands"; + // Use the flavor to create a custom context type MyContext = Context & CommandsFlavor; // Use the new context to instantiate your bot const bot = new Bot("token"); -// Register the context shortcut +// Register the plugin bot.use(commands()); -const myCommands = new Commands(); +const myCommands = new CommandGroup(); // ... Register the commands bot // Check if there is a command - .filter(Context.has.filterQuery("::bot_command")) + .filter(commandNotFound(myCommands)) // If so, that means it wasn't handled by any of our commands. - // Let's try and guess what the user meant. .use(async (ctx) => { - const suggestedCommand = ctx.getNearestCommand(myCommands); - // We found a potential match - if (suggestedCommand) { - return ctx.reply( - `Hmm... I don't know that command. Did you mean ${suggestedCommand}?`, + if (ctx.commandSuggestion) { + await ctx.reply( + `Hmm... I don't know that command. Did you mean ${ctx.commandSuggestion}?`, ); } @@ -436,81 +434,90 @@ bot // Register the context shortcut bot.use(commands()); -const myCommands = new Commands(); +const myCommands = new CommandGroup(); // ... Register the commands + bot // Check if there is a command - .filter(Context.has.filterQuery("::bot_command")) + .filter(commandNotFound(myCommands)) // If so, that means it wasn't handled by any of our commands. - // Let's try and guess what the user meant. .use(async (ctx) => { - const suggestedCommand = ctx.getNearestCommand(myCommands); - // We found a potential match - if (suggestedCommand) { - return ctx.reply( - `Hmm... I don't know that command. Did you mean ${suggestedCommand}?`, + if (ctx.commandSuggestion) { + await ctx.reply( + `Hmm... I don't know that command. Did you mean ${ctx.commandSuggestion}?`, ); } // Nothing seems to come close to what the user typed await ctx.reply("Oops... I don't know that command :/"); }); + ``` ::: -By default the `getNearestCommand` method will prioritize commands that correspond to the user language, if you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. +Behind the scenes `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language, if you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. + +It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. + +It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command, and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. -It is possible to search across multiple Commands instances, and the `getNearestCommand` method will return the most similar command across them. +The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. For example, if you only have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it trigger the handle for anything that looks like your commands, e.g: `?sayhi` or `supercustomhi`, but no `/definitely-a-command`. Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that looks like `/regular /commands`. -Heres is an example demonstrating both things: +The recommended commands will only come from the commandGroup instances you pass to the function. So you could defer the checks into multiple, separate filters. + +Let's use the previous knowledge to inspect the next example: ```js -bot.use(commands()); -const myCommands = new Commands(); -myCommands.command("dad", "_", () => {}) - .localize("es", "papa", "_") - .localize("fr", "pere", "_"); +const myCommands = new CommandGroup; +myCommands.command("dad", "calls dad", () => {}, { prefix: '?'}) + .localize("es", "papa", "llama a papa") + .localize("fr", "pere", "appelle papa"); -const otherCommands = new Commands(); -otherCommands.command("bread", "_", () => {}) - .localize("es", "pan", "_") - .localize("fr", "pain", "_"); +const otherCommands = new CommandGroup(); +otherCommands.command("bread", "eat a toast", () => {}) + .localize("es", "pan", "come un pan") + .localize("fr", "pain", "manger du pain"); -bot.use(myCommands); -bot.use(otherCommands); +// Register Each -// let says the user is french and typed '/papi' +// Let's assume the user is french and typed /Papi bot - .filter(Context.has.filterQuery("::bot_command")) + // this filter will trigger for any command-like as '/regular' or '?custom' + .filter(commandNotFound([myCommands, otherCommands], { + ignoreLocalization: true, + ignoreCase: true, + })) .use(async (ctx) => { - const suggestedCommandLocal = ctx.getNearestCommand([ - myCommands, - otherCommands, - ]); - suggestedCommandLocal === "/pain"; // true - - const suggestedCommandGlobal = ctx.getNearestCommand([ - myCommands, - otherCommands, - ], { ignoreLocalization: true }); - - suggestedCommandGlobal === "/papa"; // true + ctx.commandSuggestion === "?papa" // evaluates to true + + /* if the ignoreLocalization was falsy instead + * we would have gotten: + * ctx.commandSuggestion equals "/pain" + */ }); + + /* We could add more filters like the above, + * with different parameters or CommandGroups to check against + */ ``` -It also allows to set the `ignoreCase` flag, which is self-explanatory, and the `similarityThreshold` flag, which controls how similar a command name has to be in comparison to the user input for it to be recommended. +There is a lot of possibilities! ## Command Options -There are a few options that can be specified per command, per scope, or globally for a `Commands` instance. +There are a few options that can be specified per command, per scope, or globally for a `CommandGroup` instance. These options allow you to further customize how your bot handles commands, giving you more flexibility. -### `targetedCommands` +### ignoreCase + +By default commands will match the user input in a case-sensitive manner. Having this flag set, for example, in a command named `/dandy` will match `/DANDY` the same as `/dandY` or any other case-only variation. + +### targetedCommands When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. @@ -520,13 +527,19 @@ With it you can choose between three different behaviors: - `optional`: Handles both commands that do and that don't mention the bot's user - `required`: Only handles commands that mention the bot's user -### `prefix` +### prefix Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). In some occasions, you might want to change that and use a custom prefix for your bot. That is made possible by the `prefix` option, which will tell the Commands plugin to look for that prefix when trying to identify a command. -### `matchOnlyAtStart` +If you are ever in the need for retrieving botCommand entities from an update and need it to be hydrated with the custom prefixed you have register, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. It returns the same interface as `ctx.entities('bot_command')` + +:::tip +Commands with custom prefixes cannot be shown in the Commands Menu. +::: + +### matchOnlyAtStart When [handling commands](../guide/commands), the grammY core library will only recognize commands that start on the first character of a message. The Commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! From 8a6f4587b1ba480b4157e151ff70f1f4cc673272 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Wed, 24 Jul 2024 19:31:16 -0400 Subject: [PATCH 17/59] add starting point example for usage with i18n --- site/docs/plugins/commands.md | 49 +++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 4e6e0956f..2627689c3 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -175,7 +175,7 @@ As stated in the [Telegram API documentation](https://core.telegram.org/bots/api > 1-32 characters. Can contain only lowercase English letters, digits and underscores. -Therefore calling `setCommands` or `setMyCommands` with anything but lower_case-commands will throw an exception. Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. +Therefore calling `setCommands` or `setMyCommands` with anything but lower_c4s3_commands will throw an exception. Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. ::: **Be aware** that `SetCommands` and `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the [Scoped Commands](#scoped-commands) section. @@ -390,6 +390,26 @@ myCommands.command( ::: +### Combo with i18n + +If you are looking to have your localized command names and descriptions bundle inside your `.ftl` files, you could make use of the following idea: + +```ts +function addLocalizations(command: Command) { + i18n.locales.forEach((locale) => { + command.localize( + locale, + i18n.t(locale, `${command.name}.command`), + i18n.t(locale, `${command.name}.description`) + ); + }); + return command; +} + +myCommands.commands.forEach(addLocalizations) + +``` + ## Finding the Nearest Command Even though Telegram it's capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. @@ -438,7 +458,6 @@ const myCommands = new CommandGroup(); // ... Register the commands - bot // Check if there is a command .filter(commandNotFound(myCommands)) @@ -454,7 +473,6 @@ bot // Nothing seems to come close to what the user typed await ctx.reply("Oops... I don't know that command :/"); }); - ``` ::: @@ -463,18 +481,17 @@ Behind the scenes `commandNotFound` will use the `getNearestCommand` context met It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. -It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command, and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. +It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. -The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. For example, if you only have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it trigger the handle for anything that looks like your commands, e.g: `?sayhi` or `supercustomhi`, but no `/definitely-a-command`. Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that looks like `/regular /commands`. +The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. For example, if you only have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it will trigger the handle for anything that looks like your commands, e.g: `?sayhi` or `supercustomhi`, but no `/definitely_a_command`. Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that looks like `/regular /commands`. The recommended commands will only come from the commandGroup instances you pass to the function. So you could defer the checks into multiple, separate filters. Let's use the previous knowledge to inspect the next example: ```js - -const myCommands = new CommandGroup; -myCommands.command("dad", "calls dad", () => {}, { prefix: '?'}) +const myCommands = new CommandGroup(); +myCommands.command("dad", "calls dad", () => {}, { prefix: "?" }) .localize("es", "papa", "llama a papa") .localize("fr", "pere", "appelle papa"); @@ -485,15 +502,15 @@ otherCommands.command("bread", "eat a toast", () => {}) // Register Each -// Let's assume the user is french and typed /Papi +// Let's assume the user is french and typed /Papi bot // this filter will trigger for any command-like as '/regular' or '?custom' .filter(commandNotFound([myCommands, otherCommands], { - ignoreLocalization: true, - ignoreCase: true, - })) + ignoreLocalization: true, + ignoreCase: true, + })) .use(async (ctx) => { - ctx.commandSuggestion === "?papa" // evaluates to true + ctx.commandSuggestion === "?papa"; // evaluates to true /* if the ignoreLocalization was falsy instead * we would have gotten: @@ -501,9 +518,9 @@ bot */ }); - /* We could add more filters like the above, - * with different parameters or CommandGroups to check against - */ +/* We could add more filters like the above, + * with different parameters or CommandGroups to check against + */ ``` There is a lot of possibilities! From 26f56d1eca159ee57a2fba1f67c4c3567967e089 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Sun, 25 Aug 2024 10:15:23 -0400 Subject: [PATCH 18/59] Apply suggestions from code review Co-authored-by: Roz <3948961+roziscoding@users.noreply.github.com> --- site/docs/plugins/commands.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 2627689c3..eb87d25da 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -16,7 +16,7 @@ Here is a quick overview of what you get with this plugin: - Ability to scope command reach, e.g: only accessible to group admins or channels, etc - Defining command translations - `Did you mean ...?` feature that finds the nearest existing command to a given user miss-input -- Allow for commands to match in a case-insensitive manner +- Case-insensitive command matching - Set custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` - Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` - Support for commands that are not in the beginning of the message @@ -47,16 +47,16 @@ First of all, we need to import the `CommandGroup` class. ::: code-group ```ts [TypeScript] -import { CommandGroup commands, type CommandsFlavor } from "@grammyjs/commands"; +import { CommandGroup, commands, type CommandsFlavor } from "@grammyjs/commands"; ``` ```js [JavaScript] -const { CommandGroup commands } = require("@grammyjs/commands"); +const { CommandGroup, commands } = require("@grammyjs/commands"); ``` ```ts [Deno] import { - CommandGroup + CommandGroup, commands, type CommandsFlavor, } from "https://deno.land/x/grammy_commands/mod.ts"; @@ -200,7 +200,7 @@ tsconfig.json We are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every necessary import. ::: -The following code group exemplify how we could implement a developer only command group, and update our Telegram client Command menu. Make sure you inspect the `admin.ts` and `user.ts` file-tabs. +The following code group exemplifies how we could implement a developer only command group, and update our Telegram client Command menu. Make sure you inspect the `admin.ts` and `user.ts` file-tabs. ::: code-group @@ -264,7 +264,7 @@ Combining this knowledge with the following section will get your Command-game t ## Scoped Commands Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? -That's what Telegram call **Command Scopes**. +That's what Telegram calls **Command Scopes**. The `Command` class returned by the `command` method exposes a method called `addToScope`. This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. @@ -291,7 +291,7 @@ myCommands The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. -Heres an example of a command that it's only accesible to group admins +Heres an example of a command that's only accesible to group admins ```js adminCommands @@ -307,7 +307,7 @@ adminCommands If you only want a command to be accesible on certain scopes, make sure you do not add a handler in the first `MyCommands.command` call. Doing that will automatically add it to all private chats, including groups. ::: -Here is an example of a command that it's only accesible in groups +Here is an example of a command that's only accesible in groups ```js myCommands @@ -412,8 +412,8 @@ myCommands.commands.forEach(addLocalizations) ## Finding the Nearest Command -Even though Telegram it's capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. -The Commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. It is compatible with custom prefixes, so you don't have to worry about that, and it's usage is quite straight-forward: +Even though Telegram is capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. +The Commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. It is compatible with custom prefixes, so you don't have to worry about that, and its usage is quite straight-forward: ::: code-group @@ -429,7 +429,7 @@ const bot = new Bot("token"); // Register the plugin bot.use(commands()); -const myCommands = new CommandGroup(); +const myCommands = new CommandGroup(); // ... Register the commands @@ -477,7 +477,7 @@ bot ::: -Behind the scenes `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language, if you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. +Behind the scenes, `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language. If you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. @@ -485,7 +485,7 @@ It also allows to set the `ignoreCase` flag, which will ignore casing while look The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. For example, if you only have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it will trigger the handle for anything that looks like your commands, e.g: `?sayhi` or `supercustomhi`, but no `/definitely_a_command`. Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that looks like `/regular /commands`. -The recommended commands will only come from the commandGroup instances you pass to the function. So you could defer the checks into multiple, separate filters. +The recommended commands will only come from the `CommandGroup` instances you pass to the function. So you could defer the checks into multiple, separate filters. Let's use the previous knowledge to inspect the next example: @@ -550,7 +550,7 @@ Currently, only commands starting with `/` are recognized by Telegram and, thus, In some occasions, you might want to change that and use a custom prefix for your bot. That is made possible by the `prefix` option, which will tell the Commands plugin to look for that prefix when trying to identify a command. -If you are ever in the need for retrieving botCommand entities from an update and need it to be hydrated with the custom prefixed you have register, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. It returns the same interface as `ctx.entities('bot_command')` +If you ever need to retrieve `botCommand` entities from an update and need them to be hydrated with the custom prefix you have registered, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. It returns the same interface as `ctx.entities('bot_command')` :::tip Commands with custom prefixes cannot be shown in the Commands Menu. @@ -574,7 +574,7 @@ myCommands ); ``` -This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply `Deleting me` in the first case and `Deleting you` in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing trough as if it was no there. +This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply `Deleting me` in the first case and `Deleting you` in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing trough as if it wasn't there. You can use custom prefixes and localize them as usual. From 3868e5c8a48c690192f1f838ae0fa6fe4da637a6 Mon Sep 17 00:00:00 2001 From: Roz Date: Sun, 25 Aug 2024 11:34:19 -0300 Subject: [PATCH 19/59] chore: deno fmt --- site/docs/plugins/commands.md | 227 ++++++++++++++++++++++------------ 1 file changed, 150 insertions(+), 77 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index eb87d25da..6311022a4 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -7,26 +7,33 @@ next: false Command handling on steroids. -This plugin provides various features related to command handling that are not contained in the [command handling done by the grammY core library](../guide/commands). -Here is a quick overview of what you get with this plugin: +This plugin provides various features related to command handling that are not +contained in the +[command handling done by the grammY core library](../guide/commands). Here is a +quick overview of what you get with this plugin: - Better code readability by encapsulating middleware with command definitions - User command menu synchronization via `setMyCommands` - Improved command grouping and organization -- Ability to scope command reach, e.g: only accessible to group admins or channels, etc +- Ability to scope command reach, e.g: only accessible to group admins or + channels, etc - Defining command translations -- `Did you mean ...?` feature that finds the nearest existing command to a given user miss-input +- `Did you mean ...?` feature that finds the nearest existing command to a given + user miss-input - Case-insensitive command matching -- Set custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` +- Set custom behavior for commands that explicitly mention your bot's user, + like: `/start@your_bot` - Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` - Support for commands that are not in the beginning of the message - RegExp Commands! -All of these features are made possible because you will define one or more central command structures that define your bot's commands. +All of these features are made possible because you will define one or more +central command structures that define your bot's commands. ## Basic Usage -Before we dive in, take a look at how you can register and handle a command with the plugin: +Before we dive in, take a look at how you can register and handle a command with +the plugin: ```js const myCommands = new CommandGroup(); @@ -36,7 +43,8 @@ myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); bot.use(myCommands); ``` -This registers a new `/start` command to your bot that will be handled by the given middleware. +This registers a new `/start` command to your bot that will be handled by the +given middleware. Now, let's get into some of the extra tools this plugin has to offer. @@ -47,7 +55,11 @@ First of all, we need to import the `CommandGroup` class. ::: code-group ```ts [TypeScript] -import { CommandGroup, commands, type CommandsFlavor } from "@grammyjs/commands"; +import { + CommandGroup, + commands, + type CommandsFlavor, +} from "@grammyjs/commands"; ``` ```js [JavaScript] @@ -64,11 +76,14 @@ import { ::: -Now that that's settled, let's see how we can make our commands visible to our users. +Now that that's settled, let's see how we can make our commands visible to our +users. ## User Command Menu Setting -Once you defined your commands with an instance of the `CommandGroup` class, you can call the `setCommands` method, which will register all the defined commands to your bot. +Once you defined your commands with an instance of the `CommandGroup` class, you +can call the `setCommands` method, which will register all the defined commands +to your bot. ```js const myCommands = new CommandGroup(); @@ -81,14 +96,16 @@ bot.use(myCommands); await myCommands.setCommands(bot); ``` -This will make it so every command you registered is displayed on the menu of a private chat with your bot, or whenever users type `/` on a chat your bot is a member of. +This will make it so every command you registered is displayed on the menu of a +private chat with your bot, or whenever users type `/` on a chat your bot is a +member of. ### Context Shortcut -What if you want some commands to be displayed only to certain users. -For example, imagine you have a `login` and a `logout` command. -The `login` command should only appear for logged out users, and vice versa. -This is how you can do that with the Commands plugin: +What if you want some commands to be displayed only to certain users. For +example, imagine you have a `login` and a `logout` command. The `login` command +should only appear for logged out users, and vice versa. This is how you can do +that with the Commands plugin: ::: code-group @@ -166,25 +183,37 @@ await loggedOutCommands.setCommands(bot); ::: -This way when a user calls `/login`, they'll have their commands list changed to contain only the `logout` command. Neat, right? +This way when a user calls `/login`, they'll have their commands list changed to +contain only the `logout` command. Neat, right? -If you want to prevent, for example, the commands contained in `loggedInCommands` from being callable after the user called `/logout`, you must implement it in your handlers with your own business logic. +If you want to prevent, for example, the commands contained in +`loggedInCommands` from being callable after the user called `/logout`, you must +implement it in your handlers with your own business logic. -::: danger -As stated in the [Telegram API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: +::: danger As stated in the +[Telegram API documentation](https://core.telegram.org/bots/api#botcommand), +command names can only be form out of: -> 1-32 characters. Can contain only lowercase English letters, digits and underscores. +> 1-32 characters. Can contain only lowercase English letters, digits and +> underscores. -Therefore calling `setCommands` or `setMyCommands` with anything but lower_c4s3_commands will throw an exception. Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. -::: +Therefore calling `setCommands` or `setMyCommands` with anything but +lower_c4s3_commands will throw an exception. Commands not following this rules +can still be registered, used and handled, but will never be displayed on the +user menu as such. ::: -**Be aware** that `SetCommands` and `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the [Scoped Commands](#scoped-commands) section. +**Be aware** that `SetCommands` and `SetMyCommands` only affects the commands +displayed in the user's commands menu, and not the actual access to them. You +will learn how to implement restricted command access in the +[Scoped Commands](#scoped-commands) section. ### Grouping Commands -Since we can split and group our commands into different instances, it allows for a much more idiomatic command file organization. +Since we can split and group our commands into different instances, it allows +for a much more idiomatic command file organization. -Let say we want to have developer-only commands. We can achieve that with the the following code structure: +Let say we want to have developer-only commands. We can achieve that with the +the following code structure: ```ascii src/ @@ -196,11 +225,12 @@ src/ tsconfig.json ``` -::: tip -We are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every necessary import. -::: +::: tip We are assuming your `tsconfig` file is well-set to resolve the types +from `types.d.ts` and have resolved every necessary import. ::: -The following code group exemplifies how we could implement a developer only command group, and update our Telegram client Command menu. Make sure you inspect the `admin.ts` and `user.ts` file-tabs. +The following code group exemplifies how we could implement a developer only +command group, and update our Telegram client Command menu. Make sure you +inspect the `admin.ts` and `user.ts` file-tabs. ::: code-group @@ -259,17 +289,22 @@ type MyContext = Context & CommandsFlavor; ::: -Combining this knowledge with the following section will get your Command-game to the next level. +Combining this knowledge with the following section will get your Command-game +to the next level. ## Scoped Commands -Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? -That's what Telegram calls **Command Scopes**. +Did you know you can allow different commands to be shown on different chats +depending on the chat type, the language, and even the user status in a chat +group? That's what Telegram calls **Command Scopes**. -The `Command` class returned by the `command` method exposes a method called `addToScope`. -This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. +The `Command` class returned by the `command` method exposes a method called +`addToScope`. This method takes in a +[BotCommandScope](/ref/types/botcommandscope) together with one or more +handlers, and registers those handlers to be ran at that specific scope. -You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. +You don't even need to worry about calling `filter`, the `addToScope` method +will guarantee that your handler only gets called if the context is right. Here's an example of a scoped command: @@ -288,8 +323,10 @@ myCommands ); ``` -The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. -Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. +The `start` command can now be called from both private and group chats, and it +will give a different response depending on where it gets called from. Now if +you call `myCommands.setCommands`, the `start` command will be registered to +both private and group chats. Heres an example of a command that's only accesible to group admins @@ -303,9 +340,9 @@ adminCommands }); ``` -::: tip -If you only want a command to be accesible on certain scopes, make sure you do not add a handler in the first `MyCommands.command` call. Doing that will automatically add it to all private chats, including groups. -::: +::: tip If you only want a command to be accesible on certain scopes, make sure +you do not add a handler in the first `MyCommands.command` call. Doing that will +automatically add it to all private chats, including groups. ::: Here is an example of a command that's only accesible in groups @@ -324,9 +361,10 @@ myCommands ## Command Translations -Another powerful feature is the ability to set different names for the same command, and their respective descriptions based on the user language. -The Commands plugin makes that easy by providing the `localize` method. -Check it out: +Another powerful feature is the ability to set different names for the same +command, and their respective descriptions based on the user language. The +Commands plugin makes that easy by providing the `localize` method. Check it +out: ```js myCommands @@ -336,10 +374,11 @@ myCommands .localize("pt", "ola", "Dizer olá"); ``` -Add as many as you want! -The plugin will take care of registering them for you when you call `myCommands.setCommands`. +Add as many as you want! The plugin will take care of registering them for you +when you call `myCommands.setCommands`. -For convenience the types package exports a `LanguageCodes` enum-like object, that you can use for a more idiomatic approach: +For convenience the types package exports a `LanguageCodes` enum-like object, +that you can use for a more idiomatic approach: ::: code-group @@ -392,7 +431,8 @@ myCommands.command( ### Combo with i18n -If you are looking to have your localized command names and descriptions bundle inside your `.ftl` files, you could make use of the following idea: +If you are looking to have your localized command names and descriptions bundle +inside your `.ftl` files, you could make use of the following idea: ```ts function addLocalizations(command: Command) { @@ -400,20 +440,23 @@ function addLocalizations(command: Command) { command.localize( locale, i18n.t(locale, `${command.name}.command`), - i18n.t(locale, `${command.name}.description`) + i18n.t(locale, `${command.name}.description`), ); }); return command; } -myCommands.commands.forEach(addLocalizations) - +myCommands.commands.forEach(addLocalizations); ``` ## Finding the Nearest Command -Even though Telegram is capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. -The Commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. It is compatible with custom prefixes, so you don't have to worry about that, and its usage is quite straight-forward: +Even though Telegram is capable of auto completing the registered commands, +sometimes users do type them manually and, in some cases, happen to make +mistakes. The Commands plugin helps you deal with that by allowing you to +suggest a command that might be what the user wanted in the first place. It is +compatible with custom prefixes, so you don't have to worry about that, and its +usage is quite straight-forward: ::: code-group @@ -477,15 +520,30 @@ bot ::: -Behind the scenes, `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language. If you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. +Behind the scenes, `commandNotFound` will use the `getNearestCommand` context +method which by default will prioritize commands that correspond to the user +language. If you want to opt-out of this behavior, you can pass the +`ignoreLocalization` flag set to true. -It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. +It is possible to search across multiple CommandGroup instances, and +`ctx.commandSuggestion` will be the most similar command, if any, across them +all. -It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. +It also allows to set the `ignoreCase` flag, which will ignore casing while +looking for a similar command and the `similarityThreshold` flag, which controls +how similar a command name has to be to the user input for it to be recommended. -The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. For example, if you only have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it will trigger the handle for anything that looks like your commands, e.g: `?sayhi` or `supercustomhi`, but no `/definitely_a_command`. Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that looks like `/regular /commands`. +The `commandNotFound` function will only trigger for updates which contains +command-like-text similar to your registered commands. For example, if you only +have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it +will trigger the handle for anything that looks like your commands, e.g: +`?sayhi` or `supercustomhi`, but no `/definitely_a_command`. Same goes the other +way, if you only have commands with the default prefix, it will only trigger on +updates that looks like `/regular /commands`. -The recommended commands will only come from the `CommandGroup` instances you pass to the function. So you could defer the checks into multiple, separate filters. +The recommended commands will only come from the `CommandGroup` instances you +pass to the function. So you could defer the checks into multiple, separate +filters. Let's use the previous knowledge to inspect the next example: @@ -527,44 +585,56 @@ There is a lot of possibilities! ## Command Options -There are a few options that can be specified per command, per scope, or globally for a `CommandGroup` instance. -These options allow you to further customize how your bot handles commands, giving you more flexibility. +There are a few options that can be specified per command, per scope, or +globally for a `CommandGroup` instance. These options allow you to further +customize how your bot handles commands, giving you more flexibility. ### ignoreCase -By default commands will match the user input in a case-sensitive manner. Having this flag set, for example, in a command named `/dandy` will match `/DANDY` the same as `/dandY` or any other case-only variation. +By default commands will match the user input in a case-sensitive manner. Having +this flag set, for example, in a command named `/dandy` will match `/DANDY` the +same as `/dandY` or any other case-only variation. ### targetedCommands -When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. -You can decide what to do with these commands by using the `targetedCommands` config option. -With it you can choose between three different behaviors: +When users invoke a command, they can optionally tag your bot, like so: +`/command@bot_username`. You can decide what to do with these commands by using +the `targetedCommands` config option. With it you can choose between three +different behaviors: - `ignored`: Ignores commands that mention your bot's user -- `optional`: Handles both commands that do and that don't mention the bot's user +- `optional`: Handles both commands that do and that don't mention the bot's + user - `required`: Only handles commands that mention the bot's user ### prefix -Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). -In some occasions, you might want to change that and use a custom prefix for your bot. -That is made possible by the `prefix` option, which will tell the Commands plugin to look for that prefix when trying to identify a command. +Currently, only commands starting with `/` are recognized by Telegram and, thus, +by the [command handling done by the grammY core library](../guide/commands). In +some occasions, you might want to change that and use a custom prefix for your +bot. That is made possible by the `prefix` option, which will tell the Commands +plugin to look for that prefix when trying to identify a command. -If you ever need to retrieve `botCommand` entities from an update and need them to be hydrated with the custom prefix you have registered, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. It returns the same interface as `ctx.entities('bot_command')` +If you ever need to retrieve `botCommand` entities from an update and need them +to be hydrated with the custom prefix you have registered, there is a method +specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. +It returns the same interface as `ctx.entities('bot_command')` -:::tip -Commands with custom prefixes cannot be shown in the Commands Menu. -::: +:::tip Commands with custom prefixes cannot be shown in the Commands Menu. ::: ### matchOnlyAtStart -When [handling commands](../guide/commands), the grammY core library will only recognize commands that start on the first character of a message. -The Commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! -All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. +When [handling commands](../guide/commands), the grammY core library will only +recognize commands that start on the first character of a message. The Commands +plugin, however, allows you to listen for commands in the middle of the message +text, or in the end, it doesn't matter! All you have to do is set the +`matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. ## RegExp Commands -This feature is for those who are really looking to go wild, it allows you to create command handlers based on Regular Expressions instead of static strings, a basic example would look like: +This feature is for those who are really looking to go wild, it allows you to +create command handlers based on Regular Expressions instead of static strings, +a basic example would look like: ```js myCommands @@ -574,7 +644,10 @@ myCommands ); ``` -This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply `Deleting me` in the first case and `Deleting you` in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing trough as if it wasn't there. +This command handler will trigger on `/delete_me` the same as in `/delete_you`, +and it will reply `Deleting me` in the first case and `Deleting you` in the +later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing trough +as if it wasn't there. You can use custom prefixes and localize them as usual. From ddf8fe68f1cf501acaf12cebdf6bc4ce494ae818 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Sun, 25 Aug 2024 12:43:44 -0400 Subject: [PATCH 20/59] fix tip block --- site/docs/plugins/commands.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 6311022a4..31e83dacc 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -620,7 +620,9 @@ to be hydrated with the custom prefix you have registered, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. It returns the same interface as `ctx.entities('bot_command')` -:::tip Commands with custom prefixes cannot be shown in the Commands Menu. ::: +:::tip +Commands with custom prefixes cannot be shown in the Commands Menu. +::: ### matchOnlyAtStart From a14eedacb220b7a0b44597c9afb27259f981bcec Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Sun, 25 Aug 2024 12:51:07 -0400 Subject: [PATCH 21/59] fix more tip blocks --- site/docs/plugins/commands.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 31e83dacc..3ddc397e3 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -225,8 +225,10 @@ src/ tsconfig.json ``` -::: tip We are assuming your `tsconfig` file is well-set to resolve the types -from `types.d.ts` and have resolved every necessary import. ::: +::: tip +We are assuming your `tsconfig` file is well-set to resolve the types +from `types.d.ts` and have resolved every necessary import. +::: The following code group exemplifies how we could implement a developer only command group, and update our Telegram client Command menu. Make sure you @@ -340,9 +342,11 @@ adminCommands }); ``` -::: tip If you only want a command to be accesible on certain scopes, make sure +::: tip +If you only want a command to be accesible on certain scopes, make sure you do not add a handler in the first `MyCommands.command` call. Doing that will -automatically add it to all private chats, including groups. ::: +automatically add it to all private chats, including groups. +::: Here is an example of a command that's only accesible in groups From f28e5147a25f0ebb6fd63fdb5d2cbadb7b7015cc Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 29 Aug 2024 18:57:29 -0400 Subject: [PATCH 22/59] fix danger block --- site/docs/plugins/commands.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 3ddc397e3..82588115c 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -190,7 +190,8 @@ If you want to prevent, for example, the commands contained in `loggedInCommands` from being callable after the user called `/logout`, you must implement it in your handlers with your own business logic. -::: danger As stated in the +::: danger +As stated in the [Telegram API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: @@ -200,7 +201,8 @@ command names can only be form out of: Therefore calling `setCommands` or `setMyCommands` with anything but lower_c4s3_commands will throw an exception. Commands not following this rules can still be registered, used and handled, but will never be displayed on the -user menu as such. ::: +user menu as such. +::: **Be aware** that `SetCommands` and `SetMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. You From 7fb17c1071f52e82f88870ed6642d2edc21c52fd Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 29 Aug 2024 19:30:30 -0400 Subject: [PATCH 23/59] add section for importing Commands into a CommandGroup --- site/docs/plugins/commands.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 82588115c..6c1311152 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -220,27 +220,30 @@ the following code structure: ```ascii src/ ├─ commands/ -│ ├─ users.ts │ ├─ admin.ts +│ ├─ users/ +│ │ ├─ group.ts +│ │ ├─ say-hi.ts +│ │ ├─ say-bye.ts +│ │ ├─ ... ├─ bot.ts ├─ types.d.ts tsconfig.json ``` ::: tip -We are assuming your `tsconfig` file is well-set to resolve the types -from `types.d.ts` and have resolved every necessary import. +For the sake of brevity, we are assuming your `tsconfig` file is well-set to resolve the types +from `types.d.ts` and have resolved every other necessary import. ::: The following code group exemplifies how we could implement a developer only -command group, and update our Telegram client Command menu. Make sure you -inspect the `admin.ts` and `user.ts` file-tabs. +command group, and update the Telegram client Command menu accordingly. Make sure you take notice of the different patterns being use in the `admin.ts` and `group.ts` file-tabs. ::: code-group ```ts [bot.ts] import { devCommands } from "./commands/admin.ts"; -import { userCommands } from "./commands/users.ts"; +import { userCommands } from "./commands/users/group.ts"; export const bot = new Bot("MyBotToken"); @@ -251,7 +254,7 @@ bot.use(devCommands); ``` ```ts [admin.ts] -import { userCommands } from './users.ts' +import { userCommands } from './users/group.ts' export const devCommands = new CommandGroup() @@ -278,12 +281,16 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { }) ``` -```ts [users.ts] -export const userCommands = new CommandGroup(); +```ts [group.ts] +import sayHi from "./say-hi.ts"; +import sayBye from "./say-bye.ts"; +export const userCommands = new CommandGroup() + .add([sayHi, sayBye]); +``` -userCommands.command("start", "Greetings", async (ctx) => { - await ctx.reply("Hello user"); - await ctx.setMyCommands(userCommands); +```ts [say-hi.ts] +export default new Command("sayhi", "Greetings", async (ctx) => { + await ctx.reply("Hello little User!"); }); ``` @@ -293,6 +300,10 @@ type MyContext = Context & CommandsFlavor; ::: +Did you notice it is possible to register Commands via using the `.add` method in the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. + +The plugin also enforce you to have the same Context-type for a given `CommandGroup` so avoid at first glance the silly mistakes! + Combining this knowledge with the following section will get your Command-game to the next level. From 090b5c93ce7ded9ea3d0ef2f043d242ee4b41ac7 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 29 Aug 2024 19:33:03 -0400 Subject: [PATCH 24/59] add a little newline into the command grouping section --- site/docs/plugins/commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 6c1311152..6eac4dad3 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -284,6 +284,7 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { ```ts [group.ts] import sayHi from "./say-hi.ts"; import sayBye from "./say-bye.ts"; + export const userCommands = new CommandGroup() .add([sayHi, sayBye]); ``` From 5ccc2b73a0d68775e2e797403b598aed6eba34fe Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 29 Aug 2024 19:34:56 -0400 Subject: [PATCH 25/59] add forgotten personal pronoun --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 6eac4dad3..a1554d551 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -303,7 +303,7 @@ type MyContext = Context & CommandsFlavor; Did you notice it is possible to register Commands via using the `.add` method in the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. -The plugin also enforce you to have the same Context-type for a given `CommandGroup` so avoid at first glance the silly mistakes! +The plugin also enforce you to have the same Context-type for a given `CommandGroup` so you avoid at first glance that kind of silly mistake! Combining this knowledge with the following section will get your Command-game to the next level. From bd55f7396585abd7232fdcf6a42c6b715a28a799 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 29 Aug 2024 19:35:54 -0400 Subject: [PATCH 26/59] improve clarity related to the context enforcement in commandGroups and their respective Command childs --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index a1554d551..9fe3c01c6 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -303,7 +303,7 @@ type MyContext = Context & CommandsFlavor; Did you notice it is possible to register Commands via using the `.add` method in the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. -The plugin also enforce you to have the same Context-type for a given `CommandGroup` so you avoid at first glance that kind of silly mistake! +The plugin also enforce you to have the same Context-type for a given `CommandGroup` and their respective `Commands` so you avoid at first glance that kind of silly mistake! Combining this knowledge with the following section will get your Command-game to the next level. From 470751165969589f6c5947237b1062025a810fe5 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Thu, 29 Aug 2024 20:29:42 -0400 Subject: [PATCH 27/59] add tip block regarding that a Command by it's own, one that is not register onto a CommandGroup, its useless --- site/docs/plugins/commands.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 9fe3c01c6..ff5a8f2c3 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -284,6 +284,7 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { ```ts [group.ts] import sayHi from "./say-hi.ts"; import sayBye from "./say-bye.ts"; +import etc from "./another-command.ts"; export const userCommands = new CommandGroup() .add([sayHi, sayBye]); @@ -301,7 +302,13 @@ type MyContext = Context & CommandsFlavor; ::: -Did you notice it is possible to register Commands via using the `.add` method in the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. +Did you notice it is possible to register single initialized Commands via the `.add` method into the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. + +::: tip + +When creating and exporting commands using the `Command` constructor, it's mandatory to register them onto a `CommandGroup` instance via the `.add` method. By their own they are useless, so make sure you do that at some point. + +::: The plugin also enforce you to have the same Context-type for a given `CommandGroup` and their respective `Commands` so you avoid at first glance that kind of silly mistake! From 24e2aac8b1ae46fe3d11e2b4d1d6d5760df751ad Mon Sep 17 00:00:00 2001 From: Roz Date: Tue, 15 Oct 2024 15:51:27 -0300 Subject: [PATCH 28/59] feat: address review suggestions --- site/docs/plugins/commands.md | 311 ++++++++++++++-------------------- 1 file changed, 126 insertions(+), 185 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index ff5a8f2c3..e22e11155 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -7,10 +7,8 @@ next: false Command handling on steroids. -This plugin provides various features related to command handling that are not -contained in the -[command handling done by the grammY core library](../guide/commands). Here is a -quick overview of what you get with this plugin: +This plugin provides various features related to command handling that are not contained in the [command handling done by the grammY core library](../guide/commands). +Here is a quick overview of what you get with this plugin: - Better code readability by encapsulating middleware with command definitions - User command menu synchronization via `setMyCommands` @@ -21,19 +19,17 @@ quick overview of what you get with this plugin: - `Did you mean ...?` feature that finds the nearest existing command to a given user miss-input - Case-insensitive command matching -- Set custom behavior for commands that explicitly mention your bot's user, +- Setting custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` - Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` - Support for commands that are not in the beginning of the message - RegExp Commands! -All of these features are made possible because you will define one or more -central command structures that define your bot's commands. +All of these features are made possible because you will define one or more central command structures that define your bot's commands. ## Basic Usage -Before we dive in, take a look at how you can register and handle a command with -the plugin: +Before we dive in, take a look at how you can register and handle a command with the plugin: ```js const myCommands = new CommandGroup(); @@ -43,8 +39,7 @@ myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); bot.use(myCommands); ``` -This registers a new `/start` command to your bot that will be handled by the -given middleware. +This registers a new `/start` command to your bot that will be handled by the given middleware. Now, let's get into some of the extra tools this plugin has to offer. @@ -76,14 +71,11 @@ import { ::: -Now that that's settled, let's see how we can make our commands visible to our -users. +Now that that's settled, let's see how we can make our commands visible to our users. ## User Command Menu Setting -Once you defined your commands with an instance of the `CommandGroup` class, you -can call the `setCommands` method, which will register all the defined commands -to your bot. +Once you defined your commands with an instance of the `CommandGroup` class, you can call the `setCommands` method, which will register all the defined commands to your bot. ```js const myCommands = new CommandGroup(); @@ -96,16 +88,13 @@ bot.use(myCommands); await myCommands.setCommands(bot); ``` -This will make it so every command you registered is displayed on the menu of a -private chat with your bot, or whenever users type `/` on a chat your bot is a -member of. +This will make it so every command you registered is displayed on the menu of a private chat with your bot, or whenever users type `/` on a chat your bot is a member of. ### Context Shortcut -What if you want some commands to be displayed only to certain users. For -example, imagine you have a `login` and a `logout` command. The `login` command -should only appear for logged out users, and vice versa. This is how you can do -that with the Commands plugin: +What if you want some commands to be displayed only to certain users? For example, imagine you have a `login` and a `logout` command. +The `login` command should only appear for logged out users, and vice versa. +This is how you can do that with the commands plugin: ::: code-group @@ -183,39 +172,28 @@ await loggedOutCommands.setCommands(bot); ::: -This way when a user calls `/login`, they'll have their commands list changed to -contain only the `logout` command. Neat, right? - -If you want to prevent, for example, the commands contained in -`loggedInCommands` from being callable after the user called `/logout`, you must -implement it in your handlers with your own business logic. +This way when a user calls `/login`, they'll have their command list changed to contain only the `logout` command. +Neat, right? ::: danger -As stated in the -[Telegram API documentation](https://core.telegram.org/bots/api#botcommand), -command names can only be form out of: +As stated in the [Telegram Bot API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: -> 1-32 characters. Can contain only lowercase English letters, digits and -> underscores. +> 1-32 characters. +> Can contain only lowercase English letters, digits and underscores. -Therefore calling `setCommands` or `setMyCommands` with anything but -lower_c4s3_commands will throw an exception. Commands not following this rules -can still be registered, used and handled, but will never be displayed on the -user menu as such. +Therefore calling `setCommands` or `setMyCommands` with anything but lower_c4s3_commands will throw an exception. +Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. ::: -**Be aware** that `SetCommands` and `SetMyCommands` only affects the commands -displayed in the user's commands menu, and not the actual access to them. You -will learn how to implement restricted command access in the -[Scoped Commands](#scoped-commands) section. +**Be aware** that `setCommands` and `setMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. +You will learn how to implement restricted command access in the [Scoped Commands](#scoped-commands) section. ### Grouping Commands -Since we can split and group our commands into different instances, it allows -for a much more idiomatic command file organization. +Since we can split and group our commands into different instances, it allows for a much more idiomatic command file organization. -Let say we want to have developer-only commands. We can achieve that with the -the following code structure: +Let's say we want to have developer-only commands. +We can achieve that with the following code structure: ```ascii src/ @@ -232,12 +210,11 @@ tsconfig.json ``` ::: tip -For the sake of brevity, we are assuming your `tsconfig` file is well-set to resolve the types -from `types.d.ts` and have resolved every other necessary import. +For the sake of brevity, we are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every other necessary import. ::: -The following code group exemplifies how we could implement a developer only -command group, and update the Telegram client Command menu accordingly. Make sure you take notice of the different patterns being use in the `admin.ts` and `group.ts` file-tabs. +The following code group exemplifies how we could implement a developer only command group, and update the Telegram client Command menu accordingly. +Make sure you take notice of the different patterns being use in the `admin.ts` and `group.ts` file-tabs. ::: code-group @@ -302,32 +279,33 @@ type MyContext = Context & CommandsFlavor; ::: -Did you notice it is possible to register single initialized Commands via the `.add` method into the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. +Did you notice it is possible to register single initialized Commands via the `.add` method into the `CommandGroup` instance or also directly through the `.command(...)` method? +This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. ::: tip -When creating and exporting commands using the `Command` constructor, it's mandatory to register them onto a `CommandGroup` instance via the `.add` method. By their own they are useless, so make sure you do that at some point. +When creating and exporting commands using the `Command` constructor, it's mandatory to register them onto a `CommandGroup` instance via the `.add` method. +On their own they are useless, so make sure you do that at some point. ::: The plugin also enforce you to have the same Context-type for a given `CommandGroup` and their respective `Commands` so you avoid at first glance that kind of silly mistake! -Combining this knowledge with the following section will get your Command-game -to the next level. +Combining this knowledge with the following section will get your Command-game to the next level. ## Scoped Commands -Did you know you can allow different commands to be shown on different chats -depending on the chat type, the language, and even the user status in a chat -group? That's what Telegram calls **Command Scopes**. +Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? That's what Telegram calls [**Command Scopes**](https://core.telegram.org/bots/features#command-scopes). + +Now, Command Scopes are a cool feature, but using it by hand can get really messy, since it's hard to keep track of all the scopes and what commands they present. +Plus, by using Command Scopes on their own, you have to do manual filtering inside each command to ensure they'll only run for the correct scopes. +Syncing those two things up can be a nightmare, and that's why this plugin exists. +Check how it's done. -The `Command` class returned by the `command` method exposes a method called -`addToScope`. This method takes in a -[BotCommandScope](/ref/types/botcommandscope) together with one or more -handlers, and registers those handlers to be ran at that specific scope. +The `Command` class returned by the `command` method exposes a method called `addToScope`. +This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. -You don't even need to worry about calling `filter`, the `addToScope` method -will guarantee that your handler only gets called if the context is right. +You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. Here's an example of a scoped command: @@ -346,50 +324,63 @@ myCommands ); ``` -The `start` command can now be called from both private and group chats, and it -will give a different response depending on where it gets called from. Now if -you call `myCommands.setCommands`, the `start` command will be registered to -both private and group chats. +The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. +Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. -Heres an example of a command that's only accesible to group admins +Here's an example of a command that's only accessible to group admins. ```js adminCommands .command("secret", "Admin only") - .addToScope({ - type: "all_chat_administrators", - }, async (ctx) => { - await ctx.reply("Free cake!"); - }); + .addToScope( + { type: "all_chat_administrators" }, + (ctx) => ctx.reply("Free cake!") + ); ``` -::: tip -If you only want a command to be accesible on certain scopes, make sure -you do not add a handler in the first `MyCommands.command` call. Doing that will -automatically add it to all private chats, including groups. -::: - -Here is an example of a command that's only accesible in groups +And here is an example of a command that's only accessible in groups ```js +myCommands + .command("fun", "Laugh") + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply("Haha") + ); +``` + +Notice that when you call the `command` method, it opens up a new command. +If you give it a handler, that handler will apply to the `default` scope of that command. +Calling `addToScope` on that command will then add a new handler, which will be filtered to that scope. +Take a look at this example. + +```ts myCommands .command( - "fun", - "Laugh", - /** skip this handler */ - ).addToScope({ - type: "all_group_chats", - }, async (ctx) => { - await ctx.reply("Haha"); - }); + "default", + "Default command", + // This will be called when not on a group chat, or when the user is not an admin + (ctx) => ctx.reply("Hello from default scope") + ) + .addToScope( + { type: "all_group_chats" }, + // This will only be called for non-admin users in a group + (ctx) => ctx.reply("Hello, group chat!") + ) + .addToScope( + { type: "all_chat_administrators" }, + // This will be called for group admins, when inside that group + (ctx) => ctx.reply("Hello, admin!") + ) +``` +``` ``` ## Command Translations -Another powerful feature is the ability to set different names for the same -command, and their respective descriptions based on the user language. The -Commands plugin makes that easy by providing the `localize` method. Check it -out: +Another powerful feature is the ability to set different names for the same command, and their respective descriptions based on the user language. +The commands plugin makes that easy by providing the `localize` method. +Check it out: ```js myCommands @@ -399,16 +390,14 @@ myCommands .localize("pt", "ola", "Dizer olá"); ``` -Add as many as you want! The plugin will take care of registering them for you -when you call `myCommands.setCommands`. +Add as many as you want! The plugin will take care of registering them for you when you call `myCommands.setCommands`. -For convenience the types package exports a `LanguageCodes` enum-like object, -that you can use for a more idiomatic approach: +For convenience grammY exports a `LanguageCodes` enum-like object, that you can use for a more idiomatic approach: ::: code-group ```ts [TypeScript] -import { LanguageCodes } from "@grammyjs/types"; +import { LanguageCodes } from "grammy/types"; myCommands.command( "chef", @@ -423,7 +412,7 @@ myCommands.command( ``` ```js [JavaScript] -const { LanguageCodes } = require("@grammyjs/types"); +const { LanguageCodes } = require("grammy/types"); myCommands.command( "chef", @@ -438,7 +427,7 @@ myCommands.command( ``` ```ts [Deno] -import { LanguageCodes } from "https://deno.land/x/grammy_types/mod.ts"; +import { LanguageCodes } from "https://deno.land/x/grammy/types.ts"; myCommands.command( "chef", @@ -456,8 +445,7 @@ myCommands.command( ### Combo with i18n -If you are looking to have your localized command names and descriptions bundle -inside your `.ftl` files, you could make use of the following idea: +If you are looking to have your localized command names and descriptions bundle inside your `.ftl` files, you could make use of the following idea: ```ts function addLocalizations(command: Command) { @@ -476,12 +464,9 @@ myCommands.commands.forEach(addLocalizations); ## Finding the Nearest Command -Even though Telegram is capable of auto completing the registered commands, -sometimes users do type them manually and, in some cases, happen to make -mistakes. The Commands plugin helps you deal with that by allowing you to -suggest a command that might be what the user wanted in the first place. It is -compatible with custom prefixes, so you don't have to worry about that, and its -usage is quite straight-forward: +Even though Telegram is capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. +The commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. +It is compatible with custom prefixes, so you don't have to worry about that, and its usage is quite straightforward: ::: code-group @@ -493,10 +478,6 @@ type MyContext = Context & CommandsFlavor; // Use the new context to instantiate your bot const bot = new Bot("token"); - -// Register the plugin -bot.use(commands()); - const myCommands = new CommandGroup(); // ... Register the commands @@ -519,9 +500,10 @@ bot ``` ```js [JavaScript] -// Register the context shortcut -bot.use(commands()); +import { commandNotFound } from "@grammyjs/commands"; +// Use the new context to instantiate your bot +const bot = new Bot("token"); const myCommands = new CommandGroup(); // ... Register the commands @@ -545,30 +527,16 @@ bot ::: -Behind the scenes, `commandNotFound` will use the `getNearestCommand` context -method which by default will prioritize commands that correspond to the user -language. If you want to opt-out of this behavior, you can pass the -`ignoreLocalization` flag set to true. - -It is possible to search across multiple CommandGroup instances, and -`ctx.commandSuggestion` will be the most similar command, if any, across them -all. +Behind the scenes, `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language. +If you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. +It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. +It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. -It also allows to set the `ignoreCase` flag, which will ignore casing while -looking for a similar command and the `similarityThreshold` flag, which controls -how similar a command name has to be to the user input for it to be recommended. +The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. +For example, if you only have registered [commands with a custom prefix](#prefix) like `?`, it will trigger the handler for anything that looks like your commands, e.g: `?sayhi` but not `/definitely_a_command`. +Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that look like `/regular` `/commands`. -The `commandNotFound` function will only trigger for updates which contains -command-like-text similar to your registered commands. For example, if you only -have register [commands with custom prefixes](#prefix) `?` and `supercustom`, it -will trigger the handle for anything that looks like your commands, e.g: -`?sayhi` or `supercustomhi`, but no `/definitely_a_command`. Same goes the other -way, if you only have commands with the default prefix, it will only trigger on -updates that looks like `/regular /commands`. - -The recommended commands will only come from the `CommandGroup` instances you -pass to the function. So you could defer the checks into multiple, separate -filters. +The recommended commands will only come from the `CommandGroup` instances you pass to the function. So you could defer the checks into multiple, separate filters. Let's use the previous knowledge to inspect the next example: @@ -583,9 +551,9 @@ otherCommands.command("bread", "eat a toast", () => {}) .localize("es", "pan", "come un pan") .localize("fr", "pain", "manger du pain"); -// Register Each +// Register each language-specific command group -// Let's assume the user is french and typed /Papi +// Let's assume the user is French and typed /Papi bot // this filter will trigger for any command-like as '/regular' or '?custom' .filter(commandNotFound([myCommands, otherCommands], { @@ -594,56 +562,39 @@ bot })) .use(async (ctx) => { ctx.commandSuggestion === "?papa"; // evaluates to true - - /* if the ignoreLocalization was falsy instead - * we would have gotten: - * ctx.commandSuggestion equals "/pain" - */ }); - -/* We could add more filters like the above, - * with different parameters or CommandGroups to check against - */ ``` -There is a lot of possibilities! +If the `ignoreLocalization` was falsy instead we would have gotten "`ctx.commandSuggestion` equals `/pain`". +We could add more filters like the above, with different parameters or `CommandGroups` to check against. +There are a lot of possibilities! ## Command Options -There are a few options that can be specified per command, per scope, or -globally for a `CommandGroup` instance. These options allow you to further -customize how your bot handles commands, giving you more flexibility. +There are a few options that can be specified per command, per scope, or globally for a `CommandGroup` instance. +These options allow you to further customize how your bot handles commands, giving you more flexibility. ### ignoreCase -By default commands will match the user input in a case-sensitive manner. Having -this flag set, for example, in a command named `/dandy` will match `/DANDY` the -same as `/dandY` or any other case-only variation. +By default commands will match the user input in a case-sensitive manner. +Having this flag set, for example, in a command named `/dandy` will match `/DANDY` the same as `/dandY` or any other case-only variation. ### targetedCommands -When users invoke a command, they can optionally tag your bot, like so: -`/command@bot_username`. You can decide what to do with these commands by using -the `targetedCommands` config option. With it you can choose between three -different behaviors: +When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. +With it you can choose between three different behaviors: - `ignored`: Ignores commands that mention your bot's user -- `optional`: Handles both commands that do and that don't mention the bot's - user +- `optional`: Handles both commands that do and that don't mention the bot's user - `required`: Only handles commands that mention the bot's user ### prefix -Currently, only commands starting with `/` are recognized by Telegram and, thus, -by the [command handling done by the grammY core library](../guide/commands). In -some occasions, you might want to change that and use a custom prefix for your -bot. That is made possible by the `prefix` option, which will tell the Commands -plugin to look for that prefix when trying to identify a command. +Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). +In some occasions, you might want to change that and use a custom prefix for your bot. +That is made possible by the `prefix` option, which will tell the commands plugin to look for that prefix when trying to identify a command. -If you ever need to retrieve `botCommand` entities from an update and need them -to be hydrated with the custom prefix you have registered, there is a method -specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`. -It returns the same interface as `ctx.entities('bot_command')` +If you ever need to retrieve `botCommand` entities from an update and need them to be hydrated with the custom prefix you have registered, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`, which returns the same interface as `ctx.entities('bot_command')` :::tip Commands with custom prefixes cannot be shown in the Commands Menu. @@ -651,36 +602,26 @@ Commands with custom prefixes cannot be shown in the Commands Menu. ### matchOnlyAtStart -When [handling commands](../guide/commands), the grammY core library will only -recognize commands that start on the first character of a message. The Commands -plugin, however, allows you to listen for commands in the middle of the message -text, or in the end, it doesn't matter! All you have to do is set the -`matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. +When [handling commands](../guide/commands), the grammY core library will only recognize commands that start on the first character of a message. +The commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! +All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. ## RegExp Commands -This feature is for those who are really looking to go wild, it allows you to -create command handlers based on Regular Expressions instead of static strings, -a basic example would look like: +This feature is for those who are really looking to go wild, it allows you to create command handlers based on regular expressions instead of static strings, a basic example would look like: ```js myCommands .command( - /delete_([a-zA-Z]{1,})/, - (ctx) => ctx.reply(`Deleting ${ctx.message?.text?.split("_")[1]}`), + /delete_([a-zA-Z]+)/, + (ctx) => ctx.reply(`Deleting ${ctx.msg?.text?.split("_")[1]}`), ); ``` -This command handler will trigger on `/delete_me` the same as in `/delete_you`, -and it will reply `Deleting me` in the first case and `Deleting you` in the -later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing trough -as if it wasn't there. - -You can use custom prefixes and localize them as usual. +This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply "Deleting me" in the first case and "Deleting you" in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing through as if it wasn't there. ## Plugin Summary - Name: `commands` - [Source](https://github.com/grammyjs/commands) - [Reference](/ref/commands/) -- [Telegram Docs commands reference](https://core.telegram.org/bots/features#commands) From 73dbf0223d290c4643bc2fbbdcbb1daffd11a4eb Mon Sep 17 00:00:00 2001 From: Roz Date: Tue, 15 Oct 2024 15:54:06 -0300 Subject: [PATCH 29/59] feat: add a title to every note --- site/docs/plugins/commands.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index e22e11155..411d624df 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -175,7 +175,7 @@ await loggedOutCommands.setCommands(bot); This way when a user calls `/login`, they'll have their command list changed to contain only the `logout` command. Neat, right? -::: danger +::: danger Command Name Restrictions As stated in the [Telegram Bot API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: > 1-32 characters. @@ -209,7 +209,7 @@ src/ tsconfig.json ``` -::: tip +::: tip Type Resolution For the sake of brevity, we are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every other necessary import. ::: @@ -282,7 +282,7 @@ type MyContext = Context & CommandsFlavor; Did you notice it is possible to register single initialized Commands via the `.add` method into the `CommandGroup` instance or also directly through the `.command(...)` method? This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. -::: tip +::: tip Always Use Command Groups When creating and exporting commands using the `Command` constructor, it's mandatory to register them onto a `CommandGroup` instance via the `.add` method. On their own they are useless, so make sure you do that at some point. From fa5df229f3b2ed6d04141b758df348aae7f5f555 Mon Sep 17 00:00:00 2001 From: Roz Date: Tue, 15 Oct 2024 15:59:41 -0300 Subject: [PATCH 30/59] chore: fix lint errors --- site/docs/plugins/commands.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 411d624df..9c9b576a8 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -373,8 +373,6 @@ myCommands (ctx) => ctx.reply("Hello, admin!") ) ``` -``` -``` ## Command Translations From bd6ce07fb799f3a02320dc056ea7bab50652819e Mon Sep 17 00:00:00 2001 From: Roz Date: Tue, 15 Oct 2024 16:04:16 -0300 Subject: [PATCH 31/59] fix new linter errors --- site/docs/plugins/commands.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 9c9b576a8..9b6700f4f 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -334,7 +334,7 @@ adminCommands .command("secret", "Admin only") .addToScope( { type: "all_chat_administrators" }, - (ctx) => ctx.reply("Free cake!") + (ctx) => ctx.reply("Free cake!"), ); ``` @@ -345,7 +345,7 @@ myCommands .command("fun", "Laugh") .addToScope( { type: "all_group_chats" }, - (ctx) => ctx.reply("Haha") + (ctx) => ctx.reply("Haha"), ); ``` @@ -360,18 +360,18 @@ myCommands "default", "Default command", // This will be called when not on a group chat, or when the user is not an admin - (ctx) => ctx.reply("Hello from default scope") + (ctx) => ctx.reply("Hello from default scope"), ) .addToScope( { type: "all_group_chats" }, // This will only be called for non-admin users in a group - (ctx) => ctx.reply("Hello, group chat!") + (ctx) => ctx.reply("Hello, group chat!"), ) .addToScope( { type: "all_chat_administrators" }, // This will be called for group admins, when inside that group - (ctx) => ctx.reply("Hello, admin!") - ) + (ctx) => ctx.reply("Hello, admin!"), + ); ``` ## Command Translations From 3a52bdd73800a22a1fb35f6d62043d3e801f5391 Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:13:57 -0300 Subject: [PATCH 32/59] update section title Co-authored-by: KnorpelSenf --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 9b6700f4f..b0fbecb4c 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -441,7 +441,7 @@ myCommands.command( ::: -### Combo with i18n +### Localizing Commands With the Internationalization Plugin If you are looking to have your localized command names and descriptions bundle inside your `.ftl` files, you could make use of the following idea: From 87876e72eef92bf63d1040db05d5529c7b5b7ec1 Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Wed, 16 Oct 2024 08:45:38 +0200 Subject: [PATCH 33/59] Delete accidentally committed package-lock.json --- package-lock.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 4ff32e61b..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "website", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From d5592b787ca1914a78c2340d0f23fa1d8b47df08 Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Wed, 16 Oct 2024 08:48:15 +0200 Subject: [PATCH 34/59] Fix formatting of suggestion --- site/docs/plugins/commands.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index b0fbecb4c..7c65d9d6d 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -333,8 +333,8 @@ Here's an example of a command that's only accessible to group admins. adminCommands .command("secret", "Admin only") .addToScope( - { type: "all_chat_administrators" }, - (ctx) => ctx.reply("Free cake!"), + { type: "all_chat_administrators" }, + (ctx) => ctx.reply("Free cake!"), ); ``` From 19c61873341a1bc02cdef8b6552894bc1f42e681 Mon Sep 17 00:00:00 2001 From: Roz Date: Wed, 16 Oct 2024 10:31:12 -0300 Subject: [PATCH 35/59] update code blocks to remote types.d.ts --- site/docs/plugins/commands.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 7c65d9d6d..2dd5beb8a 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -45,7 +45,7 @@ Now, let's get into some of the extra tools this plugin has to offer. ## Importing -First of all, we need to import the `CommandGroup` class. +First of all, here's how you can import all the necessary types and classes the plugin provides. ::: code-group @@ -53,18 +53,20 @@ First of all, we need to import the `CommandGroup` class. import { CommandGroup, commands, + commandNotFound, type CommandsFlavor, } from "@grammyjs/commands"; ``` ```js [JavaScript] -const { CommandGroup, commands } = require("@grammyjs/commands"); +const { CommandGroup, commands, commandNotFound } = require("@grammyjs/commands"); ``` ```ts [Deno] import { CommandGroup, commands, + commandNotFound, type CommandsFlavor, } from "https://deno.land/x/grammy_commands/mod.ts"; ``` @@ -205,7 +207,7 @@ src/ │ │ ├─ say-bye.ts │ │ ├─ ... ├─ bot.ts -├─ types.d.ts +├─ types.ts tsconfig.json ``` @@ -218,9 +220,14 @@ Make sure you take notice of the different patterns being use in the `admin.ts` ::: code-group +```ts [types.ts] +export type MyContext = Context & CommandsFlavor; +``` + ```ts [bot.ts] import { devCommands } from "./commands/admin.ts"; import { userCommands } from "./commands/users/group.ts"; +import { MyContext } from "./types.ts"; export const bot = new Bot("MyBotToken"); @@ -232,6 +239,7 @@ bot.use(devCommands); ```ts [admin.ts] import { userCommands } from './users/group.ts' +import { MyContext } from '../types.ts' export const devCommands = new CommandGroup() @@ -262,21 +270,20 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { import sayHi from "./say-hi.ts"; import sayBye from "./say-bye.ts"; import etc from "./another-command.ts"; +import { MyContext } from '../../types.ts' export const userCommands = new CommandGroup() .add([sayHi, sayBye]); ``` ```ts [say-hi.ts] +import { MyContext } from '../../types.ts' + export default new Command("sayhi", "Greetings", async (ctx) => { await ctx.reply("Hello little User!"); }); ``` -```ts [types.d.ts] -type MyContext = Context & CommandsFlavor; -``` - ::: Did you notice it is possible to register single initialized Commands via the `.add` method into the `CommandGroup` instance or also directly through the `.command(...)` method? @@ -469,8 +476,6 @@ It is compatible with custom prefixes, so you don't have to worry about that, an ::: code-group ```ts [TypeScript] -import { commandNotFound } from "@grammyjs/commands"; - // Use the flavor to create a custom context type MyContext = Context & CommandsFlavor; @@ -498,8 +503,6 @@ bot ``` ```js [JavaScript] -import { commandNotFound } from "@grammyjs/commands"; - // Use the new context to instantiate your bot const bot = new Bot("token"); const myCommands = new CommandGroup(); From fdaf345ac90bdddcad96a0feda5cc801210a2f1c Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Wed, 16 Oct 2024 16:18:40 +0200 Subject: [PATCH 36/59] style: align formatting --- site/docs/plugins/commands.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 2dd5beb8a..de7864cf8 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -52,21 +52,23 @@ First of all, here's how you can import all the necessary types and classes the ```ts [TypeScript] import { CommandGroup, - commands, commandNotFound, + commands, type CommandsFlavor, } from "@grammyjs/commands"; ``` ```js [JavaScript] -const { CommandGroup, commands, commandNotFound } = require("@grammyjs/commands"); +const { CommandGroup, commands, commandNotFound } = require( + "@grammyjs/commands", +); ``` ```ts [Deno] import { CommandGroup, - commands, commandNotFound, + commands, type CommandsFlavor, } from "https://deno.land/x/grammy_commands/mod.ts"; ``` @@ -270,14 +272,14 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { import sayHi from "./say-hi.ts"; import sayBye from "./say-bye.ts"; import etc from "./another-command.ts"; -import { MyContext } from '../../types.ts' +import { MyContext } from "../../types.ts"; export const userCommands = new CommandGroup() .add([sayHi, sayBye]); ``` ```ts [say-hi.ts] -import { MyContext } from '../../types.ts' +import { MyContext } from "../../types.ts"; export default new Command("sayhi", "Greetings", async (ctx) => { await ctx.reply("Hello little User!"); From f0e2ef89999edf932bbd43890e6cdd42a5209dc0 Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Wed, 16 Oct 2024 16:28:21 +0200 Subject: [PATCH 37/59] fix: bad heroku links --- site/docs/es/hosting/heroku.md | 2 +- site/docs/hosting/heroku.md | 2 +- site/docs/id/hosting/heroku.md | 2 +- site/docs/ru/hosting/heroku.md | 2 +- site/docs/uk/hosting/heroku.md | 2 +- site/docs/zh/hosting/heroku.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/docs/es/hosting/heroku.md b/site/docs/es/hosting/heroku.md index 1453f12a3..fca44f525 100644 --- a/site/docs/es/hosting/heroku.md +++ b/site/docs/es/hosting/heroku.md @@ -273,7 +273,7 @@ Si se ejecuta con éxito y no imprime ningún error, nuestros archivos compilado ### Configurar el `Procfile` -Por el momento, `Heroku` tiene varios [tipos de dynos](https://devcenter.heroku.com/articles/dyno-types). +Por el momento, `Heroku` tiene varios [tipos de dynos](https://devcenter.heroku.com/articles/dynos#use-cases). Dos de ellos son: - **Web dynos**: diff --git a/site/docs/hosting/heroku.md b/site/docs/hosting/heroku.md index 42baf476e..d99a2c6ad 100644 --- a/site/docs/hosting/heroku.md +++ b/site/docs/hosting/heroku.md @@ -274,7 +274,7 @@ If it runs successfully and does not print any errors, our compiled files should ### Set up `Procfile` -For the time being, `Heroku` has several [types of dynos](https://devcenter.heroku.com/articles/dyno-types). +For the time being, `Heroku` has several [types of dynos](https://devcenter.heroku.com/articles/dynos#use-cases). Two of them are: - **Web dynos**: diff --git a/site/docs/id/hosting/heroku.md b/site/docs/id/hosting/heroku.md index 12db32abd..f61a80ebb 100644 --- a/site/docs/id/hosting/heroku.md +++ b/site/docs/id/hosting/heroku.md @@ -275,7 +275,7 @@ Jika berhasil dijalankan dan tidak ada pesan error yang muncul, file-file yang t ### Siapkan File `Procfile` -`Heroku` memiliki beberapa [jenis dynos](https://devcenter.heroku.com/articles/dyno-types). +`Heroku` memiliki beberapa [jenis dynos](https://devcenter.heroku.com/articles/dynos#use-cases). Dua diantaranya adalah: - **Web dynos**: diff --git a/site/docs/ru/hosting/heroku.md b/site/docs/ru/hosting/heroku.md index 137cc6813..5afe6fff1 100644 --- a/site/docs/ru/hosting/heroku.md +++ b/site/docs/ru/hosting/heroku.md @@ -274,7 +274,7 @@ npx tsc ### Установите `Procfile` -На данный момент у `Heroku` есть несколько [типов dyno](https://devcenter.heroku.com/articles/dyno-types). +На данный момент у `Heroku` есть несколько [типов dyno](https://devcenter.heroku.com/articles/dynos#use-cases). Два из них: - **Web dynos**: diff --git a/site/docs/uk/hosting/heroku.md b/site/docs/uk/hosting/heroku.md index 0243ee824..398943f35 100644 --- a/site/docs/uk/hosting/heroku.md +++ b/site/docs/uk/hosting/heroku.md @@ -274,7 +274,7 @@ npx tsc ### Налаштування `Procfile` -Наразі у `Heroku` є кілька [типів dyno](https://devcenter.heroku.com/articles/dyno-types). +Наразі у `Heroku` є кілька [типів dyno](https://devcenter.heroku.com/articles/dynos#use-cases). Два з них: - **Веб dyno**: diff --git a/site/docs/zh/hosting/heroku.md b/site/docs/zh/hosting/heroku.md index 1e1806537..3dd9efb69 100644 --- a/site/docs/zh/hosting/heroku.md +++ b/site/docs/zh/hosting/heroku.md @@ -271,7 +271,7 @@ npx tsc ### 设置 `Procfile` -目前 `Heroku` 有好几种 [dynos 类型](https://devcenter.heroku.com/articles/dyno-types)。 +目前 `Heroku` 有好几种 [dynos 类型](https://devcenter.heroku.com/articles/dynos#use-cases)。 其中两个是: - **Web dynos**: From ca5e99f4aa62f47674e388418d00d2255bb234b9 Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:33:59 -0300 Subject: [PATCH 38/59] apply a few nitpick changes Co-authored-by: KnorpelSenf --- site/docs/plugins/commands.md | 40 ++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index de7864cf8..203d46dd0 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -31,7 +31,7 @@ All of these features are made possible because you will define one or more cent Before we dive in, take a look at how you can register and handle a command with the plugin: -```js +```ts const myCommands = new CommandGroup(); myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); @@ -81,7 +81,7 @@ Now that that's settled, let's see how we can make our commands visible to our u Once you defined your commands with an instance of the `CommandGroup` class, you can call the `setCommands` method, which will register all the defined commands to your bot. -```js +```ts const myCommands = new CommandGroup(); myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hi there!")); @@ -213,10 +213,6 @@ src/ tsconfig.json ``` -::: tip Type Resolution -For the sake of brevity, we are assuming your `tsconfig` file is well-set to resolve the types from `types.d.ts` and have resolved every other necessary import. -::: - The following code group exemplifies how we could implement a developer only command group, and update the Telegram client Command menu accordingly. Make sure you take notice of the different patterns being use in the `admin.ts` and `group.ts` file-tabs. @@ -229,7 +225,7 @@ export type MyContext = Context & CommandsFlavor; ```ts [bot.ts] import { devCommands } from "./commands/admin.ts"; import { userCommands } from "./commands/users/group.ts"; -import { MyContext } from "./types.ts"; +import type { MyContext } from "./types.ts"; export const bot = new Bot("MyBotToken"); @@ -241,7 +237,7 @@ bot.use(devCommands); ```ts [admin.ts] import { userCommands } from './users/group.ts' -import { MyContext } from '../types.ts' +import type { MyContext } from '../types.ts' export const devCommands = new CommandGroup() @@ -249,7 +245,9 @@ devCommands.command('devlogin', 'Greetings', async (ctx, next) => { if (ctx.from?.id === ctx.env.DEVELOPER_ID) { await ctx.reply('Hi to me') await ctx.setMyCommands(userCommands, devCommands) - } else next() + } else { + await next() + } }) devCommands.command('usercount', 'Greetings', async (ctx, next) => { @@ -257,14 +255,18 @@ devCommands.command('usercount', 'Greetings', async (ctx, next) => { await ctx.reply( `Active users: ${/** Your business logic */}` ) - } else next() + } else { + await next() + } }) devCommands.command('devlogout', 'Greetings', async (ctx, next) => { if (ctx.from?.id === ctx.env.DEVELOPER_ID) { await ctx.reply('Bye to me') await ctx.setMyCommands(userCommands) - } else next() + } else { + await next() + } }) ``` @@ -272,14 +274,14 @@ devCommands.command('devlogout', 'Greetings', async (ctx, next) => { import sayHi from "./say-hi.ts"; import sayBye from "./say-bye.ts"; import etc from "./another-command.ts"; -import { MyContext } from "../../types.ts"; +import type { MyContext } from "../../types.ts"; export const userCommands = new CommandGroup() .add([sayHi, sayBye]); ``` ```ts [say-hi.ts] -import { MyContext } from "../../types.ts"; +import type { MyContext } from "../../types.ts"; export default new Command("sayhi", "Greetings", async (ctx) => { await ctx.reply("Hello little User!"); @@ -318,7 +320,7 @@ You don't even need to worry about calling `filter`, the `addToScope` method wil Here's an example of a scoped command: -```js +```ts const myCommands = new CommandGroup(); myCommands @@ -409,7 +411,7 @@ import { LanguageCodes } from "grammy/types"; myCommands.command( "chef", "Steak delivery", - async (ctx) => await ctx.reply("Steak on the plate!"), + (ctx) => ctx.reply("Steak on the plate!"), ) .localize( LanguageCodes.Spanish, @@ -424,7 +426,7 @@ const { LanguageCodes } = require("grammy/types"); myCommands.command( "chef", "Steak delivery", - async (ctx) => await ctx.reply("Steak on the plate!"), + (ctx) => ctx.reply("Steak on the plate!"), ) .localize( LanguageCodes.Spanish, @@ -439,7 +441,7 @@ import { LanguageCodes } from "https://deno.land/x/grammy/types.ts"; myCommands.command( "chef", "Steak delivery", - async (ctx) => await ctx.reply("Steak on the plate!"), + (ctx) => ctx.reply("Steak on the plate!"), ) .localize( LanguageCodes.Spanish, @@ -543,7 +545,7 @@ The recommended commands will only come from the `CommandGroup` instances you pa Let's use the previous knowledge to inspect the next example: -```js +```ts const myCommands = new CommandGroup(); myCommands.command("dad", "calls dad", () => {}, { prefix: "?" }) .localize("es", "papa", "llama a papa") @@ -613,7 +615,7 @@ All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest This feature is for those who are really looking to go wild, it allows you to create command handlers based on regular expressions instead of static strings, a basic example would look like: -```js +```ts myCommands .command( /delete_([a-zA-Z]+)/, From 24b59aefec92943e826e078df6a6361afec520fb Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Sat, 19 Oct 2024 16:08:41 +0200 Subject: [PATCH 39/59] make links more uniform --- site/docs/es/guide/introduction.md | 2 +- site/docs/guide/introduction.md | 2 +- site/docs/id/guide/introduction.md | 4 ++-- site/docs/ru/guide/introduction.md | 2 +- site/docs/uk/guide/introduction.md | 2 +- site/docs/zh/guide/introduction.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/site/docs/es/guide/introduction.md b/site/docs/es/guide/introduction.md index a65bff628..ba895ceec 100644 --- a/site/docs/es/guide/introduction.md +++ b/site/docs/es/guide/introduction.md @@ -184,7 +184,7 @@ Para poder ejecutar el archivo `bot.js`, tienes que tener instalado [Node.js](ht En resumen, esto es lo que tienes que hacer para Node.js: -1. Crea un archivo fuente `bot.ts` con código TypeScript, por ejemplo usando [VS Code](https://code.visualstudio.com/) (o cualquier otro editor de código). +1. Crea un archivo fuente `bot.ts` con código TypeScript, por ejemplo usando [VS Code](https://code.visualstudio.com) (o cualquier otro editor de código). 2. Compila el código ejecutando un comando en tu terminal. Esto genera un archivo llamado `bot.js`. 3. Ejecuta `bot.js` usando Node.js, de nuevo desde tu terminal. diff --git a/site/docs/guide/introduction.md b/site/docs/guide/introduction.md index 997b96b5d..49f58acb2 100644 --- a/site/docs/guide/introduction.md +++ b/site/docs/guide/introduction.md @@ -184,7 +184,7 @@ In order to run the `bot.js` file, you have to have [Node.js](https://nodejs.org In summary, this is what you have to do for Node.js: -1. Create a source file `bot.ts` with TypeScript code, e.g. using [VS Code](https://code.visualstudio.com/) (or any other code editor). +1. Create a source file `bot.ts` with TypeScript code, e.g. using [VS Code](https://code.visualstudio.com) (or any other code editor). 2. Compile the code by running a command in your terminal. This generates a file called `bot.js`. 3. Run `bot.js` using Node.js, again from your terminal. diff --git a/site/docs/id/guide/introduction.md b/site/docs/id/guide/introduction.md index 70842611f..78332a73c 100644 --- a/site/docs/id/guide/introduction.md +++ b/site/docs/id/guide/introduction.md @@ -146,7 +146,7 @@ Pertama-tama, [instal Deno](https://docs.deno.com/runtime/getting_started/instal Siapkan juga text editor yang sesuai untuk coding. Salah satu yang sesuai untuk Deno adalah Visual Studio Code, atau biasa disebut dengan VS Code. -Silahkan [diinstal](https://code.visualstudio.com/) juga. +Silahkan [diinstal](https://code.visualstudio.com) juga. Selanjutnya, kamu perlu menghubungkan Deno dan VS Code. Caranya sangat mudah: VS Code punya extension yang bisa melakukan semua hal tersebut secara otomatis. @@ -195,7 +195,7 @@ Untuk menjalankan file `bot.js`, kamu harus meng-install [Node.js](https://nodej Berikut tahap-tahap yang perlu dilakukan di Node.js: -1. Buat source file `bot.ts` menggunakan TypeScript, misalnya dengan menggunakan [VS Code](https://code.visualstudio.com/) (atau kode editor lainnya). +1. Buat source file `bot.ts` menggunakan TypeScript, misalnya dengan menggunakan [VS Code](https://code.visualstudio.com) (atau kode editor lainnya). 2. Compile kode dengan menjalankan perintah di terminal. Langkah ini akan menghasilkan file bernama `bot.js`. 3. Jalankan `bot.js` menggunakan Node.js, sekali lagi dari terminal. diff --git a/site/docs/ru/guide/introduction.md b/site/docs/ru/guide/introduction.md index 24f8acf40..7d0413a90 100644 --- a/site/docs/ru/guide/introduction.md +++ b/site/docs/ru/guide/introduction.md @@ -183,7 +183,7 @@ code ./my-bot В общем, вот что вам нужно сделать для Node.js: -1. Создайте исходный файл `bot.ts` с кодом TypeScript, например, с помощью [VS Code](https://code.visualstudio.com/) или любого другого редактора кода. +1. Создайте исходный файл `bot.ts` с кодом TypeScript, например, с помощью [VS Code](https://code.visualstudio.com) или любого другого редактора кода. 2. Скомпилируйте код, выполнив команду в терминале. В результате будет создан файл `bot.js`. 3. Запустите `bot.js` с помощью Node.js, опять же из терминала. diff --git a/site/docs/uk/guide/introduction.md b/site/docs/uk/guide/introduction.md index 6f1412c86..b6e66262c 100644 --- a/site/docs/uk/guide/introduction.md +++ b/site/docs/uk/guide/introduction.md @@ -188,7 +188,7 @@ code . Підсумовуючи, ось що вам потрібно зробити для Node.js: -1. Створіть вихідний файл `bot.ts` з кодом TypeScript, наприклад за допомогою [VS Code](https://code.visualstudio.com/) або будь-якого іншого редактора коду. +1. Створіть вихідний файл `bot.ts` з кодом TypeScript, наприклад за допомогою [VS Code](https://code.visualstudio.com) або будь-якого іншого редактора коду. 2. Скомпілюйте код, виконавши команду в терміналі. Це згенерує файл під назвою `bot.js`. 3. Запустіть `bot.js` за допомогою Node.js з вашого терміналу. diff --git a/site/docs/zh/guide/introduction.md b/site/docs/zh/guide/introduction.md index 6288f343e..9bfb4a106 100644 --- a/site/docs/zh/guide/introduction.md +++ b/site/docs/zh/guide/introduction.md @@ -186,7 +186,7 @@ code . 你将 Node.js 所要做的事情有下面这些: -1. 用 TypeScript 代码创建一个源文件 `bot.ts` ,例如使用 [VS Code](https://code.visualstudio.com/) (或任何其他代码编辑器)。 +1. 用 TypeScript 代码创建一个源文件 `bot.ts` ,例如使用 [VS Code](https://code.visualstudio.com) (或任何其他代码编辑器)。 2. 通过在你的终端运行一个命令来编译代码。这将生成一个名为 `bot.js` 的文件。 3. 同样从你的终端,使用 Node.js 运行 `bot.js`。 From a6f0d368dfd283744e9cc285ad4660b552e6873b Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:28:04 -0300 Subject: [PATCH 40/59] fix cre lib reference Co-authored-by: Roj --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 203d46dd0..4a6d8614c 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -7,7 +7,7 @@ next: false Command handling on steroids. -This plugin provides various features related to command handling that are not contained in the [command handling done by the grammY core library](../guide/commands). +This plugin provides various features related to command handling that are not contained in the [command handling done by the core library](../guide/commands). Here is a quick overview of what you get with this plugin: - Better code readability by encapsulating middleware with command definitions From 9c9037a0ecb63deac7149149ca9dc4ec5d7bde27 Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:10:39 -0300 Subject: [PATCH 41/59] add missing commas Co-authored-by: Roj --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 4a6d8614c..7eb0e098f 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -401,7 +401,7 @@ myCommands Add as many as you want! The plugin will take care of registering them for you when you call `myCommands.setCommands`. -For convenience grammY exports a `LanguageCodes` enum-like object, that you can use for a more idiomatic approach: +For convenience, grammY exports a `LanguageCodes` enum-like object that you can use for a more idiomatic approach: ::: code-group From 90d9ba19ef769ed48950f6b3144ba3d34dcb0f2a Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:11:21 -0300 Subject: [PATCH 42/59] fix phrasing and typos Co-authored-by: KnorpelSenf --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 7eb0e098f..eef0a61b5 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -537,7 +537,7 @@ If you want to opt-out of this behavior, you can pass the `ignoreLocalization` f It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. -The `commandNotFound` function will only trigger for updates which contains command-like-text similar to your registered commands. +The `commandNotFound` function will only trigger for updates which contain command-like text similar to your registered commands. For example, if you only have registered [commands with a custom prefix](#prefix) like `?`, it will trigger the handler for anything that looks like your commands, e.g: `?sayhi` but not `/definitely_a_command`. Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that look like `/regular` `/commands`. From 8598028f5c89803f255578e481df7f9fb3dd573f Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Thu, 31 Oct 2024 17:28:15 +0100 Subject: [PATCH 43/59] Fix links --- site/docs/es/hosting/zeabur-deno.md | 2 +- site/docs/es/hosting/zeabur-nodejs.md | 2 +- site/docs/hosting/zeabur-deno.md | 2 +- site/docs/hosting/zeabur-nodejs.md | 2 +- site/docs/id/hosting/zeabur-deno.md | 2 +- site/docs/id/hosting/zeabur-nodejs.md | 2 +- site/docs/ru/hosting/zeabur-deno.md | 2 +- site/docs/ru/hosting/zeabur-nodejs.md | 2 +- site/docs/uk/hosting/zeabur-deno.md | 2 +- site/docs/uk/hosting/zeabur-nodejs.md | 2 +- site/docs/zh/hosting/zeabur-deno.md | 2 +- site/docs/zh/hosting/zeabur-nodejs.md | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/site/docs/es/hosting/zeabur-deno.md b/site/docs/es/hosting/zeabur-deno.md index 493d15875..f497cf919 100644 --- a/site/docs/es/hosting/zeabur-deno.md +++ b/site/docs/es/hosting/zeabur-deno.md @@ -56,7 +56,7 @@ bot.start(); > Nota: Obtén tu bot token con [@BotFather](https://t.me/BotFather) en Telegram, y establécelo como variable de entorno `TELEGRAM_BOT_TOKEN` en Zeabur. > -> Puedes consultar [este tutorial](https://zeabur.com/docs/deploy/variables) para establecer variables de entorno en Zeabur. +> Puedes consultar [este tutorial](https://zeabur.com/docs/en-US/deploy/variables) para establecer variables de entorno en Zeabur. Luego ejecuta el siguiente comando para iniciar tu bot: diff --git a/site/docs/es/hosting/zeabur-nodejs.md b/site/docs/es/hosting/zeabur-nodejs.md index 1987ab16e..95241ba85 100644 --- a/site/docs/es/hosting/zeabur-nodejs.md +++ b/site/docs/es/hosting/zeabur-nodejs.md @@ -61,7 +61,7 @@ bot.start(); > Nota: Obtén tu bot token con [@BotFather](https://t.me/BotFather) en Telegram, y establécelo como variable de entorno `TELEGRAM_BOT_TOKEN` en Zeabur. > -> Puedes consultar [este tutorial](https://zeabur.com/docs/deploy/variables) para establecer variables de entorno en Zeabur. +> Puedes consultar [este tutorial](https://zeabur.com/docs/en-US/deploy/variables) para establecer variables de entorno en Zeabur. Ahora el directorio raíz de tu proyecto debería verse así: diff --git a/site/docs/hosting/zeabur-deno.md b/site/docs/hosting/zeabur-deno.md index a7f3ff5a2..6a0526f6b 100644 --- a/site/docs/hosting/zeabur-deno.md +++ b/site/docs/hosting/zeabur-deno.md @@ -56,7 +56,7 @@ bot.start(); > Note: Get your bot token with [@BotFather](https://t.me/BotFather) on Telegram, and set is as an environment variable `TELEGRAM_BOT_TOKEN` in Zeabur. > -> You can check out [this tutorial](https://zeabur.com/docs/deploy/variables) for setting environment variables in Zeabur. +> You can check out [this tutorial](https://zeabur.com/docs/en-US/deploy/variables) for setting environment variables in Zeabur. Then run the following command to start your bot: diff --git a/site/docs/hosting/zeabur-nodejs.md b/site/docs/hosting/zeabur-nodejs.md index 70618d49e..3a86176fe 100644 --- a/site/docs/hosting/zeabur-nodejs.md +++ b/site/docs/hosting/zeabur-nodejs.md @@ -65,7 +65,7 @@ bot.start(); > Note: Get your bot token with [@BotFather](https://t.me/BotFather) on Telegram, and set is as an environment variable `TELEGRAM_BOT_TOKEN` in Zeabur. > -> You can check out [this tutorial](https://zeabur.com/docs/deploy/variables) for setting environment variables in Zeabur. +> You can check out [this tutorial](https://zeabur.com/docs/en-US/deploy/variables) for setting environment variables in Zeabur. Now your project's root directory should now look like this: diff --git a/site/docs/id/hosting/zeabur-deno.md b/site/docs/id/hosting/zeabur-deno.md index 972cc18f8..0862cc46b 100644 --- a/site/docs/id/hosting/zeabur-deno.md +++ b/site/docs/id/hosting/zeabur-deno.md @@ -56,7 +56,7 @@ bot.start(); > Catatan: Ambil token bot kamu di [@BotFather](https://t.me/BotFather), lalu buat sebuah environment variable di Zeabur bernama `TOKEN_BOT_TELEGRAM` yang memuat token bot tersebut. > -> Panduan untuk menyetel environment variable di Zeabur bisa dilihat di [tutorial berikut](https://zeabur.com/docs/deploy/variables). +> Panduan untuk menyetel environment variable di Zeabur bisa dilihat di [tutorial berikut](https://zeabur.com/docs/en-US/deploy/variables). Jika sudah, jalankan perintah berikut untuk memulai bot kamu: diff --git a/site/docs/id/hosting/zeabur-nodejs.md b/site/docs/id/hosting/zeabur-nodejs.md index 606188510..579fdb006 100644 --- a/site/docs/id/hosting/zeabur-nodejs.md +++ b/site/docs/id/hosting/zeabur-nodejs.md @@ -65,7 +65,7 @@ bot.start(); > Catatan: Ambil token bot kamu di [@BotFather](https://t.me/BotFather), lalu buat sebuah environment variable di Zeabur bernama `TOKEN_BOT_TELEGRAM` yang memuat token bot tersebut. > -> Panduan untuk menyetel environment variable di Zeabur bisa dilihat di [tutorial berikut](https://zeabur.com/docs/deploy/variables). +> Panduan untuk menyetel environment variable di Zeabur bisa dilihat di [tutorial berikut](https://zeabur.com/docs/en-US/deploy/variables). Sekarang, direktori root proyek kamu seharusnya memiliki struktur seperti ini: diff --git a/site/docs/ru/hosting/zeabur-deno.md b/site/docs/ru/hosting/zeabur-deno.md index 0bb96a4b6..9dd7d8a1d 100644 --- a/site/docs/ru/hosting/zeabur-deno.md +++ b/site/docs/ru/hosting/zeabur-deno.md @@ -55,7 +55,7 @@ bot.start(); ``` > Примечание: Получите токен бота с помощью [@BotFather](https://t.me/BotFather) в Telegram и установите его в качестве переменной окружения `TELEGRAM_BOT_TOKEN` в Zeabur. -> Вы можете ознакомиться с [этим руководством](https://zeabur.com/docs/deploy/variables) по настройке переменных окружения в Zeabur. +> Вы можете ознакомиться с [этим руководством](https://zeabur.com/docs/en-US/deploy/variables) по настройке переменных окружения в Zeabur. Затем выполните следующую команду для запуска бота: diff --git a/site/docs/ru/hosting/zeabur-nodejs.md b/site/docs/ru/hosting/zeabur-nodejs.md index 52454202d..0ce2f7ecd 100644 --- a/site/docs/ru/hosting/zeabur-nodejs.md +++ b/site/docs/ru/hosting/zeabur-nodejs.md @@ -64,7 +64,7 @@ bot.start(); ``` > Примечание: Получите токен бота с помощью [@BotFather](https://t.me/BotFather) в Telegram и установите его в качестве переменной окружения `TELEGRAM_BOT_TOKEN` в Zeabur. -> Вы можете ознакомиться с [этим руководством](https://zeabur.com/docs/deploy/variables) по настройке переменных окружения в Zeabur. +> Вы можете ознакомиться с [этим руководством](https://zeabur.com/docs/en-US/deploy/variables) по настройке переменных окружения в Zeabur. Теперь корневая директория вашего проекта должна выглядеть следующим образом: diff --git a/site/docs/uk/hosting/zeabur-deno.md b/site/docs/uk/hosting/zeabur-deno.md index 5459ac568..18bfb19b9 100644 --- a/site/docs/uk/hosting/zeabur-deno.md +++ b/site/docs/uk/hosting/zeabur-deno.md @@ -56,7 +56,7 @@ bot.start(); > Примітка: отримайте токен бота за допомогою [@BotFather](https://t.me/BotFather) в Telegram і встановіть його як змінну оточення `TELEGRAM_BOT_TOKEN` в Zeabur. > -> Ви можете ознайомитися з [цим посібником](https://zeabur.com/docs/deploy/variables) щодо налаштування змінних середовища в Zeabur. +> Ви можете ознайомитися з [цим посібником](https://zeabur.com/docs/en-US/deploy/variables) щодо налаштування змінних середовища в Zeabur. Потім виконайте наступну команду, щоб запустити бота: diff --git a/site/docs/uk/hosting/zeabur-nodejs.md b/site/docs/uk/hosting/zeabur-nodejs.md index 69de61908..a045074bc 100644 --- a/site/docs/uk/hosting/zeabur-nodejs.md +++ b/site/docs/uk/hosting/zeabur-nodejs.md @@ -65,7 +65,7 @@ bot.start(); > Примітка: отримайте токен бота за допомогою [@BotFather](https://t.me/BotFather) в Telegram і встановіть його як змінну оточення `TELEGRAM_BOT_TOKEN` в Zeabur. > -> Ви можете ознайомитися з [цим посібником](https://zeabur.com/docs/deploy/variables) щодо налаштування змінних середовища в Zeabur. +> Ви можете ознайомитися з [цим посібником](https://zeabur.com/docs/en-US/deploy/variables) щодо налаштування змінних середовища в Zeabur. Тепер кореневий каталог вашого проєкту має виглядати так: diff --git a/site/docs/zh/hosting/zeabur-deno.md b/site/docs/zh/hosting/zeabur-deno.md index 55a0c8bfc..44c042714 100644 --- a/site/docs/zh/hosting/zeabur-deno.md +++ b/site/docs/zh/hosting/zeabur-deno.md @@ -56,7 +56,7 @@ bot.start(); > 注意:在 Telegram 上使用 [@BotFather](https://t.me/BotFather) 获取你的 bot token,并在 Zeabur 中将其设置为环境变量 `TELEGRAM_BOT_TOKEN`。 > -> 你可以在 [这个教程](https://zeabur.com/docs/deploy/variables) 中查看如何在 Zeabur 中设置环境变量。 +> 你可以在 [这个教程](https://zeabur.com/docs/zh-CN/deploy/variables) 中查看如何在 Zeabur 中设置环境变量。 然后运行以下命令来启动你的 bot: diff --git a/site/docs/zh/hosting/zeabur-nodejs.md b/site/docs/zh/hosting/zeabur-nodejs.md index b6cacc2f0..d8e5944ca 100644 --- a/site/docs/zh/hosting/zeabur-nodejs.md +++ b/site/docs/zh/hosting/zeabur-nodejs.md @@ -65,7 +65,7 @@ bot.start(); > 注意:在 Telegram 上使用 [@BotFather](https://t.me/BotFather) 获取你的 bot token,并在 Zeabur 中将其设置为环境变量 `TELEGRAM_BOT_TOKEN`。 > -> 你可以在 [这个教程](https://zeabur.com/docs/deploy/variables) 中查看如何在 Zeabur 中设置环境变量。 +> 你可以在 [这个教程](https://zeabur.com/docs/zh-CN/deploy/variables) 中查看如何在 Zeabur 中设置环境变量。 现在你的项目的根目录应该如下所示: From 2bb1f1840ad89f08e4f99d60f4f9aba3f02c262e Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Fri, 1 Nov 2024 08:18:30 -0300 Subject: [PATCH 44/59] update text about imports Co-authored-by: Roj --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index eef0a61b5..c3ef657a1 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -75,7 +75,7 @@ import { ::: -Now that that's settled, let's see how we can make our commands visible to our users. +Now that the imports are settled, let's see how we can make our commands visible to our users. ## User Command Menu Setting From ea9e8bfa3f433fee7b68309e80e9e3de193c36d2 Mon Sep 17 00:00:00 2001 From: MasedMSD <68379695+MasedMSD@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:50:40 +0500 Subject: [PATCH 45/59] sync Russian --- site/docs/.vitepress/configs/locales/ru.ts | 4 + site/docs/ru/plugins/commands.md | 622 ++++++++++++++++++++- 2 files changed, 624 insertions(+), 2 deletions(-) diff --git a/site/docs/.vitepress/configs/locales/ru.ts b/site/docs/.vitepress/configs/locales/ru.ts index 6781822b2..bd6d8d900 100644 --- a/site/docs/.vitepress/configs/locales/ru.ts +++ b/site/docs/.vitepress/configs/locales/ru.ts @@ -212,6 +212,10 @@ const pluginOfficial = { text: "Пользователи чата (chat-members)", link: "/ru/plugins/chat-members", }, + { + text: "Команды (commands)", + link: "/plugins/commands", + }, ], }; diff --git a/site/docs/ru/plugins/commands.md b/site/docs/ru/plugins/commands.md index b7986732a..005a5c7db 100644 --- a/site/docs/ru/plugins/commands.md +++ b/site/docs/ru/plugins/commands.md @@ -5,10 +5,628 @@ next: false # Команды (`commands`) -Скоро будет, возвращайтесь позднее. +Обработка команд на стероидах. + +Этот плагин предоставляет различные возможности для работы с командами, которых нет в [основной библиотеке для обработки команд](../guide/commands). +Вот краткий обзор возможностей, которые вы получаете с этим плагином: + +- Улучшенная читаемость кода за счет инкапсуляции middleware с определениями команд +- Синхронизация меню команд через `setMyCommands` +- Улучшенная группировка и организация команд +- Возможность ограничить область действия команды, например: доступ только + администраторам группы или в каналах и т.д. +- Создание переводов для команды +- Функция `Возможно, вы имели в виду ...?`, которая находит ближайшую команду при + ошибочном вводе пользователем +- Нечувствительность к регистру при сравнении команд +- Настройка пользовательского поведения для команд, которые явно упоминают вашего бота, + например: `/start@your_bot` +- Пользовательские префиксы для команд, например: `+`, `?` или любой другой символ вместо `/` +- Поддержка команд, которые находятся не в начале сообщения +- Команды с использованием регулярных выражений! + +Все эти возможности реализуются благодаря тому, что вы будете определять одну или несколько центральных структур команд, описывающих команды вашего бота. + +## Основное использование + +Прежде чем углубляться, давайте посмотрим, как зарегистрировать и обработать команду с помощью плагина: + +```typescript +const myCommands = new CommandGroup(); + +myCommands.command("hello", "Поздороваться", (ctx) => ctx.reply(`Привет, мир!`)); + +bot.use(myCommands); +``` + +Эта команда регистрирует новую команду `/hello` для вашего бота, которая будет обрабатываться переданным middleware. + +Теперь давайте рассмотрим дополнительные инструменты, которые предоставляет этот плагин. + +## Импортирование + +Прежде всего, вот как вы можете импортировать все необходимые типы и классы, которые предоставляет плагин. + +::: code-group + +```ts [TypeScript] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "@grammyjs/commands"; +``` + +```js [JavaScript] +const { CommandGroup, commands, commandNotFound } = require( + "@grammyjs/commands", +); +``` + +```ts [Deno] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "https://deno.land/x/grammy_commands/mod.ts"; +``` + +::: + +Теперь, когда с импортом разобрались, давайте посмотрим, как сделать наши команды видимыми для пользователей. + +## Настройка пользовательского меню команд + +После того как вы определили свои команды с помощью экземпляра класса `CommandGroup`, вы можете вызвать метод `setCommands`, который зарегистрирует все заданные команды для вашего бота. + +```typescript +const myCommands = new CommandGroup(); + +myCommands.command("hello", "Поздороваться", (ctx) => ctx.reply("Привет!")); +myCommands.command("start", "Запустить бота", (ctx) => ctx.reply("Запуск...")); + +bot.use(myCommands); + +await myCommands.setCommands(bot); +``` + +Это позволит отображать каждую зарегистрированную вами команду в меню в приватном чате с вашим ботом или когда пользователи набирают `/` в чате, где присутствует ваш бот. + +### Контекстные команды + +Что, если вы хотите, чтобы некоторые команды отображались только для определённых пользователей? Например, представьте, что у вас есть команды `login` и `logout`. +Команда `login` должна отображаться только для незарегистрированных пользователей, и наоборот. +Вот как это можно реализовать с помощью плагина команд: + +::: code-group + +```typescript [TypeScript] +// Используйте расширитель для создания собственного контекста +type MyContext = Context & CommandsFlavor; + +// Используйте новый контекст для создания экземпляра бота +const bot = new Bot("токен"); + +// Регистрируем контекстную команду +bot.use(commands()); + +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); + +loggedOutCommands.command( + "login", + "Начать сессию с ботом", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Добро пожаловать! Сессия начата!"); + }, +); + +loggedInCommands.command( + "logout", + "Завершить сессию с ботом", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("До свидания :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// По умолчанию пользователи не авторизованы, +// поэтому можно установить команды для незарегистрированных +await loggedOutCommands.setCommands(bot); +``` + +```javascript [JavaScript] +// Регистрируем контекстную команду +bot.use(commands()); + +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); + +loggedOutCommands.command( + "login", + "Начать сессию с ботом", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Добро пожаловать! Сессия начата!"); + }, +); + +loggedInCommands.command( + "logout", + "Завершить сессию с ботом", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("До свидания :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// По умолчанию пользователи не авторизованы, +// поэтому можно установить команды для незарегистрированных +await loggedOutCommands.setCommands(bot); +``` + +::: + +Таким образом, когда пользователь вызывает `/login`, его список команд изменится и будет содержать только команду `logout`. +Удобно, правда? + +::: danger Ограничения на имена команд +Как указано в [документации API Telegram Bot](https://core.telegram.org/bots/api#botcommand), имена команд могут состоять только из: + +> 1-32 символов. +> Разрешены только строчные английские буквы, цифры и подчеркивания. + +Таким образом, вызов `setCommands` или `setMyCommands` с именем команды, не соответствующим нижнему регистру, вызовет исключение. +Команды, не соответствующие этим правилам, всё равно могут быть зарегистрированы, использованы и обработаны, но не будут отображаться в меню пользователя. +::: + +**Учтите**, что `setCommands` и `setMyCommands` влияют только на отображаемые команды в меню команд пользователя и не ограничивают доступ к ним. +Вы узнаете, как реализовать ограничение доступа к командам в разделе [Команды с областью видимости](#команды-с-областью-видимости). + +### Группировка команд + +Поскольку мы можем разделять и группировать команды в разные экземпляры, это позволяет намного более удобно организовывать файлы команд. + +Допустим, мы хотим создать команды, доступные только для разработчиков. +Мы можем реализовать это с помощью следующей структуры кода: + +```ascii +src/ +├─ commands/ +│ ├─ admin.ts +│ ├─ users/ +│ │ ├─ group.ts +│ │ ├─ say-hi.ts +│ │ ├─ say-bye.ts +│ │ ├─ ... +├─ bot.ts +├─ types.ts +tsconfig.json +``` + +Следующий блок кода демонстрирует, как можно реализовать группу команд, доступных только для разработчиков, и обновить меню команд в клиенте Telegram соответствующим образом. +Обратите внимание на разные шаблоны, используемые в файлах `admin.ts` и `group.ts`. + +::: code-group + +```ts [types.ts] +export type MyContext = Context & CommandsFlavor; +``` + +```ts [bot.ts] +import { devCommands } from "./commands/admin.ts"; +import { userCommands } from "./commands/users/group.ts"; +import type { MyContext } from "./types.ts"; + +export const bot = new Bot("токен"); + +bot.use(commands()); + +bot.use(userCommands); +bot.use(devCommands); +``` + +```ts [admin.ts] +import { userCommands } from './users/group.ts' +import type { MyContext } from '../types.ts' + +export const devCommands = new CommandGroup() + +devCommands.command('devlogin', 'Приветствие', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Привет мне') + await ctx.setMyCommands(userCommands, devCommands) + } else { + await next() + } +}) + +devCommands.command('usercount', 'Приветствие', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply( + `Активные пользователи: ${/** Ваша логика здесь */}` + ) + } else { + await next() + } +}) + +devCommands.command('devlogout', 'Приветствие', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Пока мне') + await ctx.setMyCommands(userCommands) + } else { + await next() + } + }) +``` + +```ts [group.ts] +import sayHi from "./say-hi.ts"; +import sayBye from "./say-bye.ts"; +import etc from "./another-command.ts"; +import type { MyContext } from "../../types.ts"; + +export const userCommands = new CommandGroup() + .add([sayHi, sayBye]); +``` + +```ts [say-hi.ts] +import type { MyContext } from "../../types.ts"; + +export default new Command("sayhi", "Приветствие", async (ctx) => { + await ctx.reply("Привет, маленький пользователь!"); +}); +``` + +::: + +Вы заметили, что вы можете регистрировать отдельные команды в экземпляр `CommandGroup` с помощью метода `.add` или напрямую через метод `.command(...)`? +Это позволяет создавать как структуру из одного файла, как в файле `admin.ts`, так и более распределенную файловую структуру, как в файле `group.ts`. + +::: tip Всегда используйте группы команд + +При создании и экспорте команд с использованием конструктора `Command`, обязательно регистрируйте их в экземпляре `CommandGroup` с помощью метода `.add`. +Без этого они бесполезны, так что не забудьте сделать это на каком-то этапе. + +::: + +Плагин также требует, чтобы для заданного `CommandGroup` и его соответствующих `Commands` использовался один и тот же тип контекста, что помогает избежать подобных ошибок на раннем этапе! + +Сочетание этих знаний с информацией из следующего раздела выведет вашу работу с командами на новый уровень. + +## Команды с областью видимости + +Знаете ли вы, что можно показывать разные команды в разных чатах в зависимости от типа чата, языка и даже статуса пользователя в группе? Это то, что Telegram называет [**Области видимости команд**](https://core.telegram.org/bots/features#command-scopes). + +Области видимости команд --- это отличная функция, но использовать её вручную может быть очень сложно, поскольку трудно отслеживать все области и команды, которые они предоставляют. +Кроме того, при использовании только областей команд вам приходится вручную добавлять фильтрацию внутри каждой команды, чтобы убедиться, что они будут выполняться только для нужных областей видимости. +Синхронизировать эти два момента бывает непросто, и именно поэтому существует этот плагин. +Посмотрите, как это делается. + +Класс `Command`, возвращаемый методом `command`, предоставляет метод под названием `addToScope`. +Этот метод принимает в качестве параметров [BotCommandScope](/ref/types/botcommandscope) вместе с одним или несколькими обработчиками и регистрирует эти обработчики для выполнения в указанной области. + +Вам даже не нужно беспокоиться о вызове `filter`, метод `addToScope` гарантирует, что ваш обработчик будет вызываться только в нужном контексте. + +Вот пример команды с определённой областью: + +```ts +const myCommands = new CommandGroup(); + +myCommands + .command("start", "Инициализирует конфигурацию бота") + .addToScope( + { type: "all_private_chats" }, + (ctx) => ctx.reply(`Привет, ${ctx.chat.first_name}!`), + ) + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply(`Привет, участники ${ctx.chat.title}!`), + ); +``` + +Команду `start` теперь можно вызывать как из личных, так и из групповых чатов, и ответ будет отличаться в зависимости от того, откуда поступил вызов. +Теперь, если вызвать `myCommands.setCommands`, команда `start` будет зарегистрирована как в личных, так и в групповых чатах. + +Вот пример команды, доступной только администраторам группы. + +```js +adminCommands + .command("secret", "Только для админов") + .addToScope( + { type: "all_chat_administrators" }, + (ctx) => ctx.reply("Бесплатный торт!"), + ); +``` + +А вот пример команды, доступной только в группах + +```js +myCommands + .command("fun", "Смех") + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply("Хаха"), + ); +``` + +Обратите внимание, что при вызове метода `command` создаётся новая команда. +Если вы добавите обработчик, он будет применяться к области этой команды `по умолчанию`. +Вызов `addToScope` для этой команды добавит новый обработчик, который будет фильтроваться по заданной области видимости. +Посмотрите на этот пример. + +```ts +myCommands + .command( + "default", + "Команда по умолчанию", + // Эта функция будет вызываться, если вы не находитесь в групповом чате или если пользователь не является администратором + (ctx) => ctx.reply("Привет из области видимости по умолчанию"), + ) + .addToScope( + { type: "all_group_chats" }, + // Эта функция будет вызываться только для не админов в группе + (ctx) => ctx.reply("Привет, групповой чат!"), + ) + .addToScope( + { type: "all_chat_administrators" }, + // Эта функция будет вызываться для администраторов групп, когда они находятся внутри этой группы + (ctx) => ctx.reply("Привет, админ!"), + ); +``` + +## Переводы команд + +Ещё одной мощной функцией является возможность задавать разные названия для одной и той же команды и соответствующие описания в зависимости от языка пользователя. +Плагин команд упрощает это с помощью метода `localize`. +Вот пример: + +```js +myCommands + // Сначала нужно установить название и описание по умолчанию + .command("hello", "Поздороваться") + // А затем можно задать локализованные варианты + .localize("pt", "ola", "Dizer olá"); +``` + +Добавьте столько вариантов, сколько хотите! Плагин сам позаботится об их регистрации, когда вы вызовете `myCommands.setCommands`. + +Для удобства grammY экспортирует объект, аналогичный перечислению `LanguageCodes`, который можно использовать для более идиоматического подхода: + +::: code-group + +```ts [TypeScript] +import { LanguageCodes } from "grammy/types"; + +myCommands.command( + "chef", + "Доставка стейка", + (ctx) => ctx.reply("Стейк на тарелке!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + ); +``` + +```js [JavaScript] +const { LanguageCodes } = require("grammy/types"); + +myCommands.command( + "chef", + "Доставка стейка", + (ctx) => ctx.reply("Стейк на тарелке!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + ); +``` + +```ts [Deno] +import { LanguageCodes } from "https://deno.land/x/grammy/types.ts"; + +myCommands.command( + "chef", + "Доставка стейка", + (ctx) => ctx.reply("Стейк на тарелке!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + ); +``` + +::: + +### Локализация команд с помощью плагина Интернационализации + +Если вы хотите, чтобы локализованные имена команд и их описания хранились в файлах `.ftl`, воспользуйтесь следующей идеей: + +```ts +function addLocalizations(command: Command) { + i18n.locales.forEach((locale) => { + command.localize( + locale, + i18n.t(locale, `${command.name}.command`), + i18n.t(locale, `${command.name}.description`), + ); + }); + return command; +} + +myCommands.commands.forEach(addLocalizations); +``` + +## Поиск ближайшей команды + +Хотя Telegram может автоматически подставляет зарегистрированные команды, иногда пользователи вводят их вручную и допускают ошибки. +Плагин команд помогает справиться с этим, предлагая команду, которую пользователь, возможно, хотел ввести с самого начала. +Он совместим с пользовательскими префиксами, так что о них можно не беспокоиться, и его использование довольно просто: + +::: code-group + +```ts [TypeScript] +// Используйте расширитель для создания собственного контекста +type MyContext = Context & CommandsFlavor; + +// Используйте новый контекст для инициализации бота +const bot = new Bot("токен"); +const myCommands = new CommandGroup(); + +// ... Регистрируем команды + +bot + // Проверяем, существует ли команда + .filter(commandNotFound(myCommands)) + // Если да, значит, она не была обработана нашими командами + .use(async (ctx) => { + // Мы нашли возможное совпадение + if (ctx.commandSuggestion) { + await ctx.reply( + `Хм... Я не знаю такой команды. Возможно, вы имели в виду ${ctx.commandSuggestion}?`, + ); + } + + // Похоже, ничего не похоже на то, что ввёл пользователь + await ctx.reply("Упс... Я не знаю такой команды :/"); + }); +``` + +```js [JavaScript] +// Используйте новый контекст для инициализации бота +const bot = new Bot("токен"); +const myCommands = new CommandGroup(); + +// ... Регистрируем команды + +bot + // Проверяем, существует ли команда + .filter(commandNotFound(myCommands)) + // Если да, значит, она не была обработана нашими командами + .use(async (ctx) => { + // Мы нашли возможное совпадение + if (ctx.commandSuggestion) { + await ctx.reply( + `Хм... Я не знаю такой команды. Возможно, вы имели в виду ${ctx.commandSuggestion}?`, + ); + } + + // Похоже, ничего не похоже на то, что ввёл пользователь + await ctx.reply("Упс... Я не знаю такой команды :/"); + }); +``` + +::: + +Под капотом, `commandNotFound` использует метод контекста `getNearestCommand`, который по умолчанию отдаёт приоритет командам, соответствующим языку пользователя. +Если вы хотите отключить такое поведение, установите параметр `ignoreLocalization` в значение `true`. +Можно искать по нескольким экземплярам `CommandGroup`, и `ctx.commandSuggestion` будет содержать наиболее подходящую команду, если таковая есть. +Также можно установить флаг `ignoreCase`, чтобы игнорировать регистр при поиске похожей команды, и флаг `similarityThreshold`, который контролирует, насколько название команды должно быть похоже на ввод пользователя, чтобы быть рекомендованным. + +Функция `commandNotFound` будет срабатывать только для обновлений, содержащих текст, похожий на зарегистрированные команды. +Например, если у вас зарегистрированы только [команды с пользовательским префиксом](#prefix), например, `?`, она вызовет обработчик для всего, что похоже на ваши команды, например: `?sayhi`, но не для `/definitely_a_command`. +Также работает и обратное: если у вас зарегистрированы только команды с префиксом по умолчанию, она сработает только на обновления, которые выглядят как `/regular` или `/commands`. + +Рекомендуемые команды будут исходить только от экземпляров `CommandGroup`, переданных функции. Поэтому можно проверять команды, применяя несколько фильтров по отдельности. + +Используем полученные знания для рассмотрения следующего примера: + +```ts +const myCommands = new CommandGroup(); +myCommands.command("dad", "звонит папе", () => {}, { prefix: "?" }) + .localize("es", "papa", "llama a papa") + .localize("fr", "pere", "appelle papa"); + +const otherCommands = new CommandGroup(); +otherCommands.command("bread", "съесть тост", () => {}) + .localize("es", "pan", "come un pan") + .localize("fr", "pain", "manger du pain"); + +// Регистрируем каждую языковую группу команд + +// Допустим, пользователь — француз и ввёл /Papi +bot + // этот фильтр сработает для всех команд, похожих на '/regular' или '?custom' + .filter(commandNotFound([myCommands, otherCommands], { + ignoreLocalization: true, + ignoreCase: true, + })) + .use(async (ctx) => { + ctx.commandSuggestion === "?papa"; // возвращает true + }); +``` + +Если бы `ignoreLocalization` был ложным, мы бы получили "`ctx.commandSuggestion` equals `/pain`". +Мы могли бы добавить больше фильтров, подобных приведённому выше, с разными параметрами или `CommandGroups` для проверки. +Возможностей множество! + +## Опции команд + +Существует несколько опций, которые можно задать для каждой команды, для каждой области или глобально для экземпляра `CommandGroup`. +Эти опции позволяют гибко настраивать, как ваш бот обрабатывает команды. + +### ignoreCase + +По умолчанию команды будут сопоставляться с пользовательским вводом с учётом регистра. +Установив этот флаг, команда с именем `/dandy` будет воспринимать `/DANDY` так же, как `/dandY` или любую другую вариацию, различающуюся только регистром. + +### targetedCommands + +При вызове команды пользователи могут упомянуть вашего бота, например: `/command@bot_username`. С помощью параметра `targetedCommands` можно задать, как бот будет обрабатывать такие команды. +Доступны три варианта поведения: + +- `ignored`: Игнорирует команды, которые упоминают бота +- `optional`: Обрабатывает команды как с упоминанием бота, так и без него +- `required`: Обрабатывает только команды, в которых упоминается бот + +### prefix + +В настоящее время Telegram распознает только команды, начинающиеся с символа `/`, и, соответственно, [обработка команд в основной библиотеке grammY](../guide/commands) также выполняется с этим префиксом. +Однако иногда может потребоваться использовать для бота другой префикс. +Это становится возможным благодаря параметру `prefix`, которая позволяет плагину команд распознавать команды с указанным префиксом. + +Если вам нужно получить сущности `botCommand` из обновления и требуется, чтобы они учитывали зарегистрированный вами пользовательский префикс, существует метод, специально предназначенный для этого --- `ctx.getCommandEntities(вашиКоманды)`, который возвращает тот же интерфейс, что и `ctx.entities('bot_command')`. + +:::tip +Команды с пользовательскими префиксами не могут быть показаны в меню команд. +::: + +### matchOnlyAtStart + +При [обработке команд](../guide/commands) основная библиотека grammY распознает команды только в том случае, если они начинаются с первого символа сообщения. +Однако плагин команд позволяет реагировать на команды, расположенные в середине текста сообщения или в его конце --- это не имеет значения! +Всё, что нужно сделать --- установить параметр `matchOnlyAtStart` на `false`, и плагин позаботится обо всём остальном. + +## Команды с использованием регулярных выражений + +Эта функция подходит для тех, кто хочет задать более гибкие команды, поскольку она позволяет создавать обработчики команд на основе регулярных выражений вместо статических строк. Пример простейшего использования: + +```ts +myCommands + .command( + /delete_([a-zA-Z]+)/, + (ctx) => ctx.reply(`Удаление ${ctx.msg?.text?.split("_")[1]}`), + ); +``` + +Этот обработчик команды сработает как на `/delete_me`, так и на `/delete_you`, и ответит "Удаление me" в первом случае и "Удаление you" во втором, но не сработает на `/delete_` или `/delete_123xyz`, пройдя мимо, как если бы его и не было. ## Краткая информация о плагине - Название: `commands` - [Исходник](https://github.com/grammyjs/commands) -- Документация +- [Ссылка](/ref/commands/) From 367a5ba0e2b91f2ec85e85bb9d6a5405fe272395 Mon Sep 17 00:00:00 2001 From: MasedMSD <68379695+MasedMSD@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:54:50 +0500 Subject: [PATCH 46/59] fix fmt & link --- site/docs/ru/plugins/commands.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/site/docs/ru/plugins/commands.md b/site/docs/ru/plugins/commands.md index 005a5c7db..f6a4f8d40 100644 --- a/site/docs/ru/plugins/commands.md +++ b/site/docs/ru/plugins/commands.md @@ -16,10 +16,10 @@ next: false - Возможность ограничить область действия команды, например: доступ только администраторам группы или в каналах и т.д. - Создание переводов для команды -- Функция `Возможно, вы имели в виду ...?`, которая находит ближайшую команду при +- Функция `Возможно, вы имели в виду ...?`, которая находит ближайшую команду при ошибочном вводе пользователем - Нечувствительность к регистру при сравнении команд -- Настройка пользовательского поведения для команд, которые явно упоминают вашего бота, +- Настройка пользовательского поведения для команд, которые явно упоминают вашего бота, например: `/start@your_bot` - Пользовательские префиксы для команд, например: `+`, `?` или любой другой символ вместо `/` - Поддержка команд, которые находятся не в начале сообщения @@ -34,7 +34,11 @@ next: false ```typescript const myCommands = new CommandGroup(); -myCommands.command("hello", "Поздороваться", (ctx) => ctx.reply(`Привет, мир!`)); +myCommands.command( + "hello", + "Поздороваться", + (ctx) => ctx.reply(`Привет, мир!`), +); bot.use(myCommands); ``` @@ -213,7 +217,7 @@ src/ tsconfig.json ``` -Следующий блок кода демонстрирует, как можно реализовать группу команд, доступных только для разработчиков, и обновить меню команд в клиенте Telegram соответствующим образом. +Следующий блок кода демонстрирует, как можно реализовать группу команд, доступных только для разработчиков, и обновить меню команд в клиенте Telegram соответствующим образом. Обратите внимание на разные шаблоны, используемые в файлах `admin.ts` и `group.ts`. ::: code-group @@ -308,7 +312,7 @@ export default new Command("sayhi", "Приветствие", async ( Знаете ли вы, что можно показывать разные команды в разных чатах в зависимости от типа чата, языка и даже статуса пользователя в группе? Это то, что Telegram называет [**Области видимости команд**](https://core.telegram.org/bots/features#command-scopes). -Области видимости команд --- это отличная функция, но использовать её вручную может быть очень сложно, поскольку трудно отслеживать все области и команды, которые они предоставляют. +Области видимости команд --- это отличная функция, но использовать её вручную может быть очень сложно, поскольку трудно отслеживать все области и команды, которые они предоставляют. Кроме того, при использовании только областей команд вам приходится вручную добавлять фильтрацию внутри каждой команды, чтобы убедиться, что они будут выполняться только для нужных областей видимости. Синхронизировать эти два момента бывает непросто, и именно поэтому существует этот плагин. Посмотрите, как это делается. From 61b635f466ab8040475d32a80a59d22f7a61cc50 Mon Sep 17 00:00:00 2001 From: Habemuscode <34692207+habemuscode@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:56:47 +0100 Subject: [PATCH 47/59] Add Spanish --- site/docs/.vitepress/configs/locales/es.ts | 8 + site/docs/es/plugins/commands.md | 632 ++++++++++++++++++++- 2 files changed, 635 insertions(+), 5 deletions(-) diff --git a/site/docs/.vitepress/configs/locales/es.ts b/site/docs/.vitepress/configs/locales/es.ts index 5117e04c2..202bc3abc 100644 --- a/site/docs/.vitepress/configs/locales/es.ts +++ b/site/docs/.vitepress/configs/locales/es.ts @@ -208,6 +208,14 @@ const pluginOfficial = { text: "Modo de parsear (parse-mode)", link: "/es/plugins/parse-mode", }, + { + text: "Miembros del chat (chat-members)", + link: "/es/plugins/chat-members", + }, + { + text: "Comandos (commands)", + link: "/es/plugins/commands", + }, ], }; diff --git a/site/docs/es/plugins/commands.md b/site/docs/es/plugins/commands.md index d98f10de9..8b8f275f3 100644 --- a/site/docs/es/plugins/commands.md +++ b/site/docs/es/plugins/commands.md @@ -5,10 +5,632 @@ next: false # Comandos (`commands`) -Próximamente, por favor vuelva más tarde. +Manejo de comandos con esteroides. -## Resumen del plugin +Este plugin proporciona varias características relacionadas con el manejo de comandos que no están contenidas en [el manejo de comandos realizado por la librería central](../guide/commands). +He aquí un rápido resumen de lo que obtienes con este plugin: -- Nombre del plugin: `commands` -- [Fuente](https://github.com/grammyjs/commands) -- Referencia +- Mejor legibilidad del código encapsulando el middleware con definiciones de comandos +- Sincronización del menú de comandos de usuario mediante `setMyCommands`. +- Mejor agrupación y organización de comandos +- Posibilidad de ampliar el alcance de los comandos, por ejemplo: sólo accesibles para administradores de grupo o + canales, etc. +- Definición de traducciones de comandos +- Función «¿Quería decir...?», que encuentra el comando existente más cercano a una determinada + error de entrada del usuario +- Coincidencia de comandos sin distinción entre mayúsculas y minúsculas +- Establecimiento de un comportamiento personalizado para los comandos que mencionan explícitamente al usuario de tu bot, + como: `/start@your_bot`. +- Prefijos de comando personalizados, por ejemplo: `+`, `?` o cualquier símbolo en lugar de `/`. +- Soporte para comandos que no están al principio del mensaje +- Comandos RegExp + +Todas estas características son posibles porque definirás una o más estructuras de comandos centrales que definan los comandos de tu bot. + +## Uso básico + +Antes de entrar en materia, echa un vistazo a cómo puedes registrar y manejar un comando con el plugin: + +```ts +const myCommands = new CommandGroup(); + +myCommands.command("hello", "Di hola", (ctx) => ctx.reply(`¡Hola, mundo!`)); + +bot.use(myCommands); +``` + +Esto registra un nuevo comando `/start` a tu bot que será manejado por el middleware dado. + +Ahora, vamos a entrar en algunas de las herramientas adicionales que este plugin tiene para ofrecer. + +## Importación + +En primer lugar, así es como puedes importar todos los tipos y clases necesarios que proporciona el plugin. + +::: code-group + +```ts [TypeScript] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "@grammyjs/commands"; +``` + +```js [JavaScript] +const { CommandGroup, commands, commandNotFound } = require( + "@grammyjs/commands", +); +``` + +```ts [Deno] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "https://deno.land/x/grammy_commands/mod.ts"; +``` + +::: + +Ahora que las importaciones están asentadas, veamos cómo podemos hacer que nuestros comandos sean visibles para nuestros usuarios. + +## Configuración del menú de comandos de usuario + +Una vez que hayas definido tus comandos con una instancia de la clase `CommandGroup`, puedes llamar al método `setCommands`, que registrará todos los comandos definidos en tu bot. + +```ts +const myCommands = new CommandGroup(); + +myCommands.command("hello", "Di hola", (ctx) => ctx.reply("¡Hola!")); +myCommands.command( + "start", + "Iniciar el bot", + (ctx) => ctx.reply("Empezando..."), +); + +bot.use(myCommands); + +await myCommands.setCommands(bot); +``` + +Esto hará que todos los comandos que registres se muestren en el menú de un chat privado con tu bot, o siempre que los usuarios escriban `/` en un chat del que tu bot sea miembro. + +### Acceso directo contextual + +¿Qué pasa si quieres que algunos comandos sólo se muestren a determinados usuarios? Por ejemplo, imagina que tienes un comando `login` y otro `logout`. +El comando `login` sólo debería aparecer para los usuarios que han cerrado sesión, y viceversa. +Así es como puedes hacerlo con el plugin de comandos: + +::: code-group + +```ts [TypeScript] +// Utilice el flavor para crear un contexto personalizado +type MyContext = Context & CommandsFlavor; + +// Utiliza el nuevo contexto para instanciar tu bot +const bot = new Bot("token"); + +// Registrar el acceso directo de contexto +bot.use(commands()); + +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); + +loggedOutCommands.command( + "login", + "Inicie su sesión con el bot", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("¡Bienvenidos! ¡Sesión iniciada!"); + }, +); + +loggedInCommands.command( + "logout", + "Termina tu sesión con el bot", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Hasta luego :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// Por defecto, los usuarios no están logueados, +// por lo que puedes establecer los comandos de desconexión para todos +await loggedOutCommands.setCommands(bot); +``` + +```js [JavaScript] +// Registrar el acceso directo de contexto +bot.use(commands()); + +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); + +loggedOutCommands.command( + "login", + "Inicie su sesión con el bot", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("¡Bienvenidos! ¡Sesión iniciada!"); + }, +); + +loggedInCommands.command( + "logout", + "Termina tu sesión con el bot", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Hasta luego :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// Por defecto, los usuarios no están logueados, +// por lo que puedes establecer los comandos de desconexión para todos +await loggedOutCommands.setCommands(bot); +``` + +::: + +De esta forma, cuando un usuario llame a `/login`, su lista de comandos cambiará para contener sólo el comando `logout`. +Genial, ¿verdad? + +::: danger Restricciones en los Nombres de Comandos +Como se indica en la [Telegram Bot API documentation](https://core.telegram.org/bots/api#botcommand), los nombres de comando sólo pueden estar formados por: + +> 1-32 caracteres. +> Sólo puede contener letras minúsculas inglesas, dígitos y guiones bajos. + +Por lo tanto, llamar a `setCommands` o `setMyCommands` con algo que no sea lower_c4s3_commands lanzará una excepción. +Los comandos que no sigan estas reglas aún podrán ser registrados, utilizados y manejados, pero nunca se mostrarán en el menú de usuario como tales. +::: + +**Ten en cuenta** que `setCommands` y `setMyCommands` sólo afectan a los comandos mostrados en el menú de comandos del usuario, y no al acceso real a los mismos. +Aprenderás cómo implementar el acceso restringido a comandos en la sección [Comandos restringidos](#comandos-de-ambito). + +### Agrupando comandos + +Dado que podemos dividir y agrupar nuestros comandos en diferentes instancias, permite una organización de archivos de comandos mucho más idiomática. + +Digamos que queremos tener comandos sólo para desarrolladores. +Podemos lograrlo con la siguiente estructura de código: + +```ascii +src/ +├─ commands/ +│ ├─ admin.ts +│ ├─ users/ +│ │ ├─ group.ts +│ │ ├─ say-hi.ts +│ │ ├─ say-bye.ts +│ │ ├─ ... +├─ bot.ts +├─ types.ts +tsconfig.json +``` + +El siguiente grupo de código ejemplifica cómo podríamos implementar un grupo de comandos sólo para desarrolladores, y actualizar el menú de comandos del cliente de Telegram en consecuencia. +Asegúrate de fijarte en los diferentes patrones utilizados en los archivos `admin.ts` y `group.ts`. + +::: code-group + +```ts [types.ts] +export type MyContext = Context & CommandsFlavor; +``` + +```ts [bot.ts] +import { devCommands } from "./commands/admin.ts"; +import { userCommands } from "./commands/users/group.ts"; +import type { MyContext } from "./types.ts"; + +export const bot = new Bot("MyBotToken"); + +bot.use(commands()); + +bot.use(userCommands); +bot.use(devCommands); +``` + +```ts [admin.ts] +import { userCommands } from './users/group.ts' +import type { MyContext } from '../types.ts' + +export const devCommands = new CommandGroup() + +devCommands.command('devlogin', 'Saludos', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Hola a mí') + await ctx.setMyCommands(userCommands, devCommands) + } else { + await next() + } +}) + +devCommands.command('usercount', 'Saludos', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply( + `Usuarios activos: ${/** Your business logic */}` + ) + } else { + await next() + } +}) + +devCommands.command('devlogout', 'Saludos', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Adiós') + await ctx.setMyCommands(userCommands) + } else { + await next() + } + }) +``` + +```ts [group.ts] +import sayHi from "./say-hi.ts"; +import sayBye from "./say-bye.ts"; +import etc from "./another-command.ts"; +import type { MyContext } from "../../types.ts"; + +export const userCommands = new CommandGroup() + .add([sayHi, sayBye]); +``` + +```ts [say-hi.ts] +import type { MyContext } from "../../types.ts"; + +export default new Command("sayhi", "Saludos", async (ctx) => { + await ctx.reply("¡Hola pequeño usuario!"); +}); +``` + +::: + +¿Te has dado cuenta de que es posible registrar comandos individuales inicializados a través del método `.add` en la instancia `CommandGroup` o también directamente a través del método `.command(...)`? +Esto permite una estructura de un solo archivo, como en el archivo `admin.ts`, o una estructura de archivos más distribuida como en el archivo `group.ts`. + +::: tip Utiliza siempre grupos de comandos + +Al crear y exportar comandos utilizando el constructor `Command`, es obligatorio registrarlos en una instancia `CommandGroup` mediante el método `.add`. +Por sí solos son inútiles, así que asegúrate de hacerlo en algún momento. + +::: + +El plugin también te obliga a tener el mismo tipo de Contexto para un determinado `CommandGroup` y sus respectivos `Commands` ¡así evitarás a primera vista ese tipo de errores tontos! + +Combinando este conocimiento con la siguiente sección llevarás tu juego de comandos al siguiente nivel. + +## Comandos de ámbito + +¿Sabías que puedes permitir que se muestren diferentes comandos en diferentes chats dependiendo del tipo de chat, el idioma, e incluso el estado del usuario en un grupo de chat? Eso es lo que Telegram llama [**Ámbitos de Comandos**](https://core.telegram.org/bots/features#command-scopes). + +Ahora, los Ámbitos de Comandos son una característica genial, pero usarlos a mano puede ser realmente complicado, ya que es difícil hacer un seguimiento de todos los ámbitos y qué comandos presentan. +Además, al usar los Ámbitos de Comandos por sí solos, tienes que hacer un filtrado manual dentro de cada comando para asegurarte de que sólo se ejecutarán para los ámbitos correctos. +Sincronizar esas dos cosas puede ser una pesadilla, y por eso existe este plugin. +Comprueba cómo se hace. + +La clase `Command` devuelta por el método `command` expone un método llamado `addToScope`. +Este método toma un [BotCommandScope](/ref/types/botcommandscope) junto con uno o más handlers, y registra esos handlers para ser ejecutados en ese scope específico. + +Ni siquiera tienes que preocuparte de llamar a `filter`, el método `addToScope` garantizará que tu handler sólo sea llamado si el contexto es el correcto. + +Este es un ejemplo de un comando con ámbito: + +```ts +const myCommands = new CommandGroup(); + +myCommands + .command("start", "Inicializa la configuración del bot") + .addToScope( + { type: "all_private_chats" }, + (ctx) => ctx.reply(`Hola, ${ctx.chat.first_name}!`), + ) + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply(`Hola, miembros de ${ctx.chat.title}!`), + ); +``` + +El comando `start` ahora puede ser llamado tanto desde chats privados como de grupo, y dará una respuesta diferente dependiendo desde donde sea llamado. +Ahora, si llamas a `myCommands.setCommands`, el comando `start` se registrará tanto en los chats privados como en los de grupo. + +Aquí tienes un ejemplo de un comando al que sólo pueden acceder los administradores de grupo. + +```js +adminCommands + .command("secret", "Sólo para administradores") + .addToScope( + { type: "all_chat_administrators" }, + (ctx) => ctx.reply("¡Pastel gratis!"), + ); +``` + +Y aquí hay un ejemplo de un comando que sólo es accesible en grupos + +```js +myCommands + .command("fun", "Risa") + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply("Jaja"), + ); +``` + +Observa que cuando llamas al método `command`, se abre un nuevo comando. +Si le das un manejador, ese manejador se aplicará al ámbito `default` de ese comando. +Al llamar a `addToScope` en ese comando se añadirá un nuevo manejador, que se filtrará a ese ámbito. +Echa un vistazo a este ejemplo. + +```ts +myCommands + .command( + "default", + "Default command", + // Se ejecutará cuando no se esté en un chat de grupo o cuando el usuario no sea un administrador. + (ctx) => ctx.reply("Hello from default scope"), + ) + .addToScope( + { type: "all_group_chats" }, + // Esto sólo se llamará para los usuarios no administradores de un grupo + (ctx) => ctx.reply("Hello, group chat!"), + ) + .addToScope( + { type: "all_chat_administrators" }, + // Esto será llamado para los administradores de grupo, cuando estén dentro de ese grupo + (ctx) => ctx.reply("Hello, admin!"), + ); +``` + +## Traducciones de comandos + +Otra potente característica es la capacidad de establecer diferentes nombres para el mismo comando, y sus respectivas descripciones basadas en el idioma del usuario. +El plugin de comandos lo hace fácil proporcionando el método `localize`. +Compruébalo: + +```js +myCommands + // Debe establecer un nombre y una descripción por defecto + .command("hello", "Di hola") + // Y luego puede establecer los localizados + .localize("pt", "ola", "Dizer olá"); +``` + +¡Añade tantos como quieras! El plugin se encargará de registrarlos por ti cuando llames a `myCommands.setCommands`. + +Por conveniencia, grammY exporta un objeto tipo enum `LanguageCodes` que puedes usar para una aproximación más idiomática: + +::: code-group + +```ts [TypeScript] +import { LanguageCodes } from "grammy/types"; + +myCommands.command( + "chef", + "Entrega de filetes", + (ctx) => ctx.reply("¡Filete al plato!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Filete a domicilio", + ); +``` + +```js [JavaScript] +const { LanguageCodes } = require("grammy/types"); + +myCommands.command( + "chef", + "Entrega de filetes", + (ctx) => ctx.reply("¡Filete al plato!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Filete a domicilio", + ); +``` + +```ts [Deno] +import { LanguageCodes } from "https://deno.land/x/grammy/types.ts"; + +myCommands.command( + "chef", + "Entrega de filetes", + (ctx) => ctx.reply("¡Filete al plato!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Filete a domicilioo", + ); +``` + +::: + +### Localización de comandos con el plugin de internacionalización + +Si desea que los nombres y descripciones de los comandos localizados se agrupen dentro de los archivos `.ftl`, puede utilizar la siguiente idea: + +```ts +function addLocalizations(command: Command) { + i18n.locales.forEach((locale) => { + command.localize( + locale, + i18n.t(locale, `${command.name}.command`), + i18n.t(locale, `${command.name}.description`), + ); + }); + return command; +} + +myCommands.commands.forEach(addLocalizations); +``` + +## Encontrar el comando más cercano + +Aunque Telegram es capaz de autocompletar los comandos registrados, a veces los usuarios los escriben manualmente y, en algunos casos, cometen errores. +El plugin de comandos te ayuda a lidiar con eso permitiéndote sugerir un comando que podría ser lo que el usuario quería en primer lugar. +Es compatible con prefijos personalizados, así que no tienes que preocuparte por eso, y su uso es bastante sencillo: + +::: code-group + +```ts [TypeScript] +// Utilice el flavor para crear un contexto personalizado +type MyContext = Context & CommandsFlavor; + +// Utiliza el nuevo contexto para instanciar tu bot +const bot = new Bot("token"); +const myCommands = new CommandGroup(); + +// ... Registre los comandos + +bot + // Comprobar si existe un comando + .filter(commandNotFound(myCommands)) + // Si es así, significa que no fue manejado por ninguno de nuestros comandos. + .use(async (ctx) => { + // Encontramos una coincidencia potencial + if (ctx.commandSuggestion) { + await ctx.reply( + `Hmm... No conozco ese comando. ¿Te refieres a ${ctx.commandSuggestion}?`, + ); + } + + // Nada parece acercarse a lo que el usuario escribió + await ctx.reply("Uy... No conozco ese comando :/"); + }); +``` + +```js [JavaScript] +// Utiliza el nuevo contexto para instanciar tu bot +const bot = new Bot("token"); +const myCommands = new CommandGroup(); + +// ... Registre los comandos + +bot + // Comprobar si existe un comando + .filter(commandNotFound(myCommands)) + // Si es así, significa que no fue manejado por ninguno de nuestros comandos. + .use(async (ctx) => { + // Encontramos una coincidencia potencial + if (ctx.commandSuggestion) { + await ctx.reply( + `Hmm... No conozco ese comando. ¿Te refieres a ${ctx.commandSuggestion}?`, + ); + } + + // Nada parece acercarse a lo que el usuario escribió + await ctx.reply("Uy... No conozco ese comando :/"); + }); +``` + +::: + +Entre bastidores, `commandNotFound` utilizará el método contextual `getNearestCommand` que, por defecto, dará prioridad a los comandos que correspondan al idioma del usuario. +Si no se desea este comportamiento, se puede pasar el parámetro `ignoreLocalization` a true. +Es posible buscar entre múltiples instancias de CommandGroup, y `ctx.commandSuggestion` será el comando más similar, si lo hay, entre todos ellos. +También permite establecer la bandera `ignoreCase`, que ignorará las mayúsculas y minúsculas mientras se busca un comando similar y la bandera `similarityThreshold`, que controla lo similar que tiene que ser el nombre de un comando a la entrada del usuario para que sea recomendado. + +La función `commandNotFound` sólo se activará para actualizaciones que contengan texto similar a comandos registrados. +Por ejemplo, si sólo ha registrado [comandos con un prefijo personalizado](#prefijo) como `?`, se activará el controlador para cualquier cosa que se parezca a sus comandos, por ejemplo: `?sayhi` pero no `/definitely_a_command`. +Lo mismo ocurre a la inversa, si sólo tienes comandos con el prefijo por defecto, sólo se activará en las actualizaciones que se parezcan a `/regular` `/commands`. + +Los comandos recomendados sólo provendrán de las instancias de `CommandGroup` que pases a la función. Así que podrías diferir las comprobaciones en múltiples filtros separados. + +Utilicemos los conocimientos anteriores para inspeccionar el siguiente ejemplo: + +```ts +const myCommands = new CommandGroup(); +myCommands.command("dad", "calls dad", () => {}, { prefix: "?" }) + .localize("es", "papa", "llama a papa") + .localize("fr", "pere", "appelle papa"); + +const otherCommands = new CommandGroup(); +otherCommands.command("bread", "eat a toast", () => {}) + .localize("es", "pan", "come un pan") + .localize("fr", "pain", "manger du pain"); + +// Registrar cada grupo de comandos específico del idioma + +// Supongamos que el usuario es francés y ha escrito /Papi +bot + // este filtro se activará para cualquier comando como '/regular' o '?custom' + .filter(commandNotFound([myCommands, otherCommands], { + ignoreLocalization: true, + ignoreCase: true, + })) + .use(async (ctx) => { + ctx.commandSuggestion === "?papa"; // se evalúa como verdadero + }); +``` + +Si el `ignoreLocalization` fuera falso habríamos obtenido «`ctx.commandSuggestion` igual a `/pain`». +Podríamos añadir más filtros como el anterior, con diferentes parámetros o `CommandGroups` para comprobar. +Hay muchas posibilidades. + +## Opciones de comandos + +Hay algunas opciones que se pueden especificar por comando, por ámbito, o globalmente para una instancia `CommandGroup`. +Estas opciones te permiten personalizar aún más cómo tu bot maneja los comandos, dándote más flexibilidad. + +### ignoreCase + +Por defecto, los comandos harán coincidir la entrada del usuario distinguiendo entre mayúsculas y minúsculas. +Si se activa esta opción, por ejemplo, en un comando llamado `/dandy`, coincidirá con `/DANDY` del mismo modo que con `/dandY` o cualquier otra variación que distinga entre mayúsculas y minúsculas. + +### targetedCommands + +Cuando los usuarios invocan un comando, pueden etiquetar opcionalmente su bot, de la siguiente manera: `/comando@nombre_usuario_bot`. Puedes decidir qué hacer con estos comandos utilizando la opción de configuración `targetedCommands`. +Con ella puedes elegir entre tres comportamientos diferentes: + +- `ignored`: Ignora los comandos que mencionan al usuario de tu bot. +- `optional`: Maneja tanto los comandos que mencionan como los que no mencionan al usuario del bot +- `required`: Sólo maneja comandos que mencionan el usuario del bot + +### prefix + +Actualmente, sólo los comandos que empiezan por `/` son reconocidos por Telegram y, por tanto, por el [manejo de comandos realizado por la librería central de grammY](../guide/commands). +En algunas ocasiones, puede que quieras cambiar eso y usar un prefijo personalizado para tu bot. +Esto es posible gracias a la opción `prefix`, que le dirá al plugin de comandos que busque ese prefijo cuando intente identificar un comando. + +Si alguna vez necesitas recuperar entidades `botCommand` de una actualización y necesitas que se hidraten con el prefijo personalizado que has registrado, existe un método específicamente adaptado para ello, llamado `ctx.getCommandEntities(yourCommands)`, que devuelve la misma interfaz que `ctx.entities('bot_command')`. + +::: tip +Los comandos con prefijos personalizados no pueden mostrarse en el Menú Comandos. +::: + +### matchOnlyAtStart + +Cuando [maneja comandos](../guide/commands), la biblioteca central de grammY sólo reconocerá comandos que empiecen en el primer carácter de un mensaje. +El plugin de comandos, sin embargo, te permite escuchar comandos en medio del texto del mensaje, o al final, ¡no importa! +Todo lo que tienes que hacer es establecer la opción `matchOnlyAtStart` a `false`, y el resto lo hará el plugin. + +## Comandos RegExp + +Esta característica es para aquellos que realmente buscan ir salvaje, que le permite crear manejadores de comandos basados en expresiones regulares en lugar de cadenas estáticas, un ejemplo básico se vería así: + +```ts +myCommands + .command( + /delete_([a-zA-Z]+)/, + (ctx) => ctx.reply(`Deleting ${ctx.msg?.text?.split("_")[1]}`), + ); +``` + +Este gestor de órdenes se disparará en `/delete_me` igual que en `/delete_you`, y responderá «Borrarme» en el primer caso y «Borrarte» en el segundo, pero no se disparará en `/delete_` ni en `/delete_123xyz`, pasando como si no estuviera. + +## Plugin Summary + +- Name: `commands` +- [Source](https://github.com/grammyjs/commands) +- [Reference](/ref/commands/) From 06727dd31bf5012351aa7a00404880bbda88ebc1 Mon Sep 17 00:00:00 2001 From: Roz <3948961+roziscoding@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:10:22 -0300 Subject: [PATCH 48/59] fix command name --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index c3ef657a1..cca307dc8 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -39,7 +39,7 @@ myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); bot.use(myCommands); ``` -This registers a new `/start` command to your bot that will be handled by the given middleware. +This registers a new `/hello` command to your bot that will be handled by the given middleware. Now, let's get into some of the extra tools this plugin has to offer. From 577af040e088f97a047565a74d90f2115b022b40 Mon Sep 17 00:00:00 2001 From: MasedMSD <68379695+MasedMSD@users.noreply.github.com> Date: Sat, 2 Nov 2024 11:16:53 +0500 Subject: [PATCH 49/59] fix my braincells :clown: --- site/docs/.vitepress/configs/locales/ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/.vitepress/configs/locales/ru.ts b/site/docs/.vitepress/configs/locales/ru.ts index bd6d8d900..5808491f1 100644 --- a/site/docs/.vitepress/configs/locales/ru.ts +++ b/site/docs/.vitepress/configs/locales/ru.ts @@ -214,7 +214,7 @@ const pluginOfficial = { }, { text: "Команды (commands)", - link: "/plugins/commands", + link: "/ru/plugins/commands", }, ], }; From 3fb8978a14b41f2ee1bc45c5843017977609616f Mon Sep 17 00:00:00 2001 From: Nazar Anroniuk Date: Sat, 2 Nov 2024 14:26:47 +0200 Subject: [PATCH 50/59] sync to Ukrainian --- site/docs/.vitepress/configs/locales/uk.ts | 4 + site/docs/uk/plugins/commands.md | 643 ++++++++++++++++++++- 2 files changed, 645 insertions(+), 2 deletions(-) diff --git a/site/docs/.vitepress/configs/locales/uk.ts b/site/docs/.vitepress/configs/locales/uk.ts index cdc1007f3..0c9aebfdc 100644 --- a/site/docs/.vitepress/configs/locales/uk.ts +++ b/site/docs/.vitepress/configs/locales/uk.ts @@ -212,6 +212,10 @@ const pluginOfficial = { text: "Учасники чату (chat-members)", link: "/uk/plugins/chat-members", }, + { + text: "Команди (commands)", + link: "/uk/plugins/commands", + }, ], }; diff --git a/site/docs/uk/plugins/commands.md b/site/docs/uk/plugins/commands.md index 598c49585..410d1d09d 100644 --- a/site/docs/uk/plugins/commands.md +++ b/site/docs/uk/plugins/commands.md @@ -5,10 +5,649 @@ next: false # Команди (`commands`) -Скоро буде додано, будь ласка, поверніться пізніше. +Обробка команд на стероїдах. + +Цей плагін надає різні можливості, повʼязані з обробкою команд, які не містяться у [стандартних засобах головної бібліотеки](../guide/commands). +Ось короткий огляд того, що ви отримаєте за допомогою цього плагіна: + +- Краща читабельність коду завдяки інкапсуляції проміжних обробників з визначеннями команд +- Синхронізація меню команд користувача за допомогою `setMyCommands` +- Покращене групування та організація команд +- Можливість обмежити доступ до команд, які, наприклад, доступні лише адміністраторам груп або каналів тощо +- Визначення перекладу команд +- Функція `Може, ви мали на увазі ...?`, яка знаходить найближчу існуючу команду до заданого пропущеного користувачем вводу +- Відповідність команд без урахування регістру +- Налаштування кастомної поведінки для команд, в яких явно згадується ваш бот, наприклад, `/start@your_bot`. +- Користувацькі префікси команд, наприклад: `+`, `?` або будь-який інший символ замість `/` +- Підтримка команд, які не стоять на початку повідомлення +- Команди з регулярними виразами! + +Всі ці функції стають можливими завдяки тому, що ви визначаєте одну або кілька центральних структур, які визначають команди вашого бота. + +## Базове використання + +Перш ніж ми зануримося в роботу, подивіться, як ви можете зареєструватися і працювати з командами за допомогою плагіна: + +```ts +const myCommands = new CommandGroup(); + +myCommands.command( + "hello", + "Привітатися", + (ctx) => ctx.reply(`Привіт, світе!`), +); + +bot.use(myCommands); +``` + +Це зареєструє нову команду `/hello` для вашого бота, яка буде оброблятися даним проміжним обробником. + +Тепер давайте розглянемо деякі додаткові інструменти, які може запропонувати цей плагін. + +## Імпортування + +Перш за все, ось як ви можете імпортувати всі необхідні типи і класи, які надає плагін. + +::: code-group + +```ts [TypeScript] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "@grammyjs/commands"; +``` + +```js [JavaScript] +const { CommandGroup, commands, commandNotFound } = require( + "@grammyjs/commands", +); +``` + +```ts [Deno] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "https://deno.land/x/grammy_commands/mod.ts"; +``` + +::: + +Тепер, коли імпортування налагоджено, давайте подивимося, як ми можемо зробити наші команди видимими для наших користувачів. + +## Налаштування меню команд користувача + +Після того, як ви визначили свої команди за допомогою екземпляра класу `CommandGroup`, ви можете викликати метод `setCommands`, який зареєструє всі визначені команди для вашого бота. + +```ts +const myCommands = new CommandGroup(); + +myCommands.command("hello", "Привітатися", (ctx) => ctx.reply("Привіт!")); +myCommands.command("start", "Запустити бота", (ctx) => ctx.reply("Запуск...")); + +bot.use(myCommands); + +await myCommands.setCommands(bot); +``` + +Тепер кожна зареєстрована вами команда відображатиметься в меню приватного чату з вашим ботом або коли користувачі набиратимуть `/` у чаті, учасником якого є ваш бот. + +### Context Shortcut + +Що робити, якщо ви хочете, щоб деякі команди відображалися тільки для певних користувачів? +Наприклад, уявіть, що у вас є команди `login` і `logout`. +Команда `login` повинна відображатися тільки для користувачів, які вийшли з системи, і навпаки. +Ось як це можна зробити за допомогою плагіна: + +::: code-group + +```ts [TypeScript] +// Використовуйте розширювач, щоб створити власний контекст. +type MyContext = Context & CommandsFlavor; + +// Використовуйте новий контекст для створення бота. +const bot = new Bot(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather) + +// Зареєструйте плагін. +bot.use(commands()); + +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); + +loggedOutCommands.command( + "login", + "Розпочати сесію з ботом", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Ласкаво просимо! Сесія розпочалася!"); + }, +); + +loggedInCommands.command( + "logout", + "Завершити сесію з ботом", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Бувайте :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// Типово, користувачі не ввійшли до системи, +// тому ви можете задати команди для всіх, хто вийшов з системи. +await loggedOutCommands.setCommands(bot); +``` + +```js [JavaScript] +const bot = new Bot(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather) + +// Зареєструйте плагін. +bot.use(commands()); + +const loggedOutCommands = new CommandGroup(); +const loggedInCommands = new CommandGroup(); + +loggedOutCommands.command( + "login", + "Розпочати сесію з ботом", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Ласкаво просимо! Сесія розпочалася!"); + }, +); + +loggedInCommands.command( + "logout", + "Завершити сесію з ботом", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Бувайте :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// Типово, користувачі не ввійшли до системи, +// тому ви можете задати команди для всіх, хто вийшов з системи. +await loggedOutCommands.setCommands(bot); +``` + +::: + +Тепер, коли користувач викликає `/login`, його список команд буде змінено на команду `logout`. +Зручно, чи не так? + +::: danger Обмеження щодо назв команд +Як зазначено в [документації Telegram Bot API](https://core.telegram.org/bots/api#botcommand), назви команд можуть бути створені тільки з: + +1. 1-32 символів. +2. Може містити лише малі англійські літери, цифри та підкреслення. + +Тому виклик `setCommands` або `setMyCommands` з будь-яким параметром, окрім чогось типу `lower_c4s3_commands`, спричинить помилку. +Команди, які не відповідають цим правилам, все одно можна реєструвати, використовувати та обробляти, але вони ніколи не будуть показані у меню користувача. +::: + +**Майте на увазі**, що `setCommands` і `setMyCommands` впливають лише на команди, що відображаються у меню команд користувача, а не на фактичний доступ до них. +Про те, як реалізувати обмежений доступ до команд, ви дізнаєтеся у розділі [обмежені команди](#команди-обмежені-областю-видимості). + +### Групування команд + +Оскільки ми можемо розбивати і групувати наші команди на різні екземпляри, це дозволяє набагато ефективніше організувати файл команд. + +Припустимо, ми хочемо мати команди тільки для розробників. +Ми можемо досягти цього за допомогою наступної структури коду: + +```ascii +src/ +├─ commands/ +│ ├─ admin.ts +│ ├─ users/ +│ │ ├─ group.ts +│ │ ├─ say-hi.ts +│ │ ├─ say-bye.ts +│ │ ├─ ... +├─ bot.ts +├─ types.ts +tsconfig.json +``` + +Наведений нижче код демонструє, як можна реалізувати групу команд тільки для розробників і відповідно оновити меню команд клієнта Telegram. +Переконайтеся, що ви звернули увагу на різні шаблони, які використовуються у вкладках файлів `admin.ts` і `group.ts`. + +::: code-group + +```ts [types.ts] +export type MyContext = Context & CommandsFlavor; +``` + +```ts [bot.ts] +import { devCommands } from "./commands/admin.ts"; +import { userCommands } from "./commands/users/group.ts"; +import type { MyContext } from "./types.ts"; + +export const bot = new Bot("MyBotToken"); + +bot.use(commands()); + +bot.use(userCommands); +bot.use(devCommands); +``` + +```ts [admin.ts] +import { userCommands } from './users/group.ts' +import type { MyContext } from '../types.ts' + +export const devCommands = new CommandGroup() + +devCommands.command('devlogin', 'Привітання', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Привіт мені') + await ctx.setMyCommands(userCommands, devCommands) + } else { + await next() + } +}) + +devCommands.command('usercount', 'Активні користувачі', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply( + `Активні користувачі: ${/** Ваша бізнес-логіка */}` + ) + } else { + await next() + } +}) + +devCommands.command('devlogout', 'Прощання', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('До побачення мені') + await ctx.setMyCommands(userCommands) + } else { + await next() + } + }) +``` + +```ts [group.ts] +import sayHi from "./say-hi.ts"; +import sayBye from "./say-bye.ts"; +import etc from "./another-command.ts"; +import type { MyContext } from "../../types.ts"; + +export const userCommands = new CommandGroup() + .add([sayHi, sayBye]); +``` + +```ts [say-hi.ts] +import type { MyContext } from "../../types.ts"; + +export default new Command("sayhi", "Привітання", async (ctx) => { + await ctx.reply("Привіт, юний користуваче!"); +}); +``` + +::: + +Чи помітили ви, що можна реєструвати окремі ініціалізовані команди за допомогою методу `.add` в екземплярі `CommandGroup` або безпосередньо за допомогою методу `.command(...)`? +Це дозволяє створити структуру з одного файлу, як у файлі `admin.ts`, або більш розподілену файлову структуру, як у файлі `group.ts`. + +::: tip Завжди використовуйте групи команд + +При створенні та експорті команд за допомогою конструктора `Command` обов'язково потрібно зареєструвати їх в екземплярі `CommandGroup` за допомогою методу `.add`. +Самі по собі вони не приносять користі, тому обов'язково зробіть це колись. + +::: + +Плагін також змушує вас мати той самий тип контексту для заданої `CommandGroup` та їхніх відповідних `Commands`, щоб ви могли уникнути такої, на перший погляд, безглуздої помилки! + +Поєднання цих знань з наступним розділом підніме вашу роботу з командами на новий рівень. + +## Команди, обмежені областю видимості + +Чи знаєте ви, що можете дозволити показувати різні команди в різних чатах залежно від типу чату, мови і навіть статусу користувача в групі? +Це те, що в Telegram називається [**областями видимості команд**](https://core.telegram.org/bots/features#command-scopes). + +Області видимості команд --- це класна функція, але використання її вручну може бути дуже заплутаним, оскільки важко відстежити всі області та команди, які вони представляють. +Крім того, використовуючи області видимості команд самостійно, вам доведеться вручну фільтрувати кожну команду, щоб переконатися, що вона буде виконуватися тільки для правильних областей видимості. +Синхронізація цих двох речей може перетворитися на справжній жах, і саме тому існує цей плагін. +Погляньте, як це робиться. + +Клас `Command`, що повертається методом `command`, містить метод з назвою `addToScope`. +Цей метод отримує [`BotCommandScope`](/ref/types/botcommandscope) разом з одним або декількома обробниками і реєструє ці обробники для виконання у вказаній області видимості. + +Вам навіть не потрібно турбуватися про виклик `filter`. +Метод `addToScope` гарантує, що ваш обробник буде викликано лише за умови правильного контексту. + +Ось приклад команди з областю видимості: + +```ts +const myCommands = new CommandGroup(); + +myCommands + .command("start", "Ініціалізує налаштування бота") + .addToScope( + { type: "all_private_chats" }, + (ctx) => ctx.reply(`Привіт, ${ctx.chat.first_name}!`), + ) + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply(`Привіт, член ${ctx.chat.title}!`), + ); +``` + +Команду `start` тепер можна викликати як з приватних, так і з групових чатів, і вона даватиме різну відповідь залежно від того, звідки її викликано. +Тепер, якщо ви викличете `myCommands.setCommands`, команда `start` буде зареєстрована як в приватних, так і в групових чатах. + +Ось приклад команди, яка доступна лише адміністраторам груп: + +```js +adminCommands + .command("secret", "Виклюючно для адміністраторів") + .addToScope( + { type: "all_chat_administrators" }, + (ctx) => ctx.reply("Free cake!"), + ); +``` + +А ось приклад команди, яка доступна лише в групах: + +```js +myCommands + .command("fun", "Сміх") + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply("Хаха"), + ); +``` + +Зверніть увагу, що коли ви викликаєте метод `command`, він відкриває нову команду. +Якщо ви надасте їй обробник, цей обробник буде застосовано до області видимості `default` цієї команди. +Виклик `addToScope` для цієї команди додасть новий обробник, який буде відфільтровано для цієї області видимості. +Погляньте на цей приклад: + +```ts +myCommands + .command( + "default", + "Типова команда", + // Ця команда буде викликана, якщо користувач не перебуває в груповому чаті або якщо він не є адміністратором. + (ctx) => ctx.reply("Привіт з типової області видимості"), + ) + .addToScope( + { type: "all_group_chats" }, + // Ця команда буде викликана лише для користувачів, які не є адміністраторами в групі. + (ctx) => ctx.reply("Привіт, груповий чате!"), + ) + .addToScope( + { type: "all_chat_administrators" }, + // Ця команда буде викликана для адміністраторів в цій групі. + (ctx) => ctx.reply("Привіт, адміне!"), + ); +``` + +## Переклади команд + +Ще однією потужною можливістю є встановлення різних назв для однієї і тієї ж команди та їхніх описів, що базуються на мові користувача. +Плагін команд полегшує це завдання за допомогою методу `localize`. +Погляньте: + +```js +myCommands + // Вам потрібно встановити типову назву та опис. + .command("hello", "Привітатися") + // А потім ви можете встановити локалізовані версії. + .localize("pt", "ola", "Dizer olá"); +``` + +Додавайте скільки завгодно! Плагін подбає про їхню реєстрацію, коли ви викличете `myCommands.setCommands`. + +Для зручності grammY експортує обʼєкт, подібний до переліку `LanguageCodes`, який ви можете використовувати для кращої зрозумілості коду: + +::: code-group + +```ts [TypeScript] +import { LanguageCodes } from "grammy/types"; + +myCommands.command( + "chef", + "Доставка стейків", + (ctx) => ctx.reply("Стейк на тарілці!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + ); +``` + +```js [JavaScript] +const { LanguageCodes } = require("grammy/types"); + +myCommands.command( + "chef", + "Доставка стейків", + (ctx) => ctx.reply("Стейк на тарілці!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + ); +``` + +```ts [Deno] +import { LanguageCodes } from "https://deno.land/x/grammy/types.ts"; + +myCommands.command( + "chef", + "Доставка стейків", + (ctx) => ctx.reply("Стейк на тарілці!"), +) + .localize( + LanguageCodes.Spanish, + "cocinero", + "Bife a domicilio", + ); +``` + +::: + +### Локалізація команд за допомогою плагіна інтернаціоналізації + +Якщо ви хочете, щоб ваші локалізовані назви команд та описи до них містилися у ваших файлах `.ftl`, ви можете скористатися наступною ідеєю: + +```ts +function addLocalizations(command: Command) { + i18n.locales.forEach((locale) => { + command.localize( + locale, + i18n.t(locale, `${command.name}.command`), + i18n.t(locale, `${command.name}.description`), + ); + }); + return command; +} + +myCommands.commands.forEach(addLocalizations); +``` + +## Пошук найближчої команди + +Незважаючи на те, що Telegram вміє автоматично завершувати зареєстровані команди, іноді користувачі вводять їх вручну і, в деяких випадках, роблять помилки. +Плагін команд допоможе вам впоратися з цим, дозволяючи запропонувати команду, яка може бути саме тією, яку користувач хотів отримати в першу чергу. +Він сумісний з користувацькими префіксами, тож вам не доведеться про це турбуватися. +Користуватися цим доволі просто: + +::: code-group + +```ts [TypeScript] +// Використовуйте розширювач, щоб створити власний контекст. +type MyContext = Context & CommandsFlavor; + +// Використовуйте новий контекст для створення бота. +const bot = new Bot(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather) +const myCommands = new CommandGroup(); + +// Зареєструйте команди. + +bot + // Перевірте, чи така команда не існує. + .filter(commandNotFound(myCommands)) + // Якщо так, то це означає, що її не обробив жоден з наших обробників команд. + .use(async (ctx) => { + // Ми знайшли потенційний збіг. + if (ctx.commandSuggestion) { + await ctx.reply( + `Хм... Я не знаю цієї команди. Може, ви мали на увазі ${ctx.commandSuggestion}?`, + ); + } + + // Здається, ніщо не збігається з тим, що ввів користувач. + await ctx.reply("Упс... Я не знаю цієї команди. :/"); + }); +``` + +```js [JavaScript] +const bot = new Bot(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather) +const myCommands = new CommandGroup(); + +// Зареєструйте команди. + +bot + // Перевірте, чи така команда не існує. + .filter(commandNotFound(myCommands)) + // Якщо так, то це означає, що її не обробив жоден з наших обробників команд. + .use(async (ctx) => { + // Ми знайшли потенційний збіг. + if (ctx.commandSuggestion) { + await ctx.reply( + `Хм... Я не знаю цієї команди. Може, ви мали на увазі ${ctx.commandSuggestion}?`, + ); + } + + // Здається, ніщо не збігається з тим, що ввів користувач. + await ctx.reply("Упс... Я не знаю цієї команди. :/"); + }); +``` + +::: + +За лаштунками `commandNotFound` використовуватиме метод контексту `getNearestCommand`, який за замовчуванням надаватиме пріоритет командам, що відповідають мові користувача. +Якщо ви хочете відмовитися від такої поведінки, ви можете передати прапорець `ignoreLocalization`, встановлений у `true`. +Можна шукати у декількох екземплярах `CommandGroup`, і `ctx.commandSuggestion` буде найбільш схожою командою, якщо така є, у всіх екземплярах. +Також можна встановити прапорець `ignoreCase`, який ігноруватиме регістр під час пошуку схожої команди, і прапорець `similarityThreshold`, який контролює, наскільки назва команди має бути схожою на введену користувачем, щоб її було рекомендовано. + +Функція `commandNotFound` спрацьовуватиме лише для оновлень, які містять текст, схожий на ваші зареєстровані команди. +Наприклад, якщо ви зареєстрували лише [команди з власним префіксом](#prefix) на кшталт `?`, вона спрацює для всього, що схоже на ваші команди, наприклад: `?sayhi`, але не `/definitely_a_command`. +Те ж саме відбудеться і в зворотному випадку, якщо у вас є лише команди з префіксом за замовчуванням, він спрацює лише для оновлень, які виглядають як `/regular` і `/commands`. + +Рекомендовані команди надходитимуть лише з екземплярів `CommandGroup`, які ви передали до функції. +Отже, ви можете винести перевірку у декілька окремих фільтрів. + +Давайте використаємо попередні знання для розгляду наступного прикладу: + +```ts +const myCommands = new CommandGroup(); +myCommands.command("dad", "Подзвонити татові", () => {}, { prefix: "?" }) + .localize("es", "papa", "llama a papa") + .localize("fr", "pere", "appelle papa"); + +const otherCommands = new CommandGroup(); +otherCommands.command("bread", "Зʼїсти тост", () => {}) + .localize("es", "pan", "come un pan") + .localize("fr", "pain", "manger du pain"); + +// Зареєструйте кожну групу команд для кожної мови. + +// Припустимо, що користувач є французом і ввів `/Papi`. +bot + // Цей фільтр спрацює для будь-якої команди, подібної до `/regular` або `?custom`. + .filter(commandNotFound([myCommands, otherCommands], { + ignoreLocalization: true, + ignoreCase: true, + })) + .use(async (ctx) => { + ctx.commandSuggestion === "?papa"; // Повертає true. + }); +``` + +Якщо значення `ignoreLocalization` було б `false`, ми отримали б, що `ctx.commandSuggestion` дорівнює `/pain`. +Ми можемо додати більше фільтрів, подібних до наведеного вище, з різними параметрами або `CommandGroup` для перевірки. + +Існує безліч можливостей! + +## Параметри команд + +Існує кілька параметрів, які можна вказати для кожної команди, області видимості або глобально для екземпляра `CommandGroup`. +Ці параметри дозволяють вам додатково налаштувати те, як ваш бот обробляє команди, надаючи вам більшої гнучкості. + +### `ignoreCase` + +За замовчуванням команди будуть відповідати введеним користувачем даним з урахуванням регістру. +Якщо цей прапорець встановлено, наприклад, у команді з назвою `/dandy`, то `/DANDY` відповідатиме так само, як `/dandY` або будь-якій іншій варіації, що враховує регістр. + +### `targetedCommands` + +When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. +With it you can choose between three different behaviors: + +- `ignored`: Ignores commands that mention your bot's user +- `optional`: Handles both commands that do and that don't mention the bot's user +- `required`: Only handles commands that mention the bot's user + +Коли користувачі викликають команду, вони можуть за бажанням позначити вашого бота, наприклад, так: `/command@bot_username`. +Ви можете вирішити, що робити з цими командами, за допомогою конфігураційного параметра `targetedCommands`. +За допомогою цього параметра ви можете вибрати один з трьох варіантів поведінки: + +- `ignored`: ігнорує команди, в яких згадується ваш бот. +- `optional`: обробляє як команди, що згадують, так і команди, що не згадують бота. +- `required`: обробляє тільки команди, в яких згадується бот. + +### `prefix` + +Наразі Telegram розпізнає лише команди, що починаються з `/`, а отже, і [обробку команд виконує grammY](../guide/commands). +У деяких випадках ви можете змінити це і використовувати власний префікс для вашого бота. +Це можливо за допомогою параметра `prefix`, яка вкаже плагіну команд шукати цей префікс при спробі ідентифікувати команду. + +Якщо вам коли-небудь знадобиться отримати сутності `botCommand` з оновлення і потрібно, щоб вони були гідратовані з зареєстрованим вами власним префіксом, існує метод, спеціально розроблений для цього, який називається `ctx.getCommandEntities(yourCommands)`, який повертає той самий інтерфейс, що і `ctx.entities('bot_command')`. + +::: tip + +Команди з власними префіксами не відображаються у меню команд. + +::: + +### `matchOnlyAtStart` + +При [обробці команд](../guide/commands) grammY розпізнає лише команди, які починаються з першого символу повідомлення. +Плагін команд, однак, дозволяє вам прослуховувати команди в середині тексту повідомлення, або в кінці, це не має значення! +Усе, що вам потрібно зробити, це встановити опцію `matchOnlyAtStart` у значення `false`, а все інше плагін зробить сам. + +## Команди з регулярними виразами + +Ця функція для тих, хто дійсно хоче розгулятись. +Вона дозволяє створювати обробники команд на основі регулярних виразів замість статичних рядків, базовий приклад виглядатиме ось так: + +```ts +myCommands + .command( + /delete_([a-zA-Z]+)/, + (ctx) => ctx.reply(`Видалення ${ctx.msg?.text?.split("_")[1]}`), + ); +``` + +This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply "Deleting me" in the first case and "Deleting you" in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing through as if it wasn't there. + +Цей обробник команд спрацює на `/delete_me` так само, як і на `/delete_you`, і відповість "Видалення me" у першому випадку і "Видалення you" у другому, але не спрацює на `/delete_` або `/delete_123xyz`, пропускаючи їх так, ніби їх там не було. ## Загальні відомості про плагін - Назва: `commands` - [Джерело](https://github.com/grammyjs/commands) -- Довідка +- [Довідка](/ref/commands/) From d90e1cd5616225a04f4358c2402056b49cee2289 Mon Sep 17 00:00:00 2001 From: qz Date: Sun, 3 Nov 2024 14:56:22 +0700 Subject: [PATCH 51/59] Sync changes to Indonesian --- site/docs/.vitepress/configs/locales/id.ts | 4 + site/docs/id/plugins/commands.md | 657 ++++++++++++++++++++- 2 files changed, 659 insertions(+), 2 deletions(-) diff --git a/site/docs/.vitepress/configs/locales/id.ts b/site/docs/.vitepress/configs/locales/id.ts index f62a4d062..98064e1f9 100644 --- a/site/docs/.vitepress/configs/locales/id.ts +++ b/site/docs/.vitepress/configs/locales/id.ts @@ -209,6 +209,10 @@ const pluginOfficial = { text: "Chat Members (chat-members)", link: "/id/plugins/chat-members", }, + { + text: "Perintah (commands)", + link: "/id/plugins/commands", + }, ], }; const pluginThirdparty = { diff --git a/site/docs/id/plugins/commands.md b/site/docs/id/plugins/commands.md index 6ff56bab2..417d01829 100644 --- a/site/docs/id/plugins/commands.md +++ b/site/docs/id/plugins/commands.md @@ -5,10 +5,663 @@ next: false # Perintah (`commands`) -Segera hadir, silahkan datang lagi di lain waktu. +Paket lengkap penanganan perintah (command). + +Plugin ini menyediakan berbagai fitur tambahan yang tidak tersedia di [library inti](../guide/commands). +Berikut manfaat yang dapat kamu peroleh: + +- Kode jadi lebih mudah dibaca. +- Sinkronisasi menu perintah user melalui `setMyCommands`. +- Perintah lebih tertata karena dapat dikelompokkan. +- Penerapan perintah untuk lingkup tertentu saja, misalnya diatur hanya tersedia untuk admin grup atau channel, dsb. +- Penerjemahan perintah. +- Fitur `Mungkin maksud Anda ...?` yang akan membantu menemukan perintah yang dimaksud ketika user salah ketik. +- Pencocokan perintah yang tidak peka huruf kapital (case-insensitive). +- Perilaku tersuai untuk perintah yang secara eksplisit terkandung di mention bot, seperti `/start@bot_kamu`. +- Awalan perintah bisa diubah, misalnya diganti menjadi `+`, `?`, `!`, dsb (semua simbol selain `/`). +- Mampu mendeteksi perintah yang tidak terletak di awal pesan. +- RegExp didukung! + +Semua fitur di atas dapat dicapai karena nantinya kamu diharuskan membuat struktur perintah yang sedemikian rupa untuk bot kamu. + +## Penggunaan Dasar + +Sebelum memulai, mari kita lihat cara mendaftarkan sebuah perintah menggunakan plugin ini: + +```ts +const myCommands = new CommandGroup(); + +myCommands.command("halo", "Ucapkan salam", (ctx) => ctx.reply(`Halo, dunia!`)); + +bot.use(myCommands); +``` + +Kode tersebut akan mendaftarkan perintah `/halo` ke bot kamu, yang kemudian akan diteruskan ke middleware terkait. + +Sekarang, mari kita pelajari alat-alat tambahan yang tersedia di plugin ini. + +## Melakukan Import + +Berikut cara meng-import semua class beserta type yang diperlukan: + +::: code-group + +```ts [TypeScript] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "@grammyjs/commands"; +``` + +```js [JavaScript] +const { CommandGroup, commands, commandNotFound } = require( + "@grammyjs/commands", +); +``` + +```ts [Deno] +import { + CommandGroup, + commandNotFound, + commands, + type CommandsFlavor, +} from "https://deno.land/x/grammy_commands/mod.ts"; +``` + +::: + +Semua import yang diperlukan sudah disusun. +Sekarang, mari kita cari tahu cara menampilkan perintah ke user. + +## Mengatur Menu Perintah User + +Setelah perintah dibuat menggunakan sebuah instansiasi class `CommandGroup`, sekarang kamu bisa memanggil method `setCommands`. +Method tersebut bertugas untuk mendaftarkan seluruh perintah tadi ke bot kamu. + +```ts +const myCommands = new CommandGroup(); + +myCommands.command( + "halo", + "Ucapkan salam", + (ctx) => ctx.reply("Halo, semuanya!"), +); +myCommands.command("start", "Mulai bot", (ctx) => ctx.reply("Memulai...")); + +bot.use(myCommands); + +await myCommands.setCommands(bot); +``` + +Kode di atas akan menampilkan setiap perintah ke menu yang berada di chat pribadi bot kamu, atau setiap kali user mengetik `/` di sebuah chat yang di dalamnya terdapat bot kamu juga. + +### Pintasan Context + +Bagaimana jika kamu ingin menampilkan perintah ke user tertentu saja? +Misalnya, bayangkan kamu memiliki sebuah perintah `login` dan `logout`. +Perintah `login` seharusnya hanya ditampilkan ketika user logout, dan sebaliknya. +Berikut cara mengatasinya menggunakan plugin commands: + +::: code-group + +```ts [TypeScript] +// Gunakan flavor untuk context tersuai. +type MyContext = Context & CommandsFlavor; + +// Gunakan context yang telah dibuat untuk menginisiasi bot kamu. +const bot = new Bot("token"); + +// Daftarkan pintasan context-nya. +bot.use(commands()); + +// Perintah untuk logout. +const loggedOutCommands = new CommandGroup(); +// Perintah untuk login. +const loggedInCommands = new CommandGroup(); + +// Tampilkan perintah masuk (login) ketika user keluar (logout). +loggedOutCommands.command( + "login", + "Mulai sesi baru", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Selamat datang! Sesi telah dimulai!"); + }, +); + +// Tampilkan perintah keluar (logout) ketika user masuk (login). +loggedInCommands.command( + "logout", + "Akhiri sesi", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Sampai jumpa :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// Secara bawaan, user tidak login. +// Oleh karena itu, kamu bisa menyetel perintah logout ke semua orang. +await loggedOutCommands.setCommands(bot); +``` + +```js [JavaScript] +// Daftarkan pintasan context-nya. +bot.use(commands()); + +// Perintah untuk logout. +const loggedOutCommands = new CommandGroup(); +// Perintah untuk login. +const loggedInCommands = new CommandGroup(); + +// Tampilkan perintah masuk (login) ketika user keluar (logout). +loggedOutCommands.command( + "login", + "Mulai sesi baru", + async (ctx) => { + await ctx.setMyCommands(loggedInCommands); + await ctx.reply("Selamat datang! Sesi telah dimulai!"); + }, +); + +// Tampilkan perintah keluar (logout) ketika user masuk (login). +loggedInCommands.command( + "logout", + "Akhiri sesi", + async (ctx) => { + await ctx.setMyCommands(loggedOutCommands); + await ctx.reply("Sampai jumpa :)"); + }, +); + +bot.use(loggedInCommands); +bot.use(loggedOutCommands); + +// Secara bawaan, user tidak login. +// Oleh karena itu, kamu bisa menyetel perintah logout ke semua orang. +await loggedOutCommands.setCommands(bot); +``` + +::: + +Dengan demikian, ketika user memanggil `/login`, daftar perintah mereka akan berubah menjadi perintah `logout`. +Keren, bukan? + +::: danger Ketentuan untuk Nama Perintah +Seperti yang telah diutarakan di [dokumentasi API Bot Telegram](https://core.telegram.org/bots/api#botcommand), nama perintah hanya boleh terdiri atas: + +> 1-32 karakter. +> Karakter yang diperbolehkan hanya huruf abjad non-kapital (a-z), angka (0-9), dan garis bawah (_). + +Itulah kenapa, pemanggilan `setCommands` atau `setMyCommands` di luar ketentuan di atas menyebabkan sebuah galat atau error. +Perintah yang tidak mengikuti ketentuan tersebut masih bisa ditambahkan, digunakan, dan ditangani, tetapi tidak akan pernah bisa ditampilkan di menu user. +::: + +**Perlu diketahui** bahwa `setCommands` dan `setMyCommands` hanya akan berdampak ke tampilan visual di menu perintah user, mereka tidak berdampak ke hak aksesnya. +Kamu akan mempelajari cara menerapkan akses terbatas untuk perintah tertentu di bagian [Lingkup Command](#lingkup-command). + +### Pengelompokan Perintah + +Karena kita bisa membagi dan mengelompokkan perintah menjadi beberapa bagian, maka penataan perintah ke bebeberapa file terpisah juga bisa dilakukan. + +Sebagai contoh, kita ingin perintah tertentu hanya tersedia untuk para developer saja. +Kita bisa melakukannya menggunakan struktur kode berikut: + +```ascii +src/ +├─ commands/ +│ ├─ admin.ts +│ ├─ users/ +│ │ ├─ grup.ts +│ │ ├─ salam-sambutan.ts +│ │ ├─ salam-perpisahan.ts +│ │ ├─ ... +├─ bot.ts +├─ types.ts +tsconfig.json +``` + +Berikut contoh kode untuk membuat grup perintah yang hanya tersedia untuk developer, serta memperbarui menu perintah di aplikasi Telegram user. +Perhatikan perbedaan pola yang digunakan di tab `admin.ts` dan `group.ts`. + +::: code-group + +```ts [types.ts] +export type ContextKu = Context & CommandsFlavor; +``` + +```ts [bot.ts] +import { commandDev } from "./commands/admin.ts"; +import { commandUser } from "./commands/users/grup.ts"; +import type { ContextKu } from "./types.ts"; + +export const bot = new Bot("TokenBot"); + +bot.use(commands()); + +bot.use(commandDev); +bot.use(commandUser); +``` + +```ts [admin.ts] +import { commandUser } from './users/grup.ts' +import type { ContextKu } from '../types.ts' + +export const commandDev = new CommandGroup() + +commandDev.command('dev_login', 'Ucapkan salam', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Halo, diriku sendiri!') + await ctx.setMyCommands(commandUser, commandDev) + } else { + await next() + } +}) + +commandDev.command('jumlah_user', 'Ucapkan salam', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply( + `User aktif: ${/** Tulis alur kodemu di sini */}` + ) + } else { + await next() + } +}) + +commandDev.command('dev_logout', 'Ucapkan salam', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Sampai jumpa, diriku!') + await ctx.setMyCommands(commandUser) + } else { + await next() + } + }) +``` + +```ts [grup.ts] +import salamSambutan from "./salam-sambutan.ts"; +import salamPerpisahan from "./salam-perpisahan.ts"; +import dsb from "./command-lainnya.ts"; +import type { MyContext } from "../../types.ts"; + +export const userCommands = new CommandGroup() + .add([salamSambutan, salamPerpisahan]); +``` + +```ts [salamSambutan.ts] +import type { ContextKu } from "../../types.ts"; + +export default new Command("halo", "Ucapkan salam", async (ctx) => { + await ctx.reply("Halo, user keren!"); +}); +``` + +::: + +Dari kode di atas, apakah kamu menyadari selain melalui method `.command(...)`, kita juga bisa menambahkan instansiasi `Commands` ke dalam `CommandGroup` menggunakan method `.add`? +Dengan begitu, baik struktur satu-file-tunggal, seperti yang telah kita terapkan di file `admin.ts`, maupun struktur file terdistribusi, seperti di file `group.ts`, keduanya bisa diterapkan dengan baik. + +::: tip Selalu Gunakan Pengelompokan Perintah + +Ketika membuat dan meng-export perintah menggunakan constructor `Command`, ia tidak bisa melakukan apa-apa hingga ia didaftarkan ke penangan terkait. +Oleh karena itu, pastikan untuk mendaftarkannya ke pengelompokan perintah `CommandGroup` melalui method `.add`. + +::: + +Plugin ini juga mengharuskan kamu untuk menggunakan type `Context` yang sama untuk `CommandGroup` dan `Commands` terkait agar kamu terhindar dari kesalahan-kesalahan remeh yang mungkin ditimbulkan. + +Dengan menggabungan pengetahuan di bagian ini dan bagian selanjutnya, kamu akan lebih jago untuk mengotak-atik perintah sesuai keinginan. + +## Lingkup Perintah + +Tahukah kamu bahwa kita bisa menampilkan command yang berbeda berdasarkan tipe obrolan, bahasa, atau bahkan status user di suatu grup? +Itulah yang Telegram sebut sebagai [**Command Scopes**](https://core.telegram.org/bots/features#command-scopes) atau lingkup perintah. + +Oke, lingkup perintah merupakan fitur yang keren. +Akan tetapi, menggunakannya secara manual dapat menimbulkan masalah baru karena untuk memantau semua lingkup serta perintah yang menyertainya bukanlah perkara mudah. +Selain itu, dengan menggunakan lingkup perintah secara langsung, kamu perlu melakukan filter secara manual di setiap perintah untuk memastikan mereka dijalankan hanya untuk lingkup tertentu saja. +Menyinkronkan kedua hal tersebut akan sangat merepotkan, itulah kenapa plugin ini dibuat. +Mari kita lihat bagaimana prosesnya. + +Class `Command` yang dikembalikan oleh method `command` mengekspos sebuah method bernama `addToScope`. +Method tersebut menerima sebuah [BotCommandScope](/ref/types/botcommandscope) serta satu atau lebih penangan lainnya. +Penangan tersebut kemudian akan didaftarkan dan dijalankan untuk lingkup-lingkup yang telah ditentukan. + +Kamu bahkan tidak perlu memikirkan `filter`, karena method `addToScope` akan memastikan penangan kamu dipanggil hanya jika context-nya sesuai. + +Berikut contoh penggunaan lingkup perintah: + +```ts +const myCommands = new CommandGroup(); + +myCommands + .command("start", "Mulai konfigurasi bot") + .addToScope( + { type: "all_private_chats" }, + (ctx) => ctx.reply(`Halo, ${ctx.chat.first_name}!`), + ) + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply(`Halo, anggota ${ctx.chat.title}!`), + ); +``` + +Perintah `start` sekarang bisa dipanggil baik dari obrolan privat maupun grup. +Responnya pun akan berbeda berdasarkan sumber obrolannya. +Selain itu, jika kamu memanggil `myCommands.setCommands`, perintah `start` akan ditambahkan baik untuk obrolan privat maupun grup. + +Berikut contoh sebuah perintah yang hanya bisa diakses melalui grup admin. + +```js +adminCommands + .command("rahasia", "Khusus admin") + .addToScope( + { type: "all_chat_administrators" }, + (ctx) => ctx.reply("Ayam geprek gratis!"), + ); +``` + +Contoh yang ini untuk perintah yang hanya bisa diakses melalui grup. + +```js +myCommands + .command("lucu", "Ketawa") + .addToScope( + { type: "all_group_chats" }, + (ctx) => ctx.reply("Wkwk"), + ); +``` + +Dari contoh di atas, ketika kamu memanggil method `command`, ia menerima sebuah perintah. +Selain perintah, jika kamu memberinya sebuah penangan, penangan tersebut akan diterapkan untuk lingkup `default` atau bawaan. +Pemanggilan `addToScope` untuk command tersebut akan menambah sebuah penangan baru untuk lingkup yang telah ditentukan. +Coba lihat contoh berikut: + +```ts +myCommands + .command( + "default", + "Perintah Bawaan", + // Berikut akan dipanggil ketika berasal selain dari obrolan grup. + (ctx) => ctx.reply("Halo! Respon ini berasal dari lingkup bawaan."), + ) + .addToScope( + { type: "all_group_chats" }, + // Berikut akan dipanggil untuk anggota grup yang bukan admin. + (ctx) => ctx.reply("Halo, grup!"), + ) + .addToScope( + { type: "all_chat_administrators" }, + // Berikut akan dipanggil khusus untuk admin grup yang bersangkutan. + (ctx) => ctx.reply("Halo, admin!"), + ); +``` + +## Terjemahan Perintah + +Fitur keren lainnya adalah kita bisa mengatur beberapa nama dan deskripsi yang berbeda untuk satu perintah yang sama berdasarkan bahasa yang digunakan oleh user. +Plugin commands menyediakan method `localize` yang bisa kamu gunakan untuk melakukan pekerjaan tersebut dengan mudah. +Coba lihat contoh berikut: + +```js +myCommands + // Kamu diharuskan membuat satu nama bawaan beserta deskripsinya. + .command("hello", "Say hello") + // Setelah itu, kamu bisa membuat versi terjemahannya. + .localize("id", "halo", "Bilang Halo"); +``` + +Kamu bisa menambahkan beberapa terjemahan sebanyak yang kamu mau! +Plugin akan mendaftarkan semua terjemahan tersebut ketika `myCommands.setCommands` dipanggil. + +Untuk mempermudah pekerjaan, grammY menyediakan sebuah object enum `LanguageCodes` yang bisa kamu gunakan seperti pada contoh berikut: + +::: code-group + +```ts [TypeScript] +import { LanguageCodes } from "grammy/types"; + +myCommands.command( + "chef", + "Fried chicken delivery", + (ctx) => ctx.reply("Fried chicken on the plate!"), +) + .localize( + LanguageCodes.Indonesian, + "Sajikan ayam goreng", + "Ayam goreng siap disajikan!", + ); +``` + +```js [JavaScript] +const { LanguageCodes } = require("grammy/types"); + +myCommands.command( + "chef", + "Fried chicken delivery", + (ctx) => ctx.reply("Fried chicken on the plate!"), +) + .localize( + LanguageCodes.Indonesian, + "Sajikan ayam goreng", + "Ayam goreng siap disajikan!", + ); +``` + +```ts [Deno] +import { LanguageCodes } from "https://deno.land/x/grammy/types.ts"; + +myCommands.command( + "chef", + "Fried chicken delivery", + (ctx) => ctx.reply("Fried chicken on the plate!"), +) + .localize( + LanguageCodes.Indonesian, + "Sajikan ayam goreng", + "Ayam goreng siap disajikan!", + ); +``` + +::: + +### Melokalkan Perintah Menggunakan Plugin Internasionalisasi + +Jika kamu mencari cara untuk melokalkan nama beserta deskripsi untuk file `.ftl`, kamu bisa menerapkan konsep berikut: + +```ts +function addLocalizations(command: Command) { + i18n.locales.forEach((locale) => { + command.localize( + locale, + i18n.t(locale, `${command.name}.command`), + i18n.t(locale, `${command.name}.description`), + ); + }); + return command; +} + +myCommands.commands.forEach(addLocalizations); +``` + +## Menemukan Perintah yang Paling Mirip + +Meski Telegram menyediakan fitur lengkapi-otomatis (auto-complete) untuk perintah-perintah yang terdaftar, namun user terkadang mengetiknya secara manual, yang mana kesalahan ketik sangat mungkin terjadi. +Plugin ini dapat membantumu mengatasi skenario tersebut dengan cara menyediakan saran perintah yang mungkin dimaksud oleh user. +Fitur tersebut juga kompatibel dengan awalan tersuai. +Jadi, kamu tidak perlu khawatir akan hal tersebut. +Cara penggunaanya pun tidak rumit: + +::: code-group + +```ts [TypeScript] +// Gunakan flavor untuk membuat context tersuai. +type MyContext = Context & CommandsFlavor; + +// Gunakan context yang telah dibuat untuk menginisiasi bot kamu. +const bot = new Bot("token"); +const myCommands = new CommandGroup(); + +// ... Daftarkan perintah-perintahnya. + +bot + // Periksa apakah mengandung sebuah perintah. + .filter(commandNotFound(myCommands)) + // Jika mengandung perintah, berarti ia tidak ditangani oleh penangan perintah sebelumnya. + .use(async (ctx) => { + // Sarankan perintah yang mirip. + if (ctx.commandSuggestion) { + await ctx.reply( + `Maaf, saya tidak tahu perintah tersebut. Apakah maksud Anda ${ctx.commandSuggestion}?`, + ); + } + + // Perintah yang mirip tidak ditemukan. + await ctx.reply("Maaf, saya tidak tahu perintah tersebut."); + }); +``` + +```js [JavaScript] +const bot = new Bot("token"); +const myCommands = new CommandGroup(); + +// ... Daftarkan perintah-perintahnya. + +bot + // Periksa apakah mengandung sebuah perintah. + .filter(commandNotFound(myCommands)) + // Jika mengandung perintah, berarti ia tidak ditangani oleh penangan perintah sebelumnya. + .use(async (ctx) => { + // Sarankan perintah yang mirip. + if (ctx.commandSuggestion) { + await ctx.reply( + `Maaf, saya tidak tahu perintah tersebut. Apakah maksud Anda ${ctx.commandSuggestion}?`, + ); + } + + // Perintah yang mirip tidak ditemukan. + await ctx.reply("Maaf, saya tidak tahu perintah tersebut."); + }); +``` + +::: + +Di balik layar, `commandNotFound` menggunakan method context `getNearestCommand` yang secara bawaan memprioritaskan perintah berdasarkan bahasa user. +Jika kamu tidak menghendakinya, atur nilai flag `ignoreLocalization` menjadi `true`. +Pencarian di beberapa instansiasi `CommandGroup` juga dimungkinkan karena `ctx.commandSuggestion` juga akan mencari perintah yang paling mirip di semua instansiasi tersebut. +Selain itu, kamu bisa mengatur flag `ignoreCase` untuk mengabaikan peka huruf kapital (case-sensitive) ketika mencari perintah yang serupa, dan flag `similarityThreshold` untuk mengatur tingkat kemiripan suatu perintah hingga layak dijadikan sebuah saran. + +Function `commandNotFound` akan terpicu hanya untuk update yang mengandung teks perintah yang mirip dengan perintah-perintah yang kamu telah kamu daftarkan. +Misalnya, jika kamu mendaftarkan [perintah yang menggunakan awalan tersuai](#prefix) seperti `?`, ia akan memicu penangan terkait untuk semua entitas yang mirip dengan perintah tersebut. +Sebagai contoh, teks `?halo` akan memicu penangan terkait, tapi tidak dengan `/halo`. +Hal yang sama juga berlaku sebaliknya, jika kamu memiliki perintah yang menggunakan awalan bawaan, ia hanya akan terpicu untuk update seperti `/halo`, `/mulai`, dsb. + +Perintah yang disarankan berasal dari instansiasi `CommandGroup` yang kamu terapkan ke function terkait saja. +Artinya, pengecekan bisa dialihkan menjadi beberapa filter terpisah. + +Mari kita terapkan pengetahuan yang sudah kita dapat sejauh ini: + +```ts +const myCommands = new CommandGroup(); +myCommands.command("dad", "calls dad", () => {}, { prefix: "?" }) + // Indonesia + .localize("id", "ayah", "panggil ayah") + // Spanyol + .localize("es", "papa", "llama a papa") + // Perancis + .localize("fr", "pere", "appelle papa"); + +const otherCommands = new CommandGroup(); +otherCommands.command("bread", "eat a toast", () => {}) + // Indonesia + .localize("id", "roti", "makan roti goreng") + // Spanyol + .localize("es", "pan", "come un pan") + // Perancis + .localize("fr", "pain", "manger du pain"); + +// Daftarkan grup perintah untuk setiap bahasa. + +// Mari kita asumsikan user adalah orang Perancis dan mengetik /Papi. +bot + // Filter ini akan terpicu untuk semua perintah, baik '/reguler' ataupun '?tersuai'. + .filter(commandNotFound([myCommands, otherCommands], { + ignoreLocalization: true, + ignoreCase: true, + })) + .use(async (ctx) => { + ctx.commandSuggestion === "?papa"; // Menghasilkan nilai true. + }); +``` + +Jika misalkan `ignoreLocalization` bernilai `false`, `ctx.commandSuggestion` akan bernilai `/pain`. +Kita bisa menambahkan lebih banyak filter dengan parameter yang berbeda ataupun menggunakan `CommandGroups` untuk melakukan pengecekan. +Dan cara-cara lain yang bisa kita eksplorasi! + +## Opsi Perintah + +Instansiasi `CommandGroup` memiliki beberapa opsi yang bisa kita terapkan, baik untuk setiap perintah, setiap lingkup, ataupun secara global. +Opsi-opsi berikut bisa kamu manfaatkan untuk mengatur perilaku bot dalam menangani perintah secara fleksibel. + +### ignoreCase + +Secara bawaan, perintah akan dicocokkan dengan memperhatikan huruf kapital (case-sensitive). +Ketika flag ini diterapkan, semua huruf baik kapital maupun tidak, akan dianggap sama. +Perintah bernama `/budi` akan cocok dengan `/BUDI`, `/budI`, atau variasi huruf kapital lainnya. + +### targetedCommands + +Ketika user memanggil sebuah perintah, mereka bisa menyebut bot kamu seperti ini: `/perintah@username_bot`. +Kamu bisa memutuskan apa yang bot harus lakukan terhadap jenis perintah tersebut menggunakan opsi pengaturan `targetedCommands`. +Melalui opsi tersebut, kamu bisa memilih tiga perilaku yang berbeda: + +- `ignored`: Abaikan perintah yang menyertakan username bot kamu. +- `optional`: Tangani kedua jenis perintah, baik yang menyertakan username bot kamu, maupun yang tidak. +- `required`: Hanya tangani perintah yang menyertakan username bot kamu. + +### prefix + +Ketika tulisan ini dibuat, Telegram hanya mengenali perintah yang dimulai dengan awalan `/`, yang mana bisa ditangani dengan baik oleh [library inti grammY](../guide/commands). +Di beberapa skenario, kamu mungkin ingin mengubah perilaku tersebut dan menggantinya dengan awalan tersuai. +Kamu bisa memanfaatkan opsi `prefix` supaya plugin mencari awalan yang dimaksud ketika menganalisa sebuah perintah. + +Jika kamu perlu mengambil entity `botCommand` dari suatu update untuk kemudian dihidrasi menggunakan awalan tersuai yang telah didaftarkan, kamu bisa menggunakan method bernama `ctx.getCommandEntities(perintahKamu)`. +Ia mengembalikan interface yang sama dengan `ctx.entities('bot_command')`. + +:::tip +Perintah dengan awalan tersuai tidak dapat ditampilkan di menu perintah user. +::: + +### matchOnlyAtStart + +Ketika [menangani perintah](../guide/commands), library inti grammY hanya akan mengenali perintah yang terletak di awal kalimat. +Berbeda dengan plugin commands, Ia mampu menyimak perintah baik yang terletak di pertengahan, maupun di akhir kalimat. +Cukup atur nilai opsi `matchOnlyAtStart` menjadi `false` untuk mengaktifkannya. + +## Perintah RegExp + +Fitur ini ditujukan untuk kamu yang suka petualangan. +Kamu bisa menggunakan regular expression (RegExp) alih-alih string statis. +Berikut contoh sederhananya: + +```ts +myCommands + .command( + /hapus_([a-zA-Z]+)/, + (ctx) => ctx.reply(`Menghapus ${ctx.msg?.text?.split("_")[1]}`), + ); +``` + +Penangan perintah di atas akan terpicu untuk `/hapus_kenangan`, ataupun `/hapus_dosa`. +Ia akan membalas pesan dengan "Menghapus kenangan" untuk contoh pertama dan "Menghapus dosa" untuk contoh kedua. +Tetapi, ia tidak akan terpicu untuk perintah `/hapus_` ataupun `/hapus_123xyz`. ## Ringkasan Plugin - Nama: `commands` - [Sumber](https://github.com/grammyjs/commands) -- Referensi +- [Reference](/ref/commands/) From 8e38d08963c381bc9337f992d3e84b5e3a4f9796 Mon Sep 17 00:00:00 2001 From: qz Date: Sun, 3 Nov 2024 15:00:57 +0700 Subject: [PATCH 52/59] id: fix anchor --- site/docs/id/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/id/plugins/commands.md b/site/docs/id/plugins/commands.md index 417d01829..44bb5618d 100644 --- a/site/docs/id/plugins/commands.md +++ b/site/docs/id/plugins/commands.md @@ -202,7 +202,7 @@ Perintah yang tidak mengikuti ketentuan tersebut masih bisa ditambahkan, digunak ::: **Perlu diketahui** bahwa `setCommands` dan `setMyCommands` hanya akan berdampak ke tampilan visual di menu perintah user, mereka tidak berdampak ke hak aksesnya. -Kamu akan mempelajari cara menerapkan akses terbatas untuk perintah tertentu di bagian [Lingkup Command](#lingkup-command). +Kamu akan mempelajari cara menerapkan akses terbatas untuk perintah tertentu di bagian [Lingkup Command](#lingkup-perintah). ### Pengelompokan Perintah From 1510b8030b9e6bb67e032c3ca52258aaaaf0a5c1 Mon Sep 17 00:00:00 2001 From: qz Date: Sun, 3 Nov 2024 15:13:04 +0700 Subject: [PATCH 53/59] id: fix typo --- site/docs/id/plugins/commands.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/site/docs/id/plugins/commands.md b/site/docs/id/plugins/commands.md index 44bb5618d..70dcc3123 100644 --- a/site/docs/id/plugins/commands.md +++ b/site/docs/id/plugins/commands.md @@ -231,15 +231,15 @@ Perhatikan perbedaan pola yang digunakan di tab `admin.ts` dan `group.ts`. ::: code-group ```ts [types.ts] -export type ContextKu = Context & CommandsFlavor; +export type MyContext = Context & CommandsFlavor; ``` ```ts [bot.ts] import { commandDev } from "./commands/admin.ts"; import { commandUser } from "./commands/users/grup.ts"; -import type { ContextKu } from "./types.ts"; +import type { MyContext } from "./types.ts"; -export const bot = new Bot("TokenBot"); +export const bot = new Bot("TokenBot"); bot.use(commands()); @@ -249,9 +249,9 @@ bot.use(commandUser); ```ts [admin.ts] import { commandUser } from './users/grup.ts' -import type { ContextKu } from '../types.ts' +import type { MyContext } from '../types.ts' -export const commandDev = new CommandGroup() +export const commandDev = new CommandGroup() commandDev.command('dev_login', 'Ucapkan salam', async (ctx, next) => { if (ctx.from?.id === ctx.env.DEVELOPER_ID) { @@ -293,9 +293,9 @@ export const userCommands = new CommandGroup() ``` ```ts [salamSambutan.ts] -import type { ContextKu } from "../../types.ts"; +import type { MyContext } from "../../types.ts"; -export default new Command("halo", "Ucapkan salam", async (ctx) => { +export default new Command("halo", "Ucapkan salam", async (ctx) => { await ctx.reply("Halo, user keren!"); }); ``` From 3fd4d0a6fb3d436617be6850d038691acb8e0a19 Mon Sep 17 00:00:00 2001 From: qz Date: Sun, 3 Nov 2024 15:36:42 +0700 Subject: [PATCH 54/59] Sync @niusia-ua suggestions From https://github.com/grammyjs/website/pull/1018#discussion_r1826509969 to https://github.com/grammyjs/website/pull/1018#discussion_r1826558371 --- site/docs/plugins/commands.md | 56 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index cca307dc8..49f376733 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -13,14 +13,11 @@ Here is a quick overview of what you get with this plugin: - Better code readability by encapsulating middleware with command definitions - User command menu synchronization via `setMyCommands` - Improved command grouping and organization -- Ability to scope command reach, e.g: only accessible to group admins or - channels, etc +- Ability to scope command reach, e.g: only accessible to group admins or channels, etc - Defining command translations -- `Did you mean ...?` feature that finds the nearest existing command to a given - user miss-input +- `Did you mean ...?` feature that finds the nearest existing command to a given user miss-input - Case-insensitive command matching -- Setting custom behavior for commands that explicitly mention your bot's user, - like: `/start@your_bot` +- Setting custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` - Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` - Support for commands that are not in the beginning of the message - RegExp Commands! @@ -96,7 +93,8 @@ This will make it so every command you registered is displayed on the menu of a ### Context Shortcut -What if you want some commands to be displayed only to certain users? For example, imagine you have a `login` and a `logout` command. +What if you want some commands to be displayed only to certain users? +For example, imagine you have a `login` and a `logout` command. The `login` command should only appear for logged out users, and vice versa. This is how you can do that with the commands plugin: @@ -107,7 +105,7 @@ This is how you can do that with the commands plugin: type MyContext = Context & CommandsFlavor; // Use the new context to instantiate your bot -const bot = new Bot("token"); +const bot = new Bot(""); // <-- put your bot token between the "" (https://t.me/BotFather) // Register the context shortcut bot.use(commands()); @@ -142,6 +140,8 @@ await loggedOutCommands.setCommands(bot); ``` ```js [JavaScript] +const bot = new Bot(""); // <-- put your bot token between the "" (https://t.me/BotFather) + // Register the context shortcut bot.use(commands()); @@ -182,8 +182,8 @@ Neat, right? ::: danger Command Name Restrictions As stated in the [Telegram Bot API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: -> 1-32 characters. -> Can contain only lowercase English letters, digits and underscores. +1. 1-32 characters. +2. Can contain only lowercase English letters, digits and underscores. Therefore calling `setCommands` or `setMyCommands` with anything but lower_c4s3_commands will throw an exception. Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. @@ -227,7 +227,7 @@ import { devCommands } from "./commands/admin.ts"; import { userCommands } from "./commands/users/group.ts"; import type { MyContext } from "./types.ts"; -export const bot = new Bot("MyBotToken"); +export const bot = new Bot(""); // <-- put your bot token between the "" (https://t.me/BotFather) bot.use(commands()); @@ -306,7 +306,8 @@ Combining this knowledge with the following section will get your Command-game t ## Scoped Commands -Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? That's what Telegram calls [**Command Scopes**](https://core.telegram.org/bots/features#command-scopes). +Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? +That's what Telegram calls [**Command Scopes**](https://core.telegram.org/bots/features#command-scopes). Now, Command Scopes are a cool feature, but using it by hand can get really messy, since it's hard to keep track of all the scopes and what commands they present. Plus, by using Command Scopes on their own, you have to do manual filtering inside each command to ensure they'll only run for the correct scopes. @@ -314,7 +315,7 @@ Syncing those two things up can be a nightmare, and that's why this plugin exist Check how it's done. The `Command` class returned by the `command` method exposes a method called `addToScope`. -This method takes in a [BotCommandScope](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. +This method takes in a [`BotCommandScope`](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. @@ -484,7 +485,7 @@ It is compatible with custom prefixes, so you don't have to worry about that, an type MyContext = Context & CommandsFlavor; // Use the new context to instantiate your bot -const bot = new Bot("token"); +const bot = new Bot(""); // <-- put your bot token between the "" (https://t.me/BotFather) const myCommands = new CommandGroup(); // ... Register the commands @@ -507,8 +508,7 @@ bot ``` ```js [JavaScript] -// Use the new context to instantiate your bot -const bot = new Bot("token"); +const bot = new Bot(""); // <-- put your bot token between the "" (https://t.me/BotFather) const myCommands = new CommandGroup(); // ... Register the commands @@ -534,14 +534,15 @@ bot Behind the scenes, `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language. If you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. -It is possible to search across multiple CommandGroup instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. +It is possible to search across multiple `CommandGroup` instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. The `commandNotFound` function will only trigger for updates which contain command-like text similar to your registered commands. For example, if you only have registered [commands with a custom prefix](#prefix) like `?`, it will trigger the handler for anything that looks like your commands, e.g: `?sayhi` but not `/definitely_a_command`. -Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that look like `/regular` `/commands`. +Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that look like `/regular` and `/commands`. -The recommended commands will only come from the `CommandGroup` instances you pass to the function. So you could defer the checks into multiple, separate filters. +The recommended commands will only come from the `CommandGroup` instances you pass to the function. +So you could defer the checks into multiple, separate filters. Let's use the previous knowledge to inspect the next example: @@ -571,7 +572,7 @@ bot ``` If the `ignoreLocalization` was falsy instead we would have gotten "`ctx.commandSuggestion` equals `/pain`". -We could add more filters like the above, with different parameters or `CommandGroups` to check against. +We could add more filters like the above, with different parameters or `CommandGroup`s to check against. There are a lot of possibilities! ## Command Options @@ -579,21 +580,22 @@ There are a lot of possibilities! There are a few options that can be specified per command, per scope, or globally for a `CommandGroup` instance. These options allow you to further customize how your bot handles commands, giving you more flexibility. -### ignoreCase +### `ignoreCase` By default commands will match the user input in a case-sensitive manner. Having this flag set, for example, in a command named `/dandy` will match `/DANDY` the same as `/dandY` or any other case-only variation. -### targetedCommands +### `targetedCommands` -When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. +When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. +You can decide what to do with these commands by using the `targetedCommands` config option. With it you can choose between three different behaviors: - `ignored`: Ignores commands that mention your bot's user - `optional`: Handles both commands that do and that don't mention the bot's user - `required`: Only handles commands that mention the bot's user -### prefix +### `prefix` Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). In some occasions, you might want to change that and use a custom prefix for your bot. @@ -601,11 +603,13 @@ That is made possible by the `prefix` option, which will tell the commands plugi If you ever need to retrieve `botCommand` entities from an update and need them to be hydrated with the custom prefix you have registered, there is a method specifically tailored for that, called `ctx.getCommandEntities(yourCommands)`, which returns the same interface as `ctx.entities('bot_command')` -:::tip +::: tip + Commands with custom prefixes cannot be shown in the Commands Menu. + ::: -### matchOnlyAtStart +### `matchOnlyAtStart` When [handling commands](../guide/commands), the grammY core library will only recognize commands that start on the first character of a message. The commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! From c269cdcd4c29c0245551863324078110f3c1ac4f Mon Sep 17 00:00:00 2001 From: Nazar Anroniuk Date: Sun, 3 Nov 2024 13:49:35 +0200 Subject: [PATCH 55/59] remove the untranslated text --- site/docs/uk/plugins/commands.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/site/docs/uk/plugins/commands.md b/site/docs/uk/plugins/commands.md index 410d1d09d..39b699489 100644 --- a/site/docs/uk/plugins/commands.md +++ b/site/docs/uk/plugins/commands.md @@ -594,13 +594,6 @@ bot ### `targetedCommands` -When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. -With it you can choose between three different behaviors: - -- `ignored`: Ignores commands that mention your bot's user -- `optional`: Handles both commands that do and that don't mention the bot's user -- `required`: Only handles commands that mention the bot's user - Коли користувачі викликають команду, вони можуть за бажанням позначити вашого бота, наприклад, так: `/command@bot_username`. Ви можете вирішити, що робити з цими командами, за допомогою конфігураційного параметра `targetedCommands`. За допомогою цього параметра ви можете вибрати один з трьох варіантів поведінки: @@ -642,8 +635,6 @@ myCommands ); ``` -This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply "Deleting me" in the first case and "Deleting you" in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing through as if it wasn't there. - Цей обробник команд спрацює на `/delete_me` так само, як і на `/delete_you`, і відповість "Видалення me" у першому випадку і "Видалення you" у другому, але не спрацює на `/delete_` або `/delete_123xyz`, пропускаючи їх так, ніби їх там не було. ## Загальні відомості про плагін From 2785d9586530f45dd24970b631fbcf77d48d9ff9 Mon Sep 17 00:00:00 2001 From: Nazar Antoniuk Date: Sun, 3 Nov 2024 13:52:56 +0200 Subject: [PATCH 56/59] Update site/docs/uk/plugins/commands.md Co-authored-by: Andrii Zontov --- site/docs/uk/plugins/commands.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/docs/uk/plugins/commands.md b/site/docs/uk/plugins/commands.md index 39b699489..e7836a5d6 100644 --- a/site/docs/uk/plugins/commands.md +++ b/site/docs/uk/plugins/commands.md @@ -299,8 +299,8 @@ export default new Command("sayhi", "Привітання", async (ct ::: tip Завжди використовуйте групи команд -При створенні та експорті команд за допомогою конструктора `Command` обов'язково потрібно зареєструвати їх в екземплярі `CommandGroup` за допомогою методу `.add`. -Самі по собі вони не приносять користі, тому обов'язково зробіть це колись. +При створенні та експорті команд за допомогою конструктора `Command` обовʼязково потрібно зареєструвати їх в екземплярі `CommandGroup` за допомогою методу `.add`. +Самі по собі вони не приносять користі, тому обовʼязково зробіть це колись. ::: From 1b12b9dd61d2c6be34f1be07588d6f641ea0c11e Mon Sep 17 00:00:00 2001 From: Nazar Anroniuk Date: Mon, 4 Nov 2024 09:59:17 +0200 Subject: [PATCH 57/59] include the commands plugin in the table on the plugins' overview page --- site/docs/plugins/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/site/docs/plugins/README.md b/site/docs/plugins/README.md index 9559a70a9..4739a11da 100644 --- a/site/docs/plugins/README.md +++ b/site/docs/plugins/README.md @@ -36,6 +36,7 @@ Installing plugins is fun and easy, and we want you to know what we have in stor | [Media Groups](./media-group) | _built-in_ | Simplify sending media groups and editing media | | [Inline Queries](./inline-query) | _built-in_ | Easily build results for inline queries | | [Auto-retry](./auto-retry) | [`auto-retry`](./auto-retry) | Automatically handle rate limiting | +| [Commands](./commands) | [`commands`](./commands) | Manage commands in a powerful way | | [Conversations](./conversations) | [`conversations`](./conversations) | Build powerful conversational interfaces and dialogs | | [Chat Members](./chat-members) | [`chat-members`](./chat-members) | Track which user joined which chat | | [Emoji](./emoji) | [`emoji`](./emoji) | Simplify using emoji in code | From 38aa286a238c579846f612c6a2e6de6ff0f1f918 Mon Sep 17 00:00:00 2001 From: qz Date: Sun, 3 Nov 2024 15:48:11 +0700 Subject: [PATCH 58/59] fix --- site/docs/plugins/commands.md | 293 ++++++++++++++++++---------------- 1 file changed, 157 insertions(+), 136 deletions(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 49f376733..0ab57c8b8 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -7,22 +7,22 @@ next: false Command handling on steroids. -This plugin provides various features related to command handling that are not contained in the [command handling done by the core library](../guide/commands). +This plugin offers advanced command-handling features beyond the core library's [command handling](../guide/commands). Here is a quick overview of what you get with this plugin: -- Better code readability by encapsulating middleware with command definitions -- User command menu synchronization via `setMyCommands` -- Improved command grouping and organization -- Ability to scope command reach, e.g: only accessible to group admins or channels, etc -- Defining command translations -- `Did you mean ...?` feature that finds the nearest existing command to a given user miss-input -- Case-insensitive command matching -- Setting custom behavior for commands that explicitly mention your bot's user, like: `/start@your_bot` -- Custom command prefixes, e.g: `+`, `?` or any symbol instead of `/` -- Support for commands that are not in the beginning of the message -- RegExp Commands! +- Better code readability by encapsulating middleware with command definitions. +- User command menu synchronization via `setMyCommands`. +- Improved command grouping and organization. +- Command reach scoping, e.g. limiting access to group admins or specific channels. +- Support for command translations. +- `Did you mean ...?` feature to suggest the closest command when a user makes a typo. +- Case-insensitive command matching. +- Setting custom behavior for commands that explicitly mention your bot's username, such as `/start@your_bot`. +- Custom command prefixes, e.g. `+`, `?`, or any symbol instead of `/`. +- Support for commands not located at the start of a message. +- RegExp commands! -All of these features are made possible because you will define one or more central command structures that define your bot's commands. +All of these features are powered by central command structures that you define for your bot. ## Basic Usage @@ -36,13 +36,13 @@ myCommands.command("hello", "Say hello", (ctx) => ctx.reply(`Hello, world!`)); bot.use(myCommands); ``` -This registers a new `/hello` command to your bot that will be handled by the given middleware. +This registers a new `/hello` command to your bot, which will be handled by the given middleware. Now, let's get into some of the extra tools this plugin has to offer. ## Importing -First of all, here's how you can import all the necessary types and classes the plugin provides. +First of all, here's how you can import all the necessary types and classes provided by the plugin. ::: code-group @@ -56,7 +56,7 @@ import { ``` ```js [JavaScript] -const { CommandGroup, commands, commandNotFound } = require( +const { CommandGroup, commandNotFound, commands } = require( "@grammyjs/commands", ); ``` @@ -76,27 +76,28 @@ Now that the imports are settled, let's see how we can make our commands visible ## User Command Menu Setting -Once you defined your commands with an instance of the `CommandGroup` class, you can call the `setCommands` method, which will register all the defined commands to your bot. +Once you have defined your commands using the `CommandGroup` class, you can call the `setCommands` method to add all the defined commands to the user command menu. ```ts const myCommands = new CommandGroup(); -myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hi there!")); +myCommands.command("hello", "Say hello", (ctx) => ctx.reply("Hello there!")); myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting...")); bot.use(myCommands); -await myCommands.setCommands(bot); +// Update the user command menu +await myCommands.setCommands(bot); // [!code highlight] ``` -This will make it so every command you registered is displayed on the menu of a private chat with your bot, or whenever users type `/` on a chat your bot is a member of. +This ensures that each registered command appears in the menu of a private chat with your bot or when users type `/` in a chat where your bot is a member. ### Context Shortcut -What if you want some commands to be displayed only to certain users? +What if you want some commands displayed only to certain users? For example, imagine you have a `login` and a `logout` command. -The `login` command should only appear for logged out users, and vice versa. -This is how you can do that with the commands plugin: +The `login` command should only appear for logged-out users, and vice versa. +Here's how to do that with the commands plugin: ::: code-group @@ -135,7 +136,7 @@ bot.use(loggedInCommands); bot.use(loggedOutCommands); // By default, users are not logged in, -// so you can set the logged out commands for everyone +// so you can set the logged-out commands for everyone await loggedOutCommands.setCommands(bot); ``` @@ -170,26 +171,26 @@ bot.use(loggedInCommands); bot.use(loggedOutCommands); // By default, users are not logged in, -// so you can set the logged out commands for everyone +// so you can set the logged-out commands for everyone await loggedOutCommands.setCommands(bot); ``` ::: -This way when a user calls `/login`, they'll have their command list changed to contain only the `logout` command. +This way, when a user calls `/login`, they'll have their command list changed to contain only the `logout` command. Neat, right? ::: danger Command Name Restrictions -As stated in the [Telegram Bot API documentation](https://core.telegram.org/bots/api#botcommand), command names can only be form out of: +As stated in the [Telegram Bot API documentation](https://core.telegram.org/bots/api#botcommand), command names must consist of: -1. 1-32 characters. -2. Can contain only lowercase English letters, digits and underscores. +1. Between 1 and 32 characters. +2. Only lowercase English letters (a-z), digits (0-9), and underscores (_). -Therefore calling `setCommands` or `setMyCommands` with anything but lower_c4s3_commands will throw an exception. -Commands not following this rules can still be registered, used and handled, but will never be displayed on the user menu as such. +Therefore, calling `setCommands` or `setMyCommands` with invalid command names will throw an exception. +Commands that don't follow these rules can still be registered and handled, but won't appear in the user command menu. ::: -**Be aware** that `setCommands` and `setMyCommands` only affects the commands displayed in the user's commands menu, and not the actual access to them. +**Be aware** that `setCommands` and `setMyCommands` only affect the commands displayed in the user's commands menu, and not the actual access to them. You will learn how to implement restricted command access in the [Scoped Commands](#scoped-commands) section. ### Grouping Commands @@ -200,25 +201,26 @@ Let's say we want to have developer-only commands. We can achieve that with the following code structure: ```ascii -src/ -├─ commands/ -│ ├─ admin.ts -│ ├─ users/ -│ │ ├─ group.ts -│ │ ├─ say-hi.ts -│ │ ├─ say-bye.ts -│ │ ├─ ... -├─ bot.ts -├─ types.ts -tsconfig.json +. +├── types.ts +├── bot.ts +└── commands/ + ├── admin.ts + └── users/ + ├── group.ts + ├── say-hello.ts + └── say-bye.ts ``` -The following code group exemplifies how we could implement a developer only command group, and update the Telegram client Command menu accordingly. -Make sure you take notice of the different patterns being use in the `admin.ts` and `group.ts` file-tabs. +The following code group exemplifies how we could implement a developer only command group, and update the Telegram client command menu accordingly. +Make sure you take notice of the different patterns being used in the `admin.ts` and `group.ts` file-tabs. ::: code-group ```ts [types.ts] +import type { Context } from "grammy"; +import type { CommandsFlavor } from "grammy_commands"; + export type MyContext = Context & CommandsFlavor; ``` @@ -236,62 +238,67 @@ bot.use(devCommands); ``` ```ts [admin.ts] -import { userCommands } from './users/group.ts' -import type { MyContext } from '../types.ts' - -export const devCommands = new CommandGroup() - -devCommands.command('devlogin', 'Greetings', async (ctx, next) => { - if (ctx.from?.id === ctx.env.DEVELOPER_ID) { - await ctx.reply('Hi to me') - await ctx.setMyCommands(userCommands, devCommands) - } else { - await next() - } -}) - -devCommands.command('usercount', 'Greetings', async (ctx, next) => { - if (ctx.from?.id === ctx.env.DEVELOPER_ID) { - await ctx.reply( - `Active users: ${/** Your business logic */}` - ) - } else { - await next() - } -}) - -devCommands.command('devlogout', 'Greetings', async (ctx, next) => { - if (ctx.from?.id === ctx.env.DEVELOPER_ID) { - await ctx.reply('Bye to me') - await ctx.setMyCommands(userCommands) - } else { - await next() - } - }) +import { userCommands } from './users/group.ts'; +import type { MyContext } from '../types.ts'; + +export const devCommands = new CommandGroup(); + +devCommands.command('devlogin', 'Login', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Hello, fellow developer! Are we having coffee today too?'); + await ctx.setMyCommands(userCommands, devCommands); + } else { + await next(); + } +}); + +devCommands.command('usercount', 'Count the active users', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply( `Active users: ${/** Your business logic */}`); + } else { + await next(); + } +}); + +devCommands.command('devlogout', 'Logout', async (ctx, next) => { + if (ctx.from?.id === ctx.env.DEVELOPER_ID) { + await ctx.reply('Until next commit!'); + await ctx.setMyCommands(userCommands); + } else { + await next(); + } + }); ``` ```ts [group.ts] -import sayHi from "./say-hi.ts"; +import sayHello from "./say-hello.ts"; import sayBye from "./say-bye.ts"; -import etc from "./another-command.ts"; import type { MyContext } from "../../types.ts"; export const userCommands = new CommandGroup() - .add([sayHi, sayBye]); + .add([sayHello, sayBye]); +``` + +```ts [say-hello.ts] +import type { MyContext } from "../../types.ts"; + +export default new Command("hello", "Say hello", async (ctx) => { + await ctx.reply("Hello, little user!"); +}); ``` -```ts [say-hi.ts] +```ts [say-bye.ts] import type { MyContext } from "../../types.ts"; -export default new Command("sayhi", "Greetings", async (ctx) => { - await ctx.reply("Hello little User!"); +export default new Command("bye", "Say bye", async (ctx) => { + await ctx.reply("Goodbye :)"); }); ``` ::: -Did you notice it is possible to register single initialized Commands via the `.add` method into the `CommandGroup` instance or also directly through the `.command(...)` method? -This allows for a one-file-only structure, like in the `admin.ts` file, or a more distributed file structure like in the `group.ts` file. +Did you know that, as shown in the example above, you can create commands either by using the `.command(...)` method directly or by registering initialized `Commands` into a `CommandGroup` instance with the `.add` method? +This approach lets you keep everything in a single file, like in `admin.ts`, or organize your commands across multiple files, like in `group.ts`. ::: tip Always Use Command Groups @@ -300,22 +307,22 @@ On their own they are useless, so make sure you do that at some point. ::: -The plugin also enforce you to have the same Context-type for a given `CommandGroup` and their respective `Commands` so you avoid at first glance that kind of silly mistake! +The plugin also ensures that a `CommandGroup` and its `Commands` share the same `Context` type, so you can avoid that kind of silly mistake at first glance! -Combining this knowledge with the following section will get your Command-game to the next level. +Combining this knowledge with the following section will get your command-game to the next level. ## Scoped Commands -Did you know you can allow different commands to be shown on different chats depending on the chat type, the language, and even the user status in a chat group? -That's what Telegram calls [**Command Scopes**](https://core.telegram.org/bots/features#command-scopes). +Did you know you can show different commands in various chats based on the chat type, language, and even user status within a chat group? +That's what Telegram refers to as [**command scopes**](https://core.telegram.org/bots/features#command-scopes). -Now, Command Scopes are a cool feature, but using it by hand can get really messy, since it's hard to keep track of all the scopes and what commands they present. -Plus, by using Command Scopes on their own, you have to do manual filtering inside each command to ensure they'll only run for the correct scopes. -Syncing those two things up can be a nightmare, and that's why this plugin exists. -Check how it's done. +Now, command scopes are a cool feature, but using them by hand can get really messy since it's hard to keep track of all the scopes and the commands they present. +Plus, by using command scopes on their own, you have to do manual filtering inside each command to ensure they run only for the correct scopes. +Syncing those two things up can be a nightmare, which is why this plugin exists. +Let's check how it's done. The `Command` class returned by the `command` method exposes a method called `addToScope`. -This method takes in a [`BotCommandScope`](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be ran at that specific scope. +This method takes in a [`BotCommandScope`](/ref/types/botcommandscope) together with one or more handlers, and registers those handlers to be run at that specific scope. You don't even need to worry about calling `filter`, the `addToScope` method will guarantee that your handler only gets called if the context is right. @@ -325,7 +332,7 @@ Here's an example of a scoped command: const myCommands = new CommandGroup(); myCommands - .command("start", "Initializes bot configuration") + .command("hello", "Say hello") .addToScope( { type: "all_private_chats" }, (ctx) => ctx.reply(`Hello, ${ctx.chat.first_name}!`), @@ -336,8 +343,8 @@ myCommands ); ``` -The `start` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. -Now if you call `myCommands.setCommands`, the `start` command will be registered to both private and group chats. +The `hello` command can now be called from both private and group chats, and it will give a different response depending on where it gets called from. +Now, if you call `myCommands.setCommands`, the `hello` command menu will be displayed in both private and group chats. Here's an example of a command that's only accessible to group admins. @@ -350,10 +357,10 @@ adminCommands ); ``` -And here is an example of a command that's only accessible in groups +And here is an example of a command that's only accessible in groups. ```js -myCommands +groupCommands .command("fun", "Laugh") .addToScope( { type: "all_group_chats" }, @@ -361,9 +368,9 @@ myCommands ); ``` -Notice that when you call the `command` method, it opens up a new command. +Notice that the `command` method could receive the handler too. If you give it a handler, that handler will apply to the `default` scope of that command. -Calling `addToScope` on that command will then add a new handler, which will be filtered to that scope. +Calling `addToScope` on that command will then add a new handler, which will be filtered for that scope. Take a look at this example. ```ts @@ -371,7 +378,7 @@ myCommands .command( "default", "Default command", - // This will be called when not on a group chat, or when the user is not an admin + // This will be called when not in a group chat (ctx) => ctx.reply("Hello from default scope"), ) .addToScope( @@ -388,7 +395,7 @@ myCommands ## Command Translations -Another powerful feature is the ability to set different names for the same command, and their respective descriptions based on the user language. +Another powerful feature is the ability to set different names and their respective descriptions for the same command based on the user language. The commands plugin makes that easy by providing the `localize` method. Check it out: @@ -400,9 +407,10 @@ myCommands .localize("pt", "ola", "Dizer olá"); ``` -Add as many as you want! The plugin will take care of registering them for you when you call `myCommands.setCommands`. +Add as many as you want! +The plugin will take care of registering them for you when you call `myCommands.setCommands`. -For convenience, grammY exports a `LanguageCodes` enum-like object that you can use for a more idiomatic approach: +For convenience, grammY exports a `LanguageCodes` enum-like object, which you can use to create a more idiomatic approach. ::: code-group @@ -453,9 +461,9 @@ myCommands.command( ::: -### Localizing Commands With the Internationalization Plugin +### Localizing Commands with the Internationalization Plugin -If you are looking to have your localized command names and descriptions bundle inside your `.ftl` files, you could make use of the following idea: +If you are looking to have your localized command names and descriptions bundled inside your `.ftl` files, you could make use of the following approach: ```ts function addLocalizations(command: Command) { @@ -474,9 +482,13 @@ myCommands.commands.forEach(addLocalizations); ## Finding the Nearest Command -Even though Telegram is capable of auto completing the registered commands, sometimes users do type them manually and, in some cases, happen to make mistakes. -The commands plugin helps you deal with that by allowing you to suggest a command that might be what the user wanted in the first place. -It is compatible with custom prefixes, so you don't have to worry about that, and its usage is quite straightforward: +Telegram can automatically complete registered commands. +However, sometimes users still type these commands manually and may make mistakes. + +To help with this, the commands plugin suggests a command that the user might have intended to use. + +This plugin works with custom prefixes, so you don’t need to worry about compatibility. +Plus, it’s easy to use. ::: code-group @@ -493,7 +505,7 @@ const myCommands = new CommandGroup(); bot // Check if there is a command .filter(commandNotFound(myCommands)) - // If so, that means it wasn't handled by any of our commands. + // If so, that means it wasn't handled by any of our commands .use(async (ctx) => { // We found a potential match if (ctx.commandSuggestion) { @@ -516,7 +528,7 @@ const myCommands = new CommandGroup(); bot // Check if there is a command .filter(commandNotFound(myCommands)) - // If so, that means it wasn't handled by any of our commands. + // If so, that means it wasn't handled by any of our commands .use(async (ctx) => { // We found a potential match if (ctx.commandSuggestion) { @@ -532,19 +544,24 @@ bot ::: -Behind the scenes, `commandNotFound` will use the `getNearestCommand` context method which by default will prioritize commands that correspond to the user language. -If you want to opt-out of this behavior, you can pass the `ignoreLocalization` flag set to true. -It is possible to search across multiple `CommandGroup` instances, and `ctx.commandSuggestion` will be the most similar command, if any, across them all. -It also allows to set the `ignoreCase` flag, which will ignore casing while looking for a similar command and the `similarityThreshold` flag, which controls how similar a command name has to be to the user input for it to be recommended. +The `commandNotFound` gives you some options to configure: + +- `ignoreLocalization`: By default, `commandNotFound` prioritizes commands that match the user language. + To opt-out, set this option to `true`. +- `ignoreCase`: Allows the plugin to ignore letter casing when searching for similar commands. +- `similarityThreshold`: Determines how similar a command name must be to the user input in order to be suggested. + +Additionally, you can search across multiple `CommandGroup` instances by providing an array of `CommandGroup` instead of just one instance. The `commandNotFound` function will only trigger for updates which contain command-like text similar to your registered commands. -For example, if you only have registered [commands with a custom prefix](#prefix) like `?`, it will trigger the handler for anything that looks like your commands, e.g: `?sayhi` but not `/definitely_a_command`. +For example, if you only have registered [commands with a custom prefix](#prefix) like `?`, it will trigger the handler for anything that looks like your commands, e.g. `?sayhi` but not `/definitely_a_command`. + Same goes the other way, if you only have commands with the default prefix, it will only trigger on updates that look like `/regular` and `/commands`. The recommended commands will only come from the `CommandGroup` instances you pass to the function. -So you could defer the checks into multiple, separate filters. +This means you can separate the checks into multiple, separate filters. -Let's use the previous knowledge to inspect the next example: +Now, let's apply this understanding to the next example. ```ts const myCommands = new CommandGroup(); @@ -559,21 +576,23 @@ otherCommands.command("bread", "eat a toast", () => {}) // Register each language-specific command group -// Let's assume the user is French and typed /Papi +// Let's assume the user is French and typed '/Papi' bot - // this filter will trigger for any command-like as '/regular' or '?custom' + // This filter will trigger for any command-like as '/regular' or '?custom' .filter(commandNotFound([myCommands, otherCommands], { ignoreLocalization: true, ignoreCase: true, })) .use(async (ctx) => { - ctx.commandSuggestion === "?papa"; // evaluates to true + ctx.commandSuggestion === "?papa"; // Evaluates to true }); ``` -If the `ignoreLocalization` was falsy instead we would have gotten "`ctx.commandSuggestion` equals `/pain`". -We could add more filters like the above, with different parameters or `CommandGroup`s to check against. -There are a lot of possibilities! +If the `ignoreLocalization` were set to false, then `ctx.commandSuggestion` would equal `/pain`. + +We could also add more filters similar to the one mentioned earlier by using different parameters or `CommandGroup`s to check against. + +There are many possibilities for how we can customize this! ## Command Options @@ -582,22 +601,22 @@ These options allow you to further customize how your bot handles commands, givi ### `ignoreCase` -By default commands will match the user input in a case-sensitive manner. -Having this flag set, for example, in a command named `/dandy` will match `/DANDY` the same as `/dandY` or any other case-only variation. +By default, commands match user input in a case-sensitive manner. +When this flag is set, a command like `/dandy` will match variations such as `/DANDY` or `/dandY`, regardless of case. ### `targetedCommands` When users invoke a command, they can optionally tag your bot, like so: `/command@bot_username`. You can decide what to do with these commands by using the `targetedCommands` config option. -With it you can choose between three different behaviors: +With this option, you can choose between three different behaviors: -- `ignored`: Ignores commands that mention your bot's user -- `optional`: Handles both commands that do and that don't mention the bot's user -- `required`: Only handles commands that mention the bot's user +- `ignored`: Ignores commands that mention your bot's username. +- `optional`: Handles both commands that mention the bot's username and ones that don't. +- `required`: Only handles commands that mention the bot's username. ### `prefix` -Currently, only commands starting with `/` are recognized by Telegram and, thus, by the [command handling done by the grammY core library](../guide/commands). +Currently, only commands starting with `/` are recognized by Telegram and, consequently, by the [command handling done by the grammY core library](../guide/commands). In some occasions, you might want to change that and use a custom prefix for your bot. That is made possible by the `prefix` option, which will tell the commands plugin to look for that prefix when trying to identify a command. @@ -611,13 +630,15 @@ Commands with custom prefixes cannot be shown in the Commands Menu. ### `matchOnlyAtStart` -When [handling commands](../guide/commands), the grammY core library will only recognize commands that start on the first character of a message. +When [handling commands](../guide/commands), the grammY core library recognizes commands only if they start at the first character of a message. The commands plugin, however, allows you to listen for commands in the middle of the message text, or in the end, it doesn't matter! -All you have to do is set the `matchOnlyAtStart` option to `false`, and the rest will be done by the plugin. +Simply set the `matchOnlyAtStart` option to `false`, and the plugin will handle the rest. ## RegExp Commands -This feature is for those who are really looking to go wild, it allows you to create command handlers based on regular expressions instead of static strings, a basic example would look like: +This feature is for those who want to go wild. +It allows you to create command handlers based on regular expressions instead of static strings. +A basic example would look like this: ```ts myCommands @@ -627,7 +648,7 @@ myCommands ); ``` -This command handler will trigger on `/delete_me` the same as in `/delete_you`, and it will reply "Deleting me" in the first case and "Deleting you" in the later, but will not trigger on `/delete_` nor `/delete_123xyz`, passing through as if it wasn't there. +This command handler will trigger on `/delete_me` the same as on `/delete_you`, and it will reply `Deleting me` in the first case and `Deleting you` in the second, but will not trigger on `/delete_` nor `/delete_123xyz`, passing through as if it wasn't there. ## Plugin Summary From ec14c95364ebc0a0c4d5d0ce1cb99662b076de39 Mon Sep 17 00:00:00 2001 From: Hero Protagonist Date: Sun, 2 Feb 2025 20:14:53 -0300 Subject: [PATCH 59/59] fix: Commands plugin typo in flavor installation #1186 --- site/docs/plugins/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/docs/plugins/commands.md b/site/docs/plugins/commands.md index 0ab57c8b8..eba12bf3d 100644 --- a/site/docs/plugins/commands.md +++ b/site/docs/plugins/commands.md @@ -103,7 +103,7 @@ Here's how to do that with the commands plugin: ```ts [TypeScript] // Use the flavor to create a custom context -type MyContext = Context & CommandsFlavor; +type MyContext = Context & CommandsFlavor; // Use the new context to instantiate your bot const bot = new Bot(""); // <-- put your bot token between the "" (https://t.me/BotFather)