Context
The Context object is an important part of Opengram.
Whenever you register a middleware / handler on your bot object, this listener will receive a context object.
bot.on("message", (ctx) => {
// `ctx` is the `Context` object.
});
The context object provides a raw update object with additional methods for sending response and shortcuts for easy access to data from the update.
Context objects are commonly called ctx
/ context
Available Information
When a user sends a message to your bot, you can access it via ctx.message
. As an example, to get the message text,
you can do this:
bot.on("message", (ctx) => {
// `text` will be a `string` when processing text messages.
// It will be `undefined` if the received message does not have any message text,
// e.g. photos, stickers, and other messages.
const text = ctx.message.text;
console.log(text)
});
Similarly, you can access other properties of the message object, e.g. ctx.message.chat
for information
about the chat where the message was sent.
Check out the part about Messages in the Telegram Bot API Reference
to see which data is available.
If you register your listener for other types, ctx will also give you information about those. Example:
bot.on("edited_message", (ctx) => {
// Get the new, edited, text of the message.
const editedText = ctx.editedMessage.text;
});
Moreover, you can get access to the raw Update
object
(Telegram Bot API Reference) that Telegram sends to your bot.
This update object (ctx.update
) contains all the data that sources ctx.message
and the like.
The context object always contains information about your bot, accessible via ctx.me
.
Shortcuts
There are a number of shortcuts installed on the context object.
Shortcut | Description |
---|---|
ctx.callbackQuery | Gets the callback query object. Shortcut to ctx.update.callback_query |
ctx.channelPost | Gets the channel post object. Shortcut to ctx.update.channel_post |
ctx.chat | Gets the chat object of the message, callback query, or other things |
ctx.chatJoinRequest | Gets the chat join request object. Shortcut to ctx.update.chat_join_request |
ctx.chatMember | Gets the chat member object. Shortcut to ctx.update.chat_member |
ctx.chosenInlineResult | Gets the chosen inline result object. Shortcut to ctx.update.chosen_inline_result |
ctx.passportData | Gets the edited channel post object. Shortcut to ctx.update.message.passportData |
ctx.editedMessage | Gets the edited message object. Shortcut to ctx.update.edited_message |
ctx.from | Gets the author of the message, callback query, or other things |
ctx.inlineMessageId | Gets the inline message identifier for callback queries or chosen inline results |
ctx.inlineQuery | Gets the inline query object. Shortcut to ctx.update.inline_query |
ctx.message | Gets the message object. Shortcut to ctx.update.message |
ctx.myChatMember | Gets the my chat member object. Shortcut to ctx.update.my_chat_member |
ctx.poll | Gets the poll object. Shortcut to ctx.update.poll |
ctx.pollAnswer | Gets the poll answer object. Shortcut to ctx.update.poll_answer |
ctx.preCheckoutQuery | Gets the pre checkout query object. Shortcut to ctx.update.pre_checkout_query |
ctx.senderChat | Gets the sender chat object of the message, callback query, or other things |
ctx.anyEntities | Gets the entities for text or photo caption and other things |
ctx.anyMessage | Gets the message object from cb query, channel post and things thinks |
In other words, you can also do this:
bot.on("message", (ctx) => {
// Get the text of the message.
const text = ctx.message.text;
});
bot.on("edited_message", (ctx) => {
// Get the new, edited, text of the message.
const editedText = ctx.editedMessage.text;
});
Available Actions
If you want to respond to a message from a user, you could write this:
bot.on("message", async (ctx) => {
// Get the chat identifier.
const chatId = ctx.chat.id;
// The text to reply with
const text = "I got your message!";
// Send the reply.
await bot.telegram.sendMessage(chatId, text);
});
You can notice two things that are not optimal about this:
- We must have access to the
bot
object. This means that we have to pass thebot
object all around our code base in order to respond, which is annoying when you have more than one source file, and you define your listener somewhere else. - We have to take out the chat identifier of the context, and explicitly pass it to
sendMessage
again. This is annoying, too, because you most likely always want to respond to the same user that sent a message. Imagine how often you would type the same thing over and over again!
Regarding point 1, the context object simply provides you access to the same API object that you find on bot.telegram
,
it is called ctx.telegram
. You could now write ctx.telegram.sendMessage
instead, and you no longer have to pass around
your bot object.
However, the real strength is to fix point 2. The context object lets you simply send a reply like this:
bot.on("message", async (ctx) => {
await ctx.reply("I got your message!");
});
// Or, even shorter:
bot.on("message", (ctx) => ctx.reply("Gotcha!"));
Under the hood, the context already knows its chat identifier (namely ctx.message.chat.id
), so it gives you the
reply method to just send a message back to the same chat. Internally, reply again calls sendMessage
with the chat identifier pre-filled for you.
Consequently, all methods on the context object take options objects of type Other as explained earlier. This can be used to pass further configuration to every API call.
Naturally, every other method on ctx.telegram
has a shortcut with the correct pre-filled values, such as
ctx.replyWithPhoto
to reply with a photo, or ctx.exportChatInviteLink
to get an invite link for the respective chat.
Note that you may not want to react in the same chat always.
In this case, you can just fall back to using ctx.telegram
methods, and specify all options when calling them.
For example, if you receive a message from Alice and want to react by sending a message to Bob,
then you cannot use ctx.reply
because it will always send messages to the chat with Alice.
Instead, call ctx.telegram.sendMessage
and specify the chat identifier of Bob.
How Context Objects Are Created
Whenever your bot receives a new message from Telegram, it is wrapped in an update object. In fact, update objects can not only contain new messages, but also all other sorts of things, such as edits to messages, poll answers, and much more.
A fresh context object is created exactly once for every incoming update.
Contexts for different updates are completely unrelated objects, they only reference the same bot information via ctx.me
.
The same context object for one update will be shared by all installed middleware on the bot.
Customizing the Context Object
You can install your own properties on the context object if you want.
Via Middleware
The customizations can be easily done in middleware.
The idea is to install middleware before you register other listeners. You can then set the properties you want inside these handlers.
For illustration purposes, let’s say you want to set a property called ctx.config
on the context object.
In this example, we will use it to store some configuration about the project so that all handlers have access to it.
The configuration will make it easier to detect if the bot is used by its developer or by regular users.
Right after creating your bot, do this:
const BOT_DEVELOPER = 123456; // bot developer chat identifier
bot.use(async (ctx, next) => {
// Modify context object here by setting the config.
ctx.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
// Run remaining handlers.
await next();
});
After that, you can use ctx.config
in the remaining handlers.
However, this example is not entirely safe, since you can overwrite some service properties of context,
it is better to use a special reserved object in the context - ctx.state
. Example:
const BOT_DEVELOPER = 123456; // bot developer chat identifier
bot.use(async (ctx, next) => {
// Modify context object here by setting the config.
ctx.state.config = {
botDeveloper: BOT_DEVELOPER,
isDeveloper: ctx.from?.id === BOT_DEVELOPER,
};
// Run remaining handlers.
await next();
});
Via Inheritance
In addition to setting custom properties on the context object, you can subclass the Context
class.
class MyContext extends Context {
// etc
}
We will now see how to use custom classes for context objects.
When constructing your bot, you can pass a custom context constructor that will be used to instantiate the context objects.
Your class must extend Context
.
const { Opengram, Context } = require("opengram");
// Define a custom context class.
class CustomContext extends Context {
constructor(update, telegram, botInfo) {
super(update, telegram, botInfo)
}
reply(...args) {
console.log('reply called with args: %j', args)
return super.reply(...args)
}
}
// Pass the constructor of the custom context class as an option.
const bot = new Telegraf(process.env.BOT_TOKEN, {
contextType: CustomContext
})
bot.start((ctx) => ctx.reply('Hello'))
bot.help((ctx) => ctx.reply('Help message'))
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))
Via bot.context
bot.context
is an object that assigns a context instance each time a context is created,
you can add some props / functions to this object and get from context later
For example, you can use it for injecting services, service locator, database models, database connection, anything that can be assigned to the context during bot initialization
const { Opengram } = require("opengram");
// Some abstract database
const database = new Map([
['123456', 'Info for user 123456'],
['1234567', 'Info for user 1234567']
])
const bot = new Telegraf(process.env.BOT_TOKEN)
// Adding database object to context
bot.context.database = database
// Send user info when user call /me command
bot.command('me', (ctx) => {
const userInfo = ctx.database.get(ctx.from.id)
return ctx.reply(userInfo) // Send "Info for user ..."
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))