Команди (commands
)
Обробка команд на стероїдах.
Цей плагін надає розширені можливості обробки команд, які доповнюють можливості основної бібліотеки. Ось короткий огляд того, що ви отримаєте за допомогою цього плагіна:
- Краща читабельність коду завдяки інкапсуляції проміжних обробників з визначеннями команд.
- Синхронізація меню команд користувача через
set
.MyCommands - Покращено групування та організацію команд.
- Обмеження доступу до команд, наприклад, для адміністраторів груп або певних каналів.
- Підтримка локалізації команд.
- Функція
Може
, що підказує правильну команду в разі помилки…, ви мали на увазі .. .? - Відповідність команд без урахування регістру.
- Налаштування кастомної поведінки для команд, у яких явно згадується імʼя користувача вашого бота, наприклад
/start@your
._bot - Користувацькі префікси команд, наприклад,
+
,?
або будь-який інший символ замість/
. - Підтримка команд, розташованих не на початку повідомлення.
- Команди з регулярними виразами!
Всі ці функції працюють на основі головних структур команд, які ви визначаєте для свого бота.
Базове використання
Перш ніж ми зануримося в роботу, подивіться, як ви можете зареєструвати та обробляти команди за допомогою плагіна:
const myCommands = new CommandGroup();
myCommands.command(
"hello",
"Привітатися",
(ctx) => ctx.reply(`Привіт, світе!`),
);
bot.use(myCommands);
2
3
4
5
6
7
8
9
Це зареєструє нову команду /hello
для вашого бота, яка буде оброблятися даним проміжним обробником.
Тепер давайте розглянемо деякі додаткові інструменти, які може запропонувати цей плагін.
Імпортування
Перш за все, ось як ви можете імпортувати всі необхідні типи і класи, надані плагіном.
import {
CommandGroup,
commandNotFound,
commands,
type CommandsFlavor,
} from "@grammyjs/commands";
2
3
4
5
6
const { CommandGroup, commandNotFound, commands } = require(
"@grammyjs/commands",
);
2
3
import {
CommandGroup,
commandNotFound,
commands,
type CommandsFlavor,
} from "https://deno.land/x/grammy_commands@v1.0.9/mod.ts";
2
3
4
5
6
Тепер, коли імпортування налагоджено, давайте подивимося, як ми можемо зробити команди доступними для наших користувачів.
Налаштування меню команд користувача
Після того, як ви визначили свої команди за допомогою класу Command
, ви можете викликати метод set
, щоб додати всі визначені команди до меню команд користувача.
const myCommands = new CommandGroup();
myCommands.command(
"hello",
"Привітатися",
(ctx) => ctx.reply("Привіт, світе!"),
);
bot.use(myCommands);
// Оновлюємо меню команд користувача.
await myCommands.setCommands(bot);
2
3
4
5
6
7
8
9
10
11
12
Це гарантує, що кожна зареєстрована команда зʼявиться в меню приватного чату з вашим ботом або коли користувачі введуть /
в чаті, учасником якого є ваш бот.
Скорочений метод контексту
Що робити, якщо ви хочете, щоб деякі команди відображалися тільки для певних користувачів? Наприклад, уявіть, що у вас є команди login
і logout
. Команда login
повинна відображатися тільки для користувачів, які вийшли з системи, і навпаки. Ось як це можна зробити за допомогою плагіна:
// Використовуйте розширювач, щоб створити власний контекст.
type MyContext = CommandsFlavor<Context>;
// Використайте новий контекст для створення бота.
const bot = new Bot<MyContext>(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather)
// Зареєструйте плагін.
bot.use(commands());
const loggedOutCommands = new CommandGroup<MyContext>();
const loggedInCommands = new CommandGroup<MyContext>();
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Тепер, коли користувач викликає /login
, його список команд буде змінено на команду logout
. Зручно, чи не так?
Обмеження щодо назв команд
Як зазначено в документації Telegram Bot API, назви команд мусять бути створені виключно з:
- Від 1 до 32 символів.
- Тільки малі англійські літери (a-z), цифри (0-9) та підкреслення (_).
Тому виклик set
або set
з неправильними назвами команд спричинить помилку. Команди, які не відповідають цим правилам, все одно можуть бути зареєстровані і оброблені, але не зʼявляться у меню команд користувача.
Майте на увазі, що set
і set
впливають лише на команди, що відображаються у меню команд користувача, а не на фактичний доступ до них. Про те, як реалізувати обмежений доступ до команд, ви дізнаєтеся у розділі обмежені команди.
Групування команд
Оскільки ми можемо розбивати і групувати наші команди на різні екземпляри, це дозволяє набагато ефективніше організувати файл команд.
Припустимо, ми хочемо мати команди тільки для розробників. Ми можемо досягти цього за допомогою наступної структури коду:
.
├── bot.ts
├── types.ts
└── commands/
├── admin.ts
└── users/
├── group.ts
├── say-hello.ts
└── say-bye.ts
2
3
4
5
6
7
8
9
Наведений нижче код демонструє, як можна реалізувати групу команд тільки для розробників і відповідно оновити меню команд клієнта Telegram. Зверніть увагу на різний підхід до реєстрації, використаний у файлах admin
та group
.
import { devCommands } from "./commands/admin.ts";
import { userCommands } from "./commands/users/group.ts";
import type { MyContext } from "./types.ts";
export const bot = new Bot<MyContext>(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather)
bot.use(commands());
bot.use(userCommands);
bot.filter((ctx) => ctx.from?.id == /** Помістіть свій ідентифікатор тут **/)
.use(devCommands);
2
3
4
5
6
7
8
9
10
11
import type { Context } from "grammy";
export type MyContext = CommandsFlavor<Context>;
2
3
import { userCommands } from "./users/group.ts";
import type { MyContext } from "../types.ts";
export const devCommands = new CommandGroup<MyContext>();
devCommands.command("devlogin", "Перевести меню команд у режим розробника", async (ctx, next) => {
await ctx.reply("Привіт, колего-розробнику! Ми сьогодні теж пʼємо каву?");
await ctx.setMyCommands(userCommands, devCommands);
});
devCommands.command("usercount", "Показати кількість користувачів", async (ctx, next) => {
await ctx.reply(`Всього користувачів: ${/** ваша бізнес-логіка */}`);
});
devCommands.command("devlogout", "Повернути меню команд в користувацький режим", async (ctx, next) => {
await ctx.reply("До наступного коміту!");
await ctx.setMyCommands(userCommands);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sayHello from "./say-hello.ts";
import sayBye from "./say-bye.ts";
import type { MyContext } from "../../types.ts";
export const userCommands = new CommandGroup<MyContext>()
.add([sayHello, sayBye]);
2
3
4
5
6
import type { MyContext } from "../../types.ts";
export default new Command<MyContext>("hello", "Привітатися", async (ctx) => {
await ctx.reply("Привіт, юний користувачу!");
});
2
3
4
5
import type { MyContext } from "../../types.ts";
export default new Command<MyContext>("bye", "Попрощатися.", async (ctx) => {
await ctx.reply("Бувай :)");
});
2
3
4
5
Чи знаєте ви, що, як показано у прикладі вище, ви можете створювати команди або безпосередньо за допомогою методу .command(
, або реєструючи ініціалізовані Commands
в екземплярі Command
за допомогою методу .add
? Такий підхід дозволяє тримати все в одному файлі, наприклад, як у admin
, або організувати команди у декількох файлах, наприклад, як у group
.
Завжди використовуйте групи команд
При створенні та експорті команд за допомогою конструктора Command
обовʼязково потрібно зареєструвати їх в екземплярі Command
за допомогою методу .add
. Без цього вони не працюють, тому обовʼязково зробіть це колись.
Плагін також гарантує, що Command
та її Commands
мають однаковий тип Context
, тому ви можете уникнути такої безглуздої на перший погляд помилки! Поєднання цих знань з наступним розділом підніме вашу роботу з командами на новий рівень.
Команди, обмежені областю видимості
Чи знали ви, що в різних чатах можна показувати різні команди залежно від типу чату, мови і навіть статусу користувача в групі чату? Це те, що в Telegram називається областями видимості команд.
Області видимості команд — це класна функція, але використання їх вручну може бути дуже заплутаним, оскільки важко відстежити всі області та команди, які вони представляють. Крім того, використовуючи області видимості команд самостійно, вам доведеться вручну фільтрувати кожну команду, щоб переконатися, що вона буде виконуватися тільки для правильних областей видимості. Синхронізація цих двох речей може перетворитися на справжній жах, і саме тому існує цей плагін. Погляньмо, як це робиться.
Клас Command
, що повертається методом command
, містить метод з назвою add
. Цей метод отримує Bot
разом з одним або декількома обробниками і реєструє ці обробники для виконання у вказаній області видимості.
Вам навіть не потрібно турбуватися про виклик filter
. Метод add
гарантує, що ваш обробник буде викликано лише за умови правильного контексту.
Ось приклад команди з областю видимості:
const myCommands = new CommandGroup();
myCommands
.command("hello", "Привітатися")
.addToScope(
{ type: "all_group_chats" },
(ctx) => ctx.reply(`Привіт, член ${ctx.chat.title}!`),
).addToScope(
{ type: "all_private_chats" },
(ctx) => ctx.reply(`Привіт, ${ctx.chat.first_name}!`),
);
2
3
4
5
6
7
8
9
10
11
Команду hello
тепер можна викликати як з приватних, так і з групових чатів, і вона даватиме різну відповідь залежно від того, звідки її викликано. Тепер, якщо ви викличете my
, меню команд hello
буде відображено як в приватних, так і в групових чатах.
Ось приклад команди, яка доступна лише адміністраторам груп:
adminCommands
.command("secret", "Виключно для адміністраторів")
.addToScope(
{ type: "all_chat_administrators" },
(ctx) => ctx.reply("Безкоштовний торт!"),
);
2
3
4
5
6
А ось приклад команди, яка доступна лише в групах:
groupCommands
.command("fun", "Сміх")
.addToScope(
{ type: "all_group_chats" },
(ctx) => ctx.reply("Хаха"),
);
2
3
4
5
6
Зверніть увагу, що метод command
також може отримувати обробник. Якщо ви надасте їй обробник, цей обробник буде застосовано до області видимості default
цієї команди. Виклик add
для цієї команди додасть новий обробник, який буде відфільтровано для цієї області видимості. Погляньте на цей приклад:
myCommands
.command(
"default",
"Типова команда",
// Ця команда буде викликана, якщо користувач не перебуває в груповому чаті.
(ctx) => ctx.reply("Привіт з типової області видимості"),
)
.addToScope(
{ type: "all_chat_administrators" },
// Ця команда буде викликана для адміністраторів в цій групі.
(ctx) => ctx.reply("Привіт, адміне!"),
).addToScope(
{ type: "all_group_chats" },
// Ця команда буде викликана лише для користувачів, які не є адміністраторами в групі.
(ctx) => ctx.reply("Привіт, груповий чате!"),
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Переклади команд
Ще однією потужною можливістю є встановлення різних назв та описів для однієї і тієї ж команди, що базуються на мові користувача. Плагін команд полегшує це завдання за допомогою методу localize
. Погляньте:
myCommands
// Вам потрібно встановити типову назву та опис.
.command("hello", "Say hello")
// А потім ви можете встановити локалізовані версії.
.localize("uk", "pryvit", "Привітатися");
2
3
4
5
Додавайте скільки завгодно! Плагін подбає про їхню реєстрацію, коли ви викличете my
.
Для зручності grammY експортує обʼєкт, подібний до переліку Language
, який ви можете використовувати для кращої зрозумілості коду:
import { LanguageCodes } from "@grammyjs/commands";
myCommands.command(
"chef",
"Steak delivery",
(ctx) => ctx.reply("Steak on the plate!"),
)
.localize(
LanguageCodes.Ukrainian,
"shefkukhar",
"Доставка стейків",
);
2
3
4
5
6
7
8
9
10
11
12
const { LanguageCodes } = require("@grammyjs/commands");
myCommands.command(
"chef",
"Steak delivery",
(ctx) => ctx.reply("Steak on the plate!"),
)
.localize(
LanguageCodes.Ukrainian,
"shefkukhar",
"Доставка стейків",
);
2
3
4
5
6
7
8
9
10
11
12
import { LanguageCodes } from "https://deno.land/x/grammy_commands@v1.0.9/mod.ts";
myCommands.command(
"chef",
"Steak delivery",
(ctx) => ctx.reply("Steak on the plate!"),
)
.localize(
LanguageCodes.Ukrainian,
"shefkukhar",
"Доставка стейків",
);
2
3
4
5
6
7
8
9
10
11
12
Локалізація команд за допомогою плагіна інтернаціоналізації
Якщо ви хочете, щоб ваші локалізовані назви команд та описи до них містилися у ваших файлах .ftl
, ви можете скористатися наступним підходом:
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);
2
3
4
5
6
7
8
9
10
11
12
Пошук найближчої команди
Telegram автоматично доповнює зареєстровані команди під час введення. Однак іноді користувачі все одно вводять ці команди повністю вручну і можуть робити помилки.
Щоб допомогти з цим, плагін команд пропонує команду, яку користувач, можливо, мав намір використати.
Ця функція працює з користувацькими префіксами, тому вам не потрібно турбуватися про сумісність. До того ж, він простий у використанні.
// Використовуйте розширювач, щоб створити власний контекст.
type MyContext = Context & CommandsFlavor;
// Використовуйте новий контекст для створення бота.
const bot = new Bot<MyContext>(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather)
const myCommands = new CommandGroup<MyContext>();
// Зареєструйте команди.
bot
// Перевірте, чи така команда не існує.
.filter(commandNotFound(myCommands))
// Якщо так, то це означає, що її не обробив жоден з наших обробників команд.
.use(async (ctx) => {
// Ми знайшли потенційний збіг.
if (ctx.commandSuggestion) {
return ctx.reply(
`Хм... Я не знаю цієї команди. Може, ви мали на увазі ${ctx.commandSuggestion}?`,
);
}
// Здається, ніщо не збігається з тим, що ввів користувач.
await ctx.reply("Упс... Я не знаю цієї команди. :/");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const bot = new Bot(""); // <-- Помістіть токен свого бота між "" (https://t.me/BotFather)
const myCommands = new CommandGroup();
// Зареєструйте команди.
bot
// Перевірте, чи така команда не існує.
.filter(commandNotFound(myCommands))
// Якщо так, то це означає, що її не обробив жоден з наших обробників команд.
.use(async (ctx) => {
// Ми знайшли потенційний збіг.
if (ctx.commandSuggestion) {
return ctx.reply(
`Хм... Я не знаю цієї команди. Може, ви мали на увазі ${ctx.commandSuggestion}?`,
);
}
// Здається, ніщо не збігається з тим, що ввів користувач.
await ctx.reply("Упс... Я не знаю цієї команди. :/");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Предикат command
приймає деякі параметри для налаштування його поведінки:
ignore
: не надавати пріоритет командам, які відповідають мові користувача.Localization ignore
: дозволяє плагіну ігнорувати регістр літер при пошуку схожих команд.Case similarity
: визначає, наскільки назва команди має бути схожою на введену користувачем, щоб її було запропоновано.Threshold
Крім того, ви можете шукати по декількох екземплярах Command
, ввівши масив Command
замість одного екземпляра.
Функція command
спрацьовуватиме лише для оновлень, які містять текст, схожий на ваші зареєстровані команди. Наприклад, якщо ви зареєстрували лише команди з власним префіксом на кшталт ?
, вона спрацює для всього, що схоже на ваші команди, наприклад: ?sayhi
, але не /definitely
.
Те ж саме відбудеться і в зворотному випадку, якщо у вас є лише команди з префіксом за замовчуванням, він спрацює лише для оновлень, які виглядають як /regular
і /commands
.
Рекомендовані команди надходитимуть лише з екземплярів Command
, які ви передали до функції. Отже, ви можете винести перевірку у декілька окремих фільтрів.
Тепер давайте застосуємо це розуміння до наступного прикладу:
const myCommands = new CommandGroup();
myCommands.command("dad", "calls dad", () => {}, { prefix: "?" })
.localize("uk", "tato", "подзвонити татові")
.localize("es", "papa", "llama a papa")
.localize("fr", "pere", "appelle papa");
const otherCommands = new CommandGroup();
otherCommands.command("bread", "eat a toast", () => {})
.localize("uk", "khlib", "зʼїсти тост")
.localize("es", "pan", "come un pan")
.localize("fr", "pain", "manger du pain");
bot.use(myCommands);
bot.use(otherCommands);
// Припустимо, що користувач є французом і ввів `/Papi`.
bot
// Цей фільтр спрацює для будь-якої команди, подібної до `/regular` або `?custom`.
.filter(commandNotFound([myCommands, otherCommands], {
ignoreLocalization: true,
ignoreCase: true,
}))
.use(async (ctx) => {
ctx.commandSuggestion === "?papa"; // Повертає true.
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Якщо ignore
було встановлено у false
, то ctx
дорівнюватиме /pain
.
Ми також можемо додати більше фільтрів, подібних до згаданого раніше, використовуючи різні параметри або Command
для перевірки.
Існує багато можливостей для налаштування!
Параметри команд
Існує кілька параметрів, які можна вказати для кожної команди, області видимості або глобально для екземпляра Command
. Ці параметри дозволяють вам додатково налаштувати те, як ваш бот обробляє команди, надаючи вам більшої гнучкості.
ignoreCase
За замовчуванням команди відповідають введеним користувачем даним з урахуванням регістру. Якщо цей прапорець встановлено, команді на зразок /dandy
відповідатимуть такі варіанти, як /DANDY
або /dandY
, незалежно від регістру.
targetedCommands
Коли користувачі викликають команду, вони можуть за бажанням позначити вашого бота, наприклад, так: /command@bot
. Ви можете вирішити, що робити з цими командами, за допомогою конфігураційного параметра targeted
. За допомогою цього параметра ви можете вибрати один з трьох варіантів поведінки:
ignored
: ігнорує команди, в яких згадується імʼя вашого боту.optional
: обробляє як команди, що згадують, так і команди, що не згадують імʼя вашого бота.required
: обробляє тільки команди, в яких згадується імʼя вашого бот.
prefix
Наразі Telegram розпізнає лише команди, що починаються з /
, а отже, і обробку команд виконує grammY. У деяких випадках ви можете змінити це і використовувати власний префікс для вашого бота. Це можливо за допомогою параметра prefix
, яка вкаже плагіну команд шукати цей префікс при спробі ідентифікувати команду.
Якщо вам коли-небудь знадобиться отримати сутності bot
з оновлення і потрібно, щоб вони були гідратовані з зареєстрованим вами власним префіксом, існує метод, спеціально розроблений для цього, який називається ctx
, який повертає той самий інтерфейс, що і ctx
.
DANGER
Команди з власними префіксами не відображаються у меню команд.
matchOnlyAtStart
При обробці команд основна бібліотека grammY розпізнає команди, лише якщо вони починаються з першого символу повідомлення. Плагін команд, однак, дозволяє вам прослуховувати команди в середині тексту повідомлення, або в кінці, це не має значення! Просто встановіть параметр match
у значення false
, і плагін впорається зі всім іншим.
Команди з регулярними виразами
Ця функція для тих, хто дійсно хоче розгулятись. Вона дозволяє створювати обробники команд на основі регулярних виразів замість статичних рядків, базовий приклад виглядатиме ось так:
myCommands
.command(
/delete_([a-zA-Z]+)/,
"Видалити це",
(ctx) => ctx.reply(`Видалення ${ctx.msg?.text?.split("_")[1]}`),
);
2
3
4
5
6
Цей обробник команд спрацює на /delete
так само, як і на /delete
, і відповість “Видалення me” у першому випадку і “Видалення you” у другому, але не спрацює на /delete
або /delete
, пропускаючи їх так, ніби їх і не було.