Modern Telegram Bot Framework for Node.js
bot.command
did not match bot usernames if the registered username was not lowercased (#1809)Normally the most exciting features of a new release would be support for the latest Bot API. But in this update, it's session! This has been in the works for many months, and we're happy to bring it to you this release!
Some of you may know that builtin session has been deprecated for quite a while. This was motivated by the fact that session is prone to race-conditions (#1372). This left the community in a grey area where they continued to use session despite the deprecation, since no clear alternative was provided. Added to this was the fact that there were no official database-backed sessions, and all unofficial async session middleware were affected by #1372.
This release finally addresses both of these long-running issues.
#1713 provides a reference-counted implementation resistant to race conditions. Session is now no longer deprecated, and can be used safely!
Note: You should read more about how to safely use session in the docs repo.
We're also happy to announce a revamped @telegraf/session
βthis provides official store implementations for database-backed sessions via Redis, MongoDB, MySQL, MariaDB, PostgreSQL, and SQLite. Just install the drivers necessary for your database, and off you go! Since this package now only provides a store
implementation, it's usable with builtin session, and effectively makes all implementations have the same safety as the core package. Check it out!
Additionally, session now accepts a defaultSession
parameter. You no longer need a hacky middleware to do ctx.session ??= { count }
.
// π€’ Old way
bot.use(session());
bot.use((ctx, next) => {
ctx.session ??= { count: 0 };
return next();
});
// π New way β
bot.use(session({ defaultSession: () => ({ count: 0 }) }));
Markup.button.userRequest
Markup.button.botRequest
Markup.button.groupRequest
Markup.button.channelRequest
Telegram::setChatPermissions
and Context::setChatPermissions
accept a new parameter for { use_independent_chat_permissions?: boolean }
as documented in the API.Telegram
and Context
:editGeneralForumTopic
closeGeneralForumTopic
reopenGeneralForumTopic
hideGeneralForumTopic
unhideGeneralForumTopic
Context::sendChatAction
will automatically infer message_thread_id
for topic messages.'this' Context of type 'NarrowedContext' is not assignable to method's 'this' of type 'Context<Update>'
.Another long-standing problem was the lack of support for RegExp or case-insensitive command matching. This is here now:
bot.command("hello", ctx => ctx.reply("You sent a case-sensitive /hello"));
bot.command(/^hello$/i, ctx => ctx.reply("You sent a case-insensitive /hELLo"));
join
fmt helper to combine dynamic arrays into a single FmtString.import { fmt, bold, join } from "telegraf/format";
// elsewhere
bot.command("/fruits", async ctx => {
const array = ["Oranges", "Apples", "Grapes"];
const fruitList = join(array.map(fruit => bold(fruit)), "\n");
const msg = fmt`Fruits to buy:\n${fruitList}`;
await ctx.sendMessage(msg);
});
bold(italic("telegraf"))
will now work as expected.ctx.sendChatAction
is used to send a "typing", or "uploading photo" status while your bot is working on something. But this is cleared after 5 seconds. If you have a longer process, you may want to keep calling sendChatAction in a loop. This new feature adds an API to help with this:
bot.command('/video', async ctx => {
// starts sending upload_video action
await ctx.persistentChatAction("upload_video", async () => {
const data = await getLargeVideoSomehow();
await ctx.sendVideo(Input.fromBuffer(data));
}); // all done, stops sending upload_video
});
Thanks to @orimiles5 for raising this pull request (#1804).
Follow Telegraf_JS to receive these updates in Telegram. If you have feedback about this update, please share with us on @TelegrafJSChat!
sendMediaGroup
to accept StreamFile
.message_thread_id
if is_topic_message
is true.
Telegram sends message_thread_id
for reply messages, even if the group doesn't have topics. This caused the bot to throw when ctx.reply
was used against reply messages in non-forums."telegraf/filters"
. Top-level filters.{js|d.ts}
were missing in package.json "files" array.Telegram
class:
createForumTopic
editForumTopic
closeForumTopic
reopenForumTopic
deleteForumTopic
unpinAllForumTopicMessages
getForumTopicIconStickers
Context
; add message_thread_id
implicitly to Context::send*
methods.We've added a new powerful feature called filters! Here's how to use them.
// import our filters
import { message, editedMessage, channelPost, editedChannelPost, callbackQuery } from "telegraf/filters";
// you can also use require, like this:
// const { message, editedMessage, channelPost, editedChannelPost, callbackQuery } = require("telegraf/filters");
const bot = new Telegraf(token);
bot.on(message("text"), ctx => {
// this is a text message update
// ctx.message.text
});
bot.on(channelPost("video"), ctx => {
// this is a video channel post update
// ctx.channelPost.video
});
bot.on(callbackQuery("game_short_name"), ctx => {
// this is a callback_query game update
// ctx.callbackQuery.game_short_name
});
This unlocks the ability to filter for very specific update types previously not possible! This is only an initial release, and filters will become even more powerful in future updates.
All filters are also usable from a new method, ctx.has
. This is very useful if you want to filter within a handler. For example:
// handles all updates
bot.use(ctx => {
if (ctx.has(message("text"))) {
// handles only text messages
// ctx.message.text;
} else {
// handles all other updates
}
});
Like bot.on
, ctx.has
also supports an array of update types and filters, even mixed:
// match a message update or a callbackQuery with data present
bot.on(["message", callbackQuery("data")], handler);
if (ctx.has(["message", callbackQuery("data")])) {
// ctx.update is a message update or a callbackQuery with data present
};
As of this release, filtering by message type using bot.on()
(for example: "text", "photo", etc.) is deprecated. Don't panic, though! Your existing bots will continue to work, but whenever you can, you must update your message type filters to use the above filters before v5. This is fairly easy to do, like this:
- bot.on("text", handler);
+ bot.on(message("text"), handler);
The deprecated message type behaviour will be removed in v5.
You might be happy, or fairly upset about this development. But it was important we made this decision. For a long time, Telegraf has supported filtering by both update type and message type.
This meant you could use bot.on("message")
, or bot.on("text")
(text here is a message type, and not an update type, so this was really making sure that update.message.text
existed). However, when polls were introduced, this caused a conflict. bot.on("poll")
would match both update.poll
(update about stopped polls sent by the bot) and update.message.poll
(a message that is a native poll). At type-level, both objects will show as available, which was wrong.
Besides, this type of filters really limited how far we could go with Telegraf. That's why we introduced filters, which are way more powerful and flexible!
A few updates back, in 4.9.0, we added ctx.send*
methods to replace ctx.reply*
methods. This is because in v5 the behaviour of ctx.reply*
will be to actually reply to the current message, instead of only sending a message.
To start using this behaviour right away, we had also introduced a middleware. We recommend you start using this, so that you're prepared for v5, which is brewing very soon!
import { useNewReplies } from "telegraf/future";
// this will enable ctx.reply throughout the bot to automatically reply to current message
// use ctx.sendMessage and friends to send a message without replying
bot.use(useNewReplies());
bot.launch
is now catchable (#1657)
Polling errors were previously uncatchable in Telegraf. They are now. Simply attach a catch
to bot.launch
:
ot.launch().catch(e => {
/ polling has errored
);
/ You an also use await and try/catch if you're using ESM
Three things to remember:
bot.launch
in webhook mode, it will immediately resolve after setWebhook
completes.bot.launch
in polling mode will not resolve immediately. Instead, it will resolve after bot.stop()
, or reject when there's a polling error.We previously did not want fatal errors to be caught, since it gives the impression that it's a handleable error. However, being able to catch this is useful when you launch multiple bots in the same process, and one of them failing doesn't need to bring down the process.
Use this feature with care. :)
Format helpers ("telegraf/format"
) now use template string substitution instead of naively using +=
. (Discussion)
Follow Telegraf_JS to receive these updates in Telegram. If you have feedback about this update, please share with us on @TelegrafJSChat!
Brand new formatting helpers! No more awkward escaping.
import { fmt, bold, italics, mention } from "telegraf/format";
ctx.reply(fmt`
Ground control to ${mention("Major Tom", 10000000)}
${bold`Lock your Soyuz hatch`} and ${italic`put your helmet on`}
β ${link("David Bowie", "https://en.wikipedia.org/wiki/David_Bowie")}
`);
This also just works with captions!
ctx.replyWithPhoto(
file.id,
{ caption: fmt`${bold`File name:`} ${file.name}` },
);
Added Input helpers to create the InputFile object.
import { Telegraf, Input } from "telegraf";
const bot = new Telegraf(token);
bot.telegram.sendVideo(chatId, Input.fromLocalFile("../assets/cats.mp4"));
bot.telegram.sendDocument(chatId, Input.fromBuffer(buf));
bot.command("cat", ctx => {
ctx.sendPhoto(Input.fromURL("https://funny-cats.example/cats.jpg"))
});
This helps clear the confusion many users have about InputFile.
Deprecated ctx.replyWithMarkdown
; prefer MarkdownV2 as Telegram recommends.
Deprecated ctx.replyWithChatAction
; use identical method ctx.sendChatAction
instead.
bot.launch()
's webhook options now accepts certificate
for self-signed certs.
Fix bot crashes if updateHandler
throws (#1709)
ctx.replyWithVideo
(#1687)You can now follow Telegraf releases on Telegram
Telegraf::createWebhook
which calls setWebhook
, and returns Express-style middleware. [Example]
Extra*
types) now found as: import type { Convenience } from "telegraf/types"
(#1659)import { useNewReplies } from telegraf/future
that changes the behaviour of Context::reply*
methods to actually reply to the context message. This will be the default in v5.Context::sendMessage
and Context:sendWith*
methods to replace the old Context::reply
and Context::replyWith*
methods.--method
and --data
to call API methods from the command-line.