Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commands plugin page #1018

Merged
merged 92 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
1705dd8
Draft commands plugin page
KnorpelSenf Mar 21, 2024
3ae0dba
Update site/docs/plugins/commands.md
KnorpelSenf Mar 22, 2024
dbf4955
Merge branch 'main' into commands-plugin
KnorpelSenf Mar 22, 2024
8f1736f
feat: add complete docs for command plugin
roziscoding Apr 27, 2024
bda0e07
Merge remote-tracking branch 'origin/main' into commands-plugin
roziscoding Apr 27, 2024
8ef037b
chore: deno fmt
roziscoding Apr 27, 2024
baad642
fix: broken link
roziscoding Apr 27, 2024
1901b7a
feat: add commands plugin page to navigation menu
roziscoding Apr 27, 2024
cd12907
Add grammy_commands to modules.json
quadratz Apr 28, 2024
3cc79c6
Apply suggestions from code review
roziscoding Apr 29, 2024
8e78422
Apply suggestions from code review
roziscoding Apr 29, 2024
c5b62b8
- restructure read flow to focus first on a full working example
carafelix Jun 18, 2024
1fd817d
- reduce example length about LanguageCode utility obj
carafelix Jun 18, 2024
fb7ee75
- add note on upperCased command names
carafelix Jun 18, 2024
5099a4a
- add section for improving file structure related to command organiz…
carafelix Jun 20, 2024
4e3c5c7
remove repetitio
carafelix Jun 20, 2024
67b0b0b
change txt to tip
carafelix Jun 23, 2024
9f79cbf
Merge branch 'main' into commands-plugin
LWJerri Jul 20, 2024
1280c04
- update Commands class name onto CommandGroup
carafelix Jul 24, 2024
8a6f458
add starting point example for usage with i18n
carafelix Jul 24, 2024
39fe557
Merge branch 'main' into commands-plugin
roziscoding Aug 25, 2024
26f56d1
Apply suggestions from code review
carafelix Aug 25, 2024
3868e5c
chore: deno fmt
roziscoding Aug 25, 2024
ddf8fe6
fix tip block
carafelix Aug 25, 2024
a14eeda
fix more tip blocks
carafelix Aug 25, 2024
f28e514
fix danger block
carafelix Aug 29, 2024
7fb17c1
add section for importing Commands into a CommandGroup
carafelix Aug 29, 2024
090b5c9
add a little newline into the command grouping section
carafelix Aug 29, 2024
5ccc2b7
add forgotten personal pronoun
carafelix Aug 29, 2024
bd55f73
improve clarity related to the context enforcement in commandGroups a…
carafelix Aug 29, 2024
4707511
add tip block regarding that a Command by it's own, one that is not r…
carafelix Aug 30, 2024
54620d5
Merge branch 'main' into commands-plugin
KnorpelSenf Sep 11, 2024
3c27620
Merge branch 'main' into commands-plugin
roziscoding Sep 12, 2024
7404975
Merge branch 'main' into commands-plugin
carafelix Sep 16, 2024
24e2aac
feat: address review suggestions
roziscoding Oct 15, 2024
73dbf02
feat: add a title to every note
roziscoding Oct 15, 2024
fa5df22
chore: fix lint errors
roziscoding Oct 15, 2024
bd6ce07
fix new linter errors
roziscoding Oct 15, 2024
894b07b
Merge branch 'main' into commands-plugin
KnorpelSenf Oct 15, 2024
3a52bdd
update section title
roziscoding Oct 15, 2024
24399c0
Merge branch 'main' into commands-plugin
KnorpelSenf Oct 15, 2024
87876e7
Delete accidentally committed package-lock.json
KnorpelSenf Oct 16, 2024
d5592b7
Fix formatting of suggestion
KnorpelSenf Oct 16, 2024
19c6187
update code blocks to remote types.d.ts
roziscoding Oct 16, 2024
fdaf345
style: align formatting
KnorpelSenf Oct 16, 2024
f0e2ef8
fix: bad heroku links
KnorpelSenf Oct 16, 2024
ca5e99f
apply a few nitpick changes
roziscoding Oct 17, 2024
24b59ae
make links more uniform
KnorpelSenf Oct 19, 2024
8527204
Merge branch 'main' into commands-plugin
KnorpelSenf Oct 26, 2024
a6f0d36
fix cre lib reference
roziscoding Oct 31, 2024
40f94a5
Merge branch 'main' into commands-plugin
KnorpelSenf Oct 31, 2024
9c9037a
add missing commas
roziscoding Oct 31, 2024
90d9ba1
fix phrasing and typos
roziscoding Oct 31, 2024
8598028
Fix links
KnorpelSenf Oct 31, 2024
2bb1f18
update text about imports
roziscoding Nov 1, 2024
ea9e8bf
sync Russian
MasedMSD Nov 1, 2024
367a5ba
fix fmt & link
MasedMSD Nov 1, 2024
61b635f
Add Spanish
habemuscode Nov 1, 2024
06727dd
fix command name
roziscoding Nov 1, 2024
577af04
fix my braincells :clown:
MasedMSD Nov 2, 2024
3fb8978
sync to Ukrainian
niusia-ua Nov 2, 2024
d90e1cd
Sync changes to Indonesian
quadratz Nov 3, 2024
8e38d08
id: fix anchor
quadratz Nov 3, 2024
1510b80
id: fix typo
quadratz Nov 3, 2024
6bef3cf
Merge branch 'main' into commands-plugin
quadratz Nov 3, 2024
c269cdc
remove the untranslated text
niusia-ua Nov 3, 2024
2785d95
Update site/docs/uk/plugins/commands.md
niusia-ua Nov 3, 2024
c168845
Merge branch 'main' into commands-plugin
LWJerri Nov 4, 2024
92e169a
Fix link
habemuscode Nov 5, 2024
6336d95
Merge branch 'main' into commands-plugin
habemuscode Nov 5, 2024
afa9159
Apply suggestions from code review
niusia-ua Nov 5, 2024
06ccc22
Merge branch 'main' into commands-plugin
KnorpelSenf Nov 6, 2024
708e7a9
Merge branch 'main' into commands-plugin
KnorpelSenf Nov 6, 2024
2bfe2c8
Apply suggestions from code review
carafelix Nov 10, 2024
a72534d
fix: change order of registration for `.addToScope` examples
carafelix Nov 10, 2024
db0d40a
Apply suggestions from code review
carafelix Nov 10, 2024
e13a321
fix: remove unnecessary .env insinuation, improve command descriptions
carafelix Nov 10, 2024
1c2ab14
sync to last English version
MasedMSD Nov 11, 2024
7cf1355
forgot about typings in example 🤡
MasedMSD Nov 11, 2024
9f7315f
Fix fmt
MasedMSD Nov 11, 2024
8bdf253
sync zh #1018
Nov 12, 2024
d7801f9
Merge branch 'main' into commands-plugin
KnorpelSenf Nov 13, 2024
5a55ae4
Revert "fix: remove unnecessary .env insinuation, improve command de…
KnorpelSenf Nov 13, 2024
e17f974
Revert "fix: change order of registration for `.addToScope` examples"
KnorpelSenf Nov 13, 2024
8794f71
Translate db0d40ab9ba324ac7fb9b37a2df15d38a133ee0f
KnorpelSenf Nov 13, 2024
448b8ae
unsync Russian
MasedMSD Nov 13, 2024
7d00d75
Merge branch 'commands-plugin' of https://github.com/grammyjs/website…
MasedMSD Nov 13, 2024
6e8855f
Revert "Apply suggestions from code review"
carafelix Nov 13, 2024
229b4b3
Merge branch 'main' into commands-plugin
KnorpelSenf Nov 14, 2024
b12002d
命令 -> 指令
Nov 14, 2024
10711f5
fix anchor
Nov 14, 2024
fc19041
fix links
KnorpelSenf Nov 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package-lock.json
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions site/docs/.vitepress/configs/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/)$",
},
],
};

Expand Down
310 changes: 308 additions & 2 deletions site/docs/plugins/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,316 @@ 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).
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
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
- 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

Before we dive in, take a look at how you can register and handle a command with the plugin:

```js
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
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.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

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.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

## Automatic Command 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.

```js
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
const myCommands = new Commands()

myCommands.command("hello", "say hello", (ctx) => ctx.reply("Hi there!"))
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
myCommands.command("start", "Start the bot", (ctx) => ctx.reply("Starting..."))\
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

bot.use(myCommands)

await myCommands.setCommands(bot)
```
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

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.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
Now if you call `myCommands.setCommands`, the "start" command will be registered to both private and group chats.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

### Context shortcut
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

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.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
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<MyContext>("token");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const bot = new Bot<MyContext>("token");
const bot = new Bot<MyContext>(""); <-- put your bot token between the "" (https://t.me/BotFather)


// 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);
carafelix marked this conversation as resolved.
Show resolved Hide resolved
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,
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
// so you can set the logged out commands for everyone
await loggedOutCommands.setCommands(bot);
```

```js [JavaScript]
// Register the context shortcut
bot.use(commands());
Comment on lines +145 to +146
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Register the context shortcut
bot.use(commands());
const bot = new Bot(""); <-- put your bot token between the "" (https://t.me/BotFather)
// 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,
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
// 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.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
Neat, right?

## Command Translations
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

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

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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add imports for these code examples.

// Use the flavor to create a custom context
type MyContext = Context & CommandsFlavor;

// Use the new context to instantiate your bot
const bot = new Bot<MyContext>("token");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const bot = new Bot<MyContext>("token");
const bot = new Bot<MyContext>(""); // <-- put your bot token between the "" (https://t.me/BotFather)


// Register the context shortcut
bot.use(commands());
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

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}?`,
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
);
}

// Nothing seems to come close to what the user typed
await ctx.reply("Ops... I don't know that command :/");
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
});
```

```js [JavaScript]
// Register the context shortcut
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
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}?`,
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
);
}

// Nothing seems to come close to what the user typed
await ctx.reply("Ops... I don't know that command :/");
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
});
```

:::

## 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
roziscoding marked this conversation as resolved.
Show resolved Hide resolved

### `matchOnlyAtStart`

When [handling commands](../guide/commands.md), the grammY core library will only recognize commands that start on the first carachter of a message.
roziscoding marked this conversation as resolved.
Show resolved Hide resolved
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

- Name: `commands`
- [Source](https://github.com/grammyjs/commands)
- Reference
- [Reference](/ref/commands/)
10 changes: 10 additions & 0 deletions site/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading