Контекст
Обʼєкт Context
(довідник API grammY) є важливою частиною grammY.
Кожного разу, коли ви реєструєте обробника на своєму обʼєкті бота, цей обробник отримує обʼєкт контексту.
bot.on("message", async (ctx) => {
// `ctx` — це обʼєкт `Context`.
});
2
3
Ви можете використовувати обʼєкт контексту, щоб
Зауважте, що обʼєкти контексту зазвичай називають ctx
.
Доступна інформація
Коли користувач надсилає повідомлення вашому боту, ви можете отримати доступ до цього повідомлення через ctx
. Наприклад, щоб отримати текст повідомлення, ви можете зробити наступне:
bot.on("message", async (ctx) => {
// Під час обробки текстових повідомлень `txt` буде типу `string`.
// Також може бути `undefined`, якщо отримане повідомлення не містить текст.
// Наприклад, фотографії, наліпки тощо.
const txt = ctx.message.text;
});
2
3
4
5
6
Так само ви можете отримати доступ до інших властивостей обʼєкта повідомлення: ctx
для інформації про чат, куди було надіслано повідомлення. Щоб дізнатися, які дані доступні, перегляньте частину про Message
в довіднику Telegram Bot API. Крім того, ви можете просто скористатися автодоповненням у своєму редакторі коду, щоб побачити можливі варіанти.
Якщо ви зареєструєте свій обробник для інших типів, ctx
також надасть вам інформацію про них. Наприклад:
bot.on("edited_message", async (ctx) => {
// Отримуємо новий, відредагований, текст повідомлення.
const editedText = ctx.editedMessage.text;
});
2
3
4
Ба більше, ви можете отримати доступ до оригінального обʼєкту Update
(довідка Telegram Bot API), який Telegram надсилає вашому боту. Цей обʼєкт оновлення (ctx
) містить усі дані, які є джерелом інформації для ctx
та інших методів.
Обʼєкт контексту завжди містить інформацію про вашого бота, яка доступна через ctx
.
Скорочені методи
Для обʼєкта контексту встановлено кілька скорочених методів.
Скорочений метод | Опис |
---|---|
ctx | Повертає обʼєкт повідомлення (навіть редагованого) |
ctx | Повертає ідентифікатор повідомлення для повідомлень або реакцій |
ctx | Повертає обʼєкт чату |
ctx | Повертає ідентифікатор чату з ctx або з оновлень business |
ctx | Повертає обʼєкт чату відправника з ctx (для повідомлень з анонімних каналів/груп) |
ctx | Повертає автора повідомлення, запиту зворотного виклику тощо |
ctx | Повертає ідентифікатор вбудованого повідомлення для запитів зворотного виклику або вибраних вбудованих результатів |
ctx | Повертає ідентифікатор бізнес-підключення для повідомлень або оновлень бізнес-підключень |
ctx | Повертає сутності повідомлення та їхній текст, необовʼязково відфільтрований за типом сутності |
ctx | Повертає реакції з оновлення в зручному для роботи вигляді |
Іншими словами, ви також можете зробити це:
bot.on("message", async (ctx) => {
// Отримуємо текст повідомлення.
const text = ctx.msg.text;
});
bot.on("edited_message", async (ctx) => {
// Отримуємо новий, відредагований, текст повідомлення.
const editedText = ctx.msg.text;
});
bot.on("message:entities", async (ctx) => {
// Отримуємо всі сутності.
const entities = ctx.entities();
// Отримуємо текст першої сутності.
entities[0].text;
// Отримуємо сутності електронної пошти.
const emails = ctx.entities("email");
// Отримуємо сутності телефону та електронної пошти.
const phonesAndEmails = ctx.entities(["email", "phone"]);
});
bot.on("message_reaction", (ctx) => {
const { emojiAdded } = ctx.reactions();
if (emojiAdded.includes("🎉")) {
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
25
26
27
28
29
30
Перегляньте розділ про реакції, якщо вони вас зацікавили.
Отже, якщо ви хочете, ви можете забути про ctx
і ctx
, і ctx
, і т.д., і т.п., та завжди використовувати замість них ctx
.
Дослідження через has перевірку
Обʼєкт контексту має декілька методів, які дозволяють перевіряти наявність певних даних у вмісті. Наприклад, ви можете викликати ctx
, щоб перевірити, чи містить обʼєкт контексту команду /start
. Ось чому методи мають загальну назву has перевірка.
Коли використовувати has перевірки
Це точно та ж логіка, яку використовує bot
. Зауважте, що зазвичай слід використовувати фільтрування запитів і подібні методи. Використання has перевірок найкраще підходить для використання в плагіні розмов.
has перевірки правильно звужують тип контексту. Це означає, що перевірка наявності в контексті даних запиту зворотного виклику повідомить TypeScript, що в контексті присутнє поле ctx
.
if (ctx.hasCallbackQuery(/query-data-\d+/)) {
// Тепер відомо, що поле `ctx.callbackQuery.data` присутнє
const data: string = ctx.callbackQuery.data;
}
2
3
4
Те саме стосується всіх інших перевірок. Перегляньте довідку API про обʼєкт контексту, щоб побачити список усіх has перевірок. Також перегляньте статичну властивість Context
у довідці API, яка дозволяє створювати ефективні предикатні функції для дослідження багатьох обʼєктів контексту.
Доступні дії
Якщо ви хочете відповісти на повідомлення від користувача, ви можете написати наступне:
bot.on("message", async (ctx) => {
// Отримуємо ідентифікатор чату.
const chatId = ctx.msg.chat.id;
// Текст для відповіді.
const text = "Я отримав твоє повідомлення!";
// Надсилаємо відповідь.
await bot.api.sendMessage(chatId, text);
});
2
3
4
5
6
7
8
Ви можете помітити дві речі, які не є оптимальними в цьому прикладі:
- Ми повинні мати доступ до обʼєкта
bot
. Це означає, що нам доведеться передавати обʼєктbot
по всій кодовій базі, щоб відповідати, що дратує, коли у вас більше одного вихідного файлу і ви визначаєте обробник десь в іншому місці. - Ми повинні вилучити ідентифікатор чату з контексту та знову явно передати його в
send
. Це теж дратує, тому що ви, швидше за все, завжди хочете відповідати тому самому користувачеві, який надіслав повідомлення. Уявіть, як часто ви вводите те саме знову і знову!Message
Щодо першого пункту обʼєкт контексту просто надає вам доступ до того самого обʼєкта API, який ви знайдете в bot
, він називається ctx
. Тепер ви можете замість цього написати ctx
, і вам більше не доведеться передавати обʼєкт bot
. Зручно.
Однак справжня сила полягає у виправленні другого пункту. Обʼєкт контексту дозволяє просто надіслати відповідь так:
bot.on("message", async (ctx) => {
await ctx.reply("Я отримав твоє повідомлення!");
});
// Або ще коротше:
bot.on("message", (ctx) => ctx.reply("Впіймав!"));
2
3
4
5
6
Лаконічно! 🎉
Під капотом контекст вже знає свій ідентифікатор чату: ctx
, тому він дає вам метод reply
, щоб надіслати повідомлення у той самий чат. Всередині себе reply
знову викликає send
із попередньо заповненим для вас ідентифікатором чату.
Отже, усі методи обʼєкта контексту приймають обʼєкти параметрів типу Other
, як пояснювалося раніше. Це можна використати для передачі подальших налаштувань кожному виклику API.
Функція відповіді Telegram
Незважаючи на те, що в grammY (та багатьох інших фреймворках) метод називається ctx
, він не використовує функцію відповіді Telegram де є посилання на попереднє повідомлення.
Якщо ви подивитеся у довіднику Bot API, що може робити send
, ви побачите кілька параметрів: parse
, link
і reply
. Останнє можна використати, щоб зробити повідомлення відповіддю:
await ctx.reply("^ Це повідомлення!", {
reply_parameters: { message_id: ctx.msg.message_id },
});
2
3
Той самий обʼєкт параметрів можна передати в bot
і ctx
. Використовуйте автодоповнення, щоб побачити доступні параметри прямо в редакторі коду.
Звичайно, кожен інший метод у ctx
має короткий метод із правильними попередньо заповненими значеннями: ctx
, щоб відповісти фотографією, або ctx
, щоб отримати посилання для запрошення до відповідного чату. Якщо ви хочете отримати загальне уявлення про те, які короткі методи існують, тоді автодоповнення стане вашим другом разом із довідкою API grammY.
Можливо, вам не завжди потрібно реагувати в тому ж чаті. У цьому випадку ви можете просто повернутися до використання методів ctx
і вказати всі параметри під час їх виклику. Наприклад, якщо ви отримали повідомлення від Аліси та хочете відреагувати, надіславши повідомлення Бобу, ви не можете використовувати ctx
, оскільки він завжди надсилатиме повідомлення до чату з Алісою. Натомість викличте ctx
і вкажіть ідентифікатор чату Боба.
Як створюються обʼєкти контексту
Щоразу, коли ваш бот отримує нове повідомлення від Telegram, воно загортається в обʼєкт оновлення. Насправді обʼєкти оновлення можуть містити не лише нові повідомлення, а й усі інші речі, як-от редагування повідомлень, відповіді на опитування та багато іншого.
Новий обʼєкт контексту створюється виключно один раз для кожного вхідного оновлення. Контексти для різних оновлень абсолютно неповʼязані обʼєкти, вони лише посилаються на ту саму інформацію про бота через ctx
.
Той самий обʼєкт контексту для одного оновлення буде спільно використовуватися всіма встановленими на боті проміжними обробниками (документація).
Налаштування обʼєкта контексту
Якщо ви вперше знайомитеся з обʼєктами контексту, вам не обовʼязково приділяти увагу решті цієї сторінки.
За бажанням ви можете встановити власні властивості в обʼєкті контексту.
Через проміжний обробник (рекомендовано)
Налаштування можна легко виконати в проміжному обробнику.
Проміжний… що?
Цей розділ потребує розуміння проміжних обробників, тому, якщо ви ще не переходили до розділу про проміжні обробники, ось дуже короткий підсумок.
Все, що вам справді потрібно знати, це те, що кілька обробників можуть обробляти один обʼєкт контексту. Існують спеціальні обробники, які можуть змінювати ctx
перед запуском будь-яких інших обробників, і зміни першого обробника будуть видимі для всіх наступних обробників.
Ідея полягає в тому, щоб встановити проміжний обробник перед реєстрацією інших обробників. Потім ви можете встановити потрібні властивості в цих обробниках. Якщо ви застосовуєте ctx
всередині такого обробника, то властивість ctx
також буде доступна в наступних обробниках.
Для ілюстрації припустімо, що ви хочете встановити властивість під назвою ctx
для обʼєкта контексту. У цьому прикладі ми будемо використовувати його для збереження деяких налаштувань проєкту, щоб усі обробники мали до них доступ. Налаштування полегшить виявлення того, ким використовується бот: його розробником чи звичайним користувачем.
Відразу після створення бота виконайте наступне:
const BOT_DEVELOPER = 123456; // ідентифікатор чату розробника бота
bot.use(async (ctx, next) => {
// Змінюємо обʼєкт контексту, встановивши налаштування.
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
// Запускаємо наступні обробники.
await next();
});
2
3
4
5
6
7
8
9
10
11
Після цього ви можете використовувати ctx
в інших обробниках.
bot.command("start", async (ctx) => {
// Працюємо тут зі зміненим контекстом!
if (ctx.config.isDeveloper) await ctx.reply("Привіт, ма!! <3");
else await ctx.reply("Ласкаво просимо, людино!");
});
2
3
4
5
Однак ви помітите, що TypeScript не знає, що ctx
доступний, навіть якщо ми призначаємо властивість правильно. Таким чином, хоча код працюватиме під час виконання, він не скомпілюється. Щоб виправити це, нам потрібно налаштувати тип контексту та додати властивість.
interface BotConfig {
botDeveloper: number;
isDeveloper: boolean;
}
type MyContext = Context & {
config: BotConfig;
};
2
3
4
5
6
7
8
Новий тип My
тепер точно описує обʼєкти контексту, які насправді обробляє наш бот.
Вам необхідно забезпечувати синхронізацію типів із властивостями, які ви ініціалізуєте.
Ми можемо використовувати новий тип, передавши його в конструктор Bot
.
const bot = new Bot<MyContext>("");
У підсумку налаштування виглядатимуть наступним чином:
const BOT_DEVELOPER = 123456; // ідентифікатор чату розробника бота
// Визначаємо власний тип контексту.
interface BotConfig {
botDeveloper: number;
isDeveloper: boolean;
}
type MyContext = Context & {
config: BotConfig;
};
const bot = new Bot<MyContext>("");
// Встановлюємо настроювані властивості обʼєктів контексту.
bot.use(async (ctx, next) => {
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
await next();
});
// Визначаємо обробника для налаштованих обʼєктів контексту.
bot.command("start", async (ctx) => {
if (ctx.config.isDeveloper) await ctx.reply("Привіт, ма!");
else 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
25
26
27
const BOT_DEVELOPER = 123456; // ідентифікатор чату розробника бота
const bot = new Bot("");
// Встановлюємо настроювані властивості обʼєктів контексту.
bot.use(async (ctx, next) => {
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
await next();
});
// Визначаємо обробника для налаштованих обʼєктів контексту.
bot.command("start", async (ctx) => {
if (ctx.config.isDeveloper) await ctx.reply("Привіт, ма!");
else await ctx.reply("Ласкаво просимо");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Звичайно, власний тип контексту також можна передати іншим речам, які працюють з проміжними обробниками, наприклад в Composer.
const composer = new Composer<MyContext>();
Деякі плагіни також вимагатимуть передачі власного типу контексту, як-от плагін маршрутизатор або плагін інтерактивних меню. Перегляньте їхню документацію, щоб дізнатися, як вони використовують власний тип контексту. Ці типи називаються розширювачі, як описано тут.
Через успадкування
Окрім встановлення власних властивостей обʼєкта контексту, ви можете створити підклас класу Context
.
class MyContext extends Context {
// ...
}
2
3
Однак ми рекомендуємо вам налаштовувати обʼєкт контексту через проміжний обробник, оскільки він гнучкіший і краще працює, якщо ви хочете встановити плагіни.
Нумо дивитися, як використовувати власні класи для обʼєктів контексту.
Під час створення свого бота ви можете передати власний конструктор контексту, який використовуватиметься для створення екземплярів обʼєктів контексту. Зауважте, що ваш клас має розширювати Context
.
import { Bot, Context } from "grammy";
import type { Update, UserFromGetMe } from "grammy/types";
// Визначаємо власний клас контексту.
class MyContext extends Context {
// Встановлюємо деякі власні властивості.
public readonly customProp: number;
constructor(update: Update, api: Api, me: UserFromGetMe) {
super(update, api, me);
this.customProp = me.username.length * 42;
}
}
// Передаємо конструктор спеціального класу контексту як параметр.
const bot = new Bot("", {
ContextConstructor: MyContext,
});
bot.on("message", async (ctx) => {
// `ctx` тепер має тип `MyContext`.
const prop = ctx.customProp;
});
bot.start();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const { Bot, Context } = require("grammy");
// Визначаємо власний клас контексту.
class MyContext extends Context {
// Встановлюємо деякі власні властивості.
public readonly customProp;
constructor(update, api, me) {
super(update, api, me);
this.customProp = me.username.length * 42;
}
}
// Передаємо конструктор спеціального класу контексту як параметр.
const bot = new Bot("", {
ContextConstructor: MyContext,
});
bot.on("message", async (ctx) => {
// `ctx` тепер має тип `MyContext`.
const prop = ctx.customProp;
});
bot.start();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Bot, Context } from "https://deno.land/x/grammy@v1.30.0/mod.ts";
import type {
Update,
UserFromGetMe,
} from "https://deno.land/x/grammy@v1.30.0/types.ts";
// Визначаємо власний клас контексту.
class MyContext extends Context {
// Встановлюємо деякі власні властивості.
public readonly customProp: number;
constructor(update: Update, api: Api, me: UserFromGetMe) {
super(update, api, me);
this.customProp = me.username.length * 42;
}
}
// Передаємо конструктор спеціального класу контексту як параметр.
const bot = new Bot("", {
ContextConstructor: MyContext,
});
bot.on("message", async (ctx) => {
// `ctx` тепер має тип `MyContext`.
const prop = ctx.customProp;
});
bot.start();
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
Зверніть увагу на те, як власний тип контексту буде виведено автоматично, коли ви використовуєте підклас. Вам більше не потрібно писати Bot<My
, оскільки ви вже вказали свій конструктор підкласу в обʼєкті параметрів new Bot()
.
Однак це дуже ускладнює, якщо не унеможливлює, встановлення плагінів, оскільки для них часто потрібно встановлювати розширювач для контексту.
Розширювач для контексту
Розширювач це спосіб повідомити TypeScript про нові властивості вашого обʼєкта контексту. Ці нові властивості можна постачати в плагінах або інших модулях, а потім встановити у вашому боті.
Розширювач для контексту також може перетворювати типи існуючих властивостей за допомогою автоматичних процедур, які визначаються плагінами.
Додавальний розширювач
Як було зазначено вище, існує два різних типи розширювачів. Основний називається додавальний розширювач, і щоразу, коли ми говоримо про розширювач для контексту, ми маємо на увазі саме цей тип. Давайте подивимося, як це працює.
Наприклад, якщо у вас є дані сесії, ви повинні зареєструвати ctx
для типу контексту. Інакше:
- ви не зможете встановити вбудований плагін сесії,
- у вас не буде доступу до
ctx
у ваших обробниках..session
Незважаючи на те, що ми використовуємо сесії як приклад, подібні речі застосовуються до багатьох інших випадків. Насправді більшість плагінів нададуть вам розширювач, який вам потрібно використовувати.
Розширювач для контексту це просто невеликий новий тип, який визначає властивості, які слід додати до типу контексту. Давайте розглянемо приклад розширювача.
interface SessionFlavor<S> {
session: S;
}
2
3
Тип Session
(довідка API) простий: він визначає лише властивість session
. Він приймає параметр типу, який визначатиме фактичну структуру даних сесії.
Чим це корисно? Ось як ви можете розширити свій контекст за допомогою даних сесії:
import { Context, SessionFlavor } from "grammy";
// Оголошуємо `ctx.session` типу `string`.
type MyContext = Context & SessionFlavor<string>;
2
3
4
Тепер ви можете використовувати плагін сесії та мати доступ до ctx
:
bot.on("message", async (ctx) => {
// Тепер `str` має тип `string`.
const str = ctx.session;
});
2
3
4
Перетворювальний розширювач
Інший тип розширювачів є більш потужним. Замість встановлення за допомогою оператора &
, його потрібно встановити так:
import { Context } from "grammy";
import { SomeFlavorA } from "my-plugin";
type MyContext = SomeFlavorA<Context>;
2
3
4
Все інше працює так само.
Кожен офіційний плагін зазначає у своїй документації, чи має він використовуватися через додавальний або перетворювальний розширювач.
Поєднання різних розширювачів
Якщо у вас є різні додавальні розширювачі, ви можете просто встановити їх наступним чином:
type MyContext = Context & FlavorA & FlavorB & FlavorC;
Порядок встановлення додавальних розширювачів не має значення, ви можете комбінувати їх як вам заманеться.
Кілька перетворювальних розширювачів також можна комбінувати:
type MyContext = FlavorX<FlavorY<FlavorZ<Context>>>;
Тут порядок може мати значення, оскільки спочатку Context
буде перетворено FlavorZ
, потім FlavorY
, і результат цього буде знову перетворено вже FlavorX
.
Ви навіть можете змішувати додавальні та перетворювальні розширювачі:
type MyContext = FlavorX<
FlavorY<
FlavorZ<
Context & FlavorA & FlavorB & FlavorC
>
>
>;
2
3
4
5
6
7
Обовʼязково дотримуйтеся цього шаблону під час встановлення кількох плагінів. Існує ряд помилок типів, які виникають через неправильне поєднання декількох розширювачів.