Запити фільтрування і bot.on()
Перший аргумент bot
— це текст, який називається запитом фільтрування.
Вступ
Більшість (усі?) інших фреймворків для створення ботів дозволяють виконувати примітивну форму фільтрації оновлень: наприклад, тільки on("message")
тощо. Реалізація інших видів фільтрації повідомлень залишається за розробниками, що часто призводить до нескінченних операторів if
у коді.
Однак grammY постачається з власною мовою запитів, яку ви можете використовувати, щоб фільтрувати саме ті повідомлення, які вам потрібні.
Це дозволяє використовувати понад 1150-ти різних фільтрів, і з часом ми можемо додати більше. Кожен дійсний фільтр може бути автоматично доповнений у вашому редакторі коду. Отже, ви можете просто ввести bot
, відкрити автодоповнення та шукати за всіма запитами, ввівши щось.
Визначення типу bot
охопить запит фільтрування, який ви вибрали. Отже, буде звужено кілька типів у контексті, які, як відомо, присутні в повідомленні.
bot.on("message", async (ctx) => {
// Може бути `undefined`, якщо отримане повідомлення не містить тексту.
const text: string | undefined = ctx.msg.text;
});
bot.on("message:text", async (ctx) => {
// Текст завжди наявний, оскільки цей обробник викликається під час отримання текстового повідомлення.
const text: string = ctx.msg.text;
});
2
3
4
5
6
7
8
Отже, grammY реалізує запити фільтрування як під час виконання, так і на рівні типів.
Приклади запитів
Ось кілька прикладів запитів:
Звичайні запити
Прості фільтри для оновлень і підфільтри:
bot.on("message"); // викликається при отриманні будь-якого повідомлення
bot.on("message:text"); // тільки текстові повідомлення
bot.on("message:photo"); // тільки повідомлення з фото
2
3
Фільтр для сутностей
Підфільтри, які йдуть на один рівень глибше:
bot.on("message:entities:url"); // повідомлення, що містять URL
bot.on("message:entities:code"); // повідомлення, що містять фрагмент коду
bot.on("edited_message:entities"); // редаговані повідомлення з будь-якими сутностями
2
3
Пропуск значень
Ви можете пропустити деякі значення у запиті фільтрування. grammY шукатиме серед усіх значень на місці пропущених, які відповідають вашому запиту.
bot.on(":text"); // будь-які текстові повідомлення та будь-які текстові дописи на каналі
bot.on("message::url"); // повідомлення з URL-адресою в тексті або підписі
bot.on("::email"); // повідомлення або дописи на каналі з електронними листами в тексті або підписах
2
3
Якщо не вказати перше значення, це відповідає повідомленням і дописам на каналі. Памʼятайте, що ctx
надає вам доступ як до повідомлень, так і до дописів каналів, залежно від того, що відповідає запиту.
Виключення другого значення відповідає охопленню усіх сутностей у тексті повідомлення і підпису. Ви можете виключити і перше, і друге значення одночасно.
Скорочення
Механізм запитів grammY дозволяє визначити вишукані скорочення, які групують повʼязані запити.
msg
Скорочення msg
групує нові повідомлення та нові дописи на каналі. Іншими словами, використання msg
еквівалентно обробленню "message"
і "channel
.
bot.on("msg"); // будь-яке повідомлення або допис на каналі
bot.on("msg:text"); // те саме, що й `:text`
2
edit
Скорочення edit
групує редаговані повідомлення та редаговані дописи каналів. Інакше кажучи, використання edit
еквівалентно обробленню edited
та edited
.
bot.on("edit"); // будь-які редаговані повідомлення або дописи каналів
bot.on("edit:text"); // редаговані повідомлення з текстом
bot.on("edit::url"); // редаговані повідомлення з URL-адресою в тексті чи підписі
bot.on("edit:location"); // редаговані розташування
2
3
4
:media
Скорочення :
групує повідомлення з фото чи відео. Інакше кажучи, використання :
еквівалентно обробленню :
та:
.
bot.on("message:media"); // повідомлення з фото чи відео
bot.on("edited_channel_post:media"); // редаговані дописи каналів з фото чи відео
bot.on(":media"); // повідомлення або дописи каналів з фото чи відео
2
3
:file
Скорочення :
групує всі повідомлення, які містять файл. Інакше кажучи, використання :
еквівалентно обробленню :
, :
, :
, :
, :
, :
, :
та :
. Отже, ви можете бути впевнені, що await ctx
поверне обʼєкт файлу.
bot.on(":file"); // повідомлення або дописи каналів з файлами
bot.on("edit:file"); // редаговані повідомлення або дописи каналів з файлами
2
Синтаксичний цукор
Є дві особливі частини запитів, які роблять фільтрування ще зручнішим. Ви можете фільтрувати ботів у запитах за допомогою частини запиту :
. Синтаксичний цукор :
можна використовувати для фільтрування посилань на вашого бота в запитах, завдяки чому буде виконуватися порівняння ідентифікаторів користувачів з ідентифікатором боту.
// Сервісне повідомлення про бота, який приєднався до чату
bot.on("message:new_chat_members:is_bot");
// Сервісне повідомлення про видалення вашого бота
bot.on("message:left_chat_member:me");
2
3
4
Зауважте, що хоча цей синтаксичний цукор корисний для роботи зі службовими повідомленнями, його не слід використовувати для виявлення того, чи хтось дійсно приєднується до чату чи залишає його. Сервісні повідомлення — це повідомлення, які інформують користувачів у чаті, і деякі з них не завжди відображаються. Наприклад, у великих групах не буде службових повідомлень про користувачів, які приєднуються до чату або залишають його. Тому, ваш бот може цього не помітити. Натомість вам варто стежити за оновленнями учасників чату.
Поєднання кількох запитів
Ви можете комбінувати будь-яку кількість запитів фільтрування з можливістю перевірки оновлення на відповідність усім фільтрам (логічна операція І) або перевірки на відповідність принаймні одному з них (логічна операція АБО).
Поєднання запитів операцією АБО
Якщо ви хочете встановити якийсь проміжний обробник після запитів фільтрування, поєднаних операцією АБО, вам треба передати запити в bot
як масив.
// Виконується, якщо оновлення стосується повідомлення АБО редагованого повідомлення
bot.on(["message", "edited_message"] /* , ... */);
// Виконується, якщо в тексті чи підписі знайдено хештег АБО електронний лист АБО згадку
bot.on(["::hashtag", "::email", "::mention"] /* , ... */);
2
3
4
Проміжний обробник буде виконано, якщо оновлення відповідає будь-якому із зазначених запитів. Порядок запитів не має значення.
Поєднання запитів операцією І
Якщо ви хочете встановити якийсь проміжний обробник після запитів фільтрування, поєднаних операцією І, вам треба створити ланцюжок викликів bot
з відповідними запитами.
// Відповідає пересланим URL-адресам
bot.on("::url").on(":forward_origin" /* , ... */);
// Відповідає фотографіям, які містять хештег у підписі
bot.on(":photo").on("::hashtag" /* , ... */);
2
3
4
Проміжний обробник буде виконано, якщо всі надані запити збігаються. Порядок запитів не має значення.
Побудова складних запитів
Технічно можливо обʼєднати запити фільтрування в більш складні формули, якщо вони знаходяться у КНФ, хоча це навряд чи буде корисним.
bot
// Відповідає будь-яким дописам каналів або пересланим повідомленям, ...
.on(["channel_post", ":forward_origin"])
// ... які містять текст ...
.on(":text")
// ... принаймні з однією URL-адресою, хештегом або кештегом.
.on(["::url", "::hashtag", "::cashtag"] /* , ... */);
2
3
4
5
6
7
Виведення типу ctx
здійснить перевірку всього ланцюжка викликів і дослідить кожен елемент усіх трьох викликів .on
. Наприклад, це дозволить виявити, що ctx
є обовʼязково наявною властивістю для наведеного вище фрагмента коду.
Корисні поради
Розглянемо деякі менш відомі можливості запитів фільтрування, які можуть стати в нагоді. Деякі з них є дещо специфічними і знадобляться не всім, тому можете сміливо переходити до наступного розділу.
Оновлення учасників чату
Ви можете використовувати наступний запит фільтрування, щоб отримувати оновлення статусу свого бота.
bot.on("my_chat_member"); // заблокований, розблокований, доданий або видалений
У приватних чатах це спрацьовує, коли бот блокується або розблоковується. У групах це спрацьовує, коли бот додається або видаляється. Тепер ви можете перевірити ctx
, щоб визначити, що саме сталося.
Не плутайте це з
bot.on("chat_member");
який можна використовувати для визначення змін статусу інших учасників чату, коли люди приєднуються, отримують нові дозволи тощо.
Зауважте, що оновлення
chat
потрібно ввімкнути явно, вказавши_member allowed
під час запуску бота._updates
Комбінування запитів з іншими методами
Ви можете комбінувати запити фільтрування з іншими методами класу Composer
(довідка API), такими як command
або filter
. Це дозволяє створювати потужні сценарії обробки повідомлень.
bot.on(":forward_origin").command("help"); // переслані команди /help
// Обробляємо команди лише в приватних чатах.
const pm = bot.chatType("private");
pm.command("start");
pm.command("help");
2
3
4
5
6
Фільтрування за типом відправника повідомлення
Існує пʼять різних можливих типів авторів повідомлень у Telegram:
- Автори дописів на каналі.
- Автоматичні пересилання з повʼязаних каналів у групи для обговорення.
- Звичайні облікові записи користувачів, включаючи ботів, тобто “звичайні” повідомлення.
- Адміністратори, які надсилають повідомлення від імені групи (анонімні адміністратори).
- Користувачі, які надсилають повідомлення від імені свого каналу.
Ви можете комбінувати запити фільтрування з іншими механізмами обробки оновлень, щоб визначити тип автора повідомлення.
// Дописи на каналі, надіслані `ctx.senderChat`
bot.on("channel_post");
// Автоматичне пересилання з каналу `ctx.senderChat`
bot.on("message:is_automatic_forward");
// Звичайні повідомлення, надіслані `ctx.from`
bot.on("message").filter((ctx) => ctx.senderChat === undefined);
// Анонімний адмін у `ctx.chat`
bot.on("message").filter((ctx) => ctx.senderChat?.id === ctx.chat.id);
// Користувачі, які надсилають повідомлення від імені свого каналу `ctx.senderChat`
bot.on("message").filter((ctx) =>
ctx.senderChat !== undefined && ctx.senderChat.id !== ctx.chat.id
);
2
3
4
5
6
7
8
9
10
11
12
13
Фільтрування за властивостями користувача
Якщо ви хочете фільтрувати за іншими властивостями користувача, вам потрібно виконати додатковий запит: наприклад, await ctx
для отримання автора повідомлення. Фільтри не виконують запити до API приховано від вас. Виконати цей вид фільтрації все одно просто:
bot.on("message").filter(
async (ctx) => {
const user = await ctx.getAuthor();
return user.status === "creator" || user.status === "administrator";
},
(ctx) => {
// Обробляємо повідомлення від авторів і адміністраторів.
},
);
2
3
4
5
6
7
8
9
Повторне використання логіки запитів фільтрування
Всередині bot
покладається на функцію під назвою match
. Він бере запит фільтрування і компілює його в предикатну функцію. Далі цей предикат передається в bot
для фільтрування оновлень.
Ви можете імпортувати match
напряму, якщо хочете використовувати його у власній логіці. Наприклад, ви можете припинити обробку оновлень, які відповідають певному запиту:
// Припинити обробку всіх текстових повідомлень або текстових дописів каналів.
bot.drop(matchFilter(":text"));
2
Аналогічно, ви можете використовувати типи запитів фільтрування, які grammY використовує внутрішньо:
Повторне використання типів запитів фільтрування
Всередині match
використовує предикати типів TypeScript, щоб звузити тип ctx
. Він приймає тип C extends Context
і Q extends Filter
і створює ctx is Filter<C
. Іншими словами, тип Filter
— це те, що ви фактично отримуєте для вашого ctx
у проміжному обробнику.
Ви можете імпортувати Filter
напряму, якщо хочете використовувати його у власній логіці. Наприклад, ви можете визначити функцію обробки, яка приймає певні обʼєкти контексту, які вже були деяким чином відфільтровані:
function handler(ctx: Filter<Context, ":text">) {
// обробляємо звужений обʼєкт контексту
}
bot.on(":text", handler);
2
3
4
5
Перегляньте довідку API для
match
,Filter Filter
іFilter
для продовження читання.Query
Мова запитів
Цей розділ призначений для користувачів, які хочуть глибше зрозуміти запити фільтрування в grammY, але він не містить жодних знань, необхідних для створення бота.
Структура запиту
Кожен запит складається максимум з трьох частин. Залежно від того, скільки частин містить запит, ми розрізняємо запити L1, L2 і L3, такі як "message"
, "message:
і "message:
відповідно.
Частини запиту розділені двокрапками (:
). Ми називаємо частину до першої двокрапки або кінця рядка L1 частиною запиту. Ми називаємо частину від першої двокрапки до другої двокрапки або до кінця рядка L2 частиною запиту. Ми називаємо частину від другої двокрапки до кінця рядка L3 частиною запиту.
Наприклад:
Фільтр | L1 частина | L2 частина | L3 частина |
---|---|---|---|
"message" | "message" | undefined | undefined |
"message: | "message" | "entities" | undefined |
"message: | "message" | "entities" | "mention" |
Перевірка запиту
Незважаючи на те, що система типів повинна виявляти всі неправильні запити фільтрування під час компіляції, grammY також перевіряє всі передані запити фільтрування під час виконання протягом початкового налаштування. Кожен переданий запит фільтрування порівнюється зі структурою валідації, яка перевіряє його дійсність. Окрім того, що добре, коли помилка виникає одразу під час початкового налаштування, а не під час виконання, раніше траплялося, що помилки в TypeScript спричиняли серйозні проблеми зі складною системою виведення типів, яка забезпечує роботу запитів фільтрування. Якщо це повториться в майбутньому, це допоможе уникнути проблем, які могли б виникнути в іншому випадку. У цьому випадку вам будуть надані корисні повідомлення про помилки.
Продуктивність
grammY може перевіряти кожен запит фільтрування за сталий час на оновлення, незалежно від структури запиту чи вхідного оновлення.
Перевірка запитів фільтрування відбувається лише один раз, коли бот ініціалізується та викликається bot
.
Під час запуску grammY створює предикатну функцію із запиту фільтрування, розділяючи його на частини. Кожна частина буде зіставлена з функцією, яка виконує одну перевірку істинності для властивості об’єкта або дві перевірки, якщо частина запиту пропущена і потрібно перевірити два значення. Потім ці функції обʼєднуються у предикат, який перевіряє лише ті значення, що мають відношення до запиту, без перебору ключів обʼєкта Update
.
Ця система використовує менше операцій, ніж деякі конкуруючі бібліотеки, яким потрібно виконувати перевірку вмісту у масивах для маршрутизації оновлень. Система запитів фільтрування grammY працює швидше, попри те, що набагато потужніша.
Безпека типів
Як згадувалося вище, запити фільтрування автоматично звужують певні властивості об’єкта контексту. Предикат, отриманий з одного або декількох запитів фільтрування, є предикатом типу TypeScript, який виконує це звуження. Загалом, ви можете довіряти тому, що виведення типів працює коректно. Якщо властивість виводиться як присутня, ви можете безпечно покладатися на неї. Якщо властивість виводиться як потенційно відсутня, то це означає, що існують певні випадки, коли вона відсутня. Не варто виконувати приведення типів за допомогою оператора !
.
Для вас може бути неочевидним, що це за випадки. Не соромтеся запитувати в груповому чаті, якщо не можете розібратися.
Обчислення цих типів є складним. При розробці запитів фільтрування було використано багато знань про Bot API. Якщо ви хочете дізнатися більше про основні підходи до обчислення цих типів, ви можете подивитися виступ на You
TIP
Зауважте, що ця доповідь викладається англійською мовою. Якщо ви погано розумієте розмовну англійську, скористайтеся англійськими або автоперекладеними субтитрами.