Telegraf VS Node-Telegram-Bot-API
Tags: telegraf, telegram, node, bot
Published: 2022-02-26 10:36:29

Today the most popular ways to build telegram chat bots in node js are [Telegraf](https://telegraf.js.org/) and [Node-Telegram-Bot-Api](https://www.npmjs.com/package/node-telegram-bot-api). The second is more popular by weekly downloads(250.000), but in this post I don't want to equalize them by stats. I want to talk about my experience with them, in which cases I used them and show you really nice guide, especially for Telegraf, cause they haven't readable docs for newbies or people who don't use typescript.  

## Node Telegram Bot API
So, let's start with this package. It is really easy, but due to it we can't use it in really big projects, cause it will be hard to understand structure of bot without comments or million javascript files.

To download type:

```
npm i node-telegram-bot-api
```
After this, as said in documentation, you need to initialize it with your telegram token, which you received from [Bot Father](https://telegram.me/BotFather)

```javascript
const TelegramBot = require('node-telegram-bot-api');

const token = 'YOUR_TELEGRAM_BOT_TOKEN';

// Create a bot that uses 'polling' to fetch new updates
const bot = new TelegramBot(token, {polling: true});
```
And there are two main methods in this package - `bot.sendMessage(chatId, message, options)` and bot.on("one of params from <a href="https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#events" rel="noopener noreferrer" target="_blank">api</a> like message or text", function which contains message param and returns response)

```javascript
bot.on('message', (msg) => {
  const chatId = msg.chat.id;

  // send a message to the chat acknowledging receipt of their message
  bot.sendMessage(chatId, 'Received your message');
});
```
Here is [message object](https://core.telegram.org/bots/api#message) with all fields. So, if we need to react on `/start` command we need to make something like this:

```javascript
bot.on("message", async function (msg) {
  const text = msg.text;
  const chatId = msg.chat.id;
  if (text === "/start") {
    bot.sendMessage(chatId, `Hello ${msg.from.username}! Choose your language...`, 
   reply_markup: {
       one_time_keyboard: true,
       keyboard: [["ua"], ["en"]],
   },);
  }
});
```
We checked if our text is equal to our command and responded with message. It is all main logic from this package, more sendMessage methods or params you can see [here](https://github.com/yagop/node-telegram-bot-api/blob/release/doc/api.md). Also we used `reply_markup` option in which we can use our keyboard, which will be sent with our message. And then we can check if text equal to "en" we will change our language to english and response with message.   
In addition, if you have a question like can you send message without getting message from user - you can do it. All that you need is to save your user to database like [MongoDB](https://www.mongodb.com/cloud/atlas/lp/try2?utm_source=google&utm_campaign=gs_emea_ukraine_search_core_brand_atlas_desktop&utm_term=mongo%20db&utm_medium=cpc_paid_search&utm_ad=e&utm_ad_campaign_id=12212624575&adgroup=115749710823&gclid=CjwKCAiA6seQBhAfEiwAvPqu15eJzfcE2IJn2R_He0p-Sc3r8dHFel0gwIAno2x0Y1QzAd1ERt-sNBoCNGEQAvD_BwE) with chatId field and use something like [express](https://expressjs.com/) to make CRUD methods to send your message. Here is my example of this 'bot mailer':

```javascript
app.get("/", async (req, res) => {
  await TelegramUser.find()
    .cursor()
    .eachAsync(async (user) => {
      bot.sendMessage(user.chatId, "<b>Hi!</b>", {
        parse_mode: "HTML",
        disable_web_page_preview: false,
      });
    });
});
```
So here we sent our message "Hi" in async forEach mongodb method when we got request to our app. `TelegramUser` is a mongodb model, where I store user chatId. Also you can see two the most useful options from this package - parse_mode, which parses our message to HTML(but can't parse all tags, like img, video, cause there are methods like sendPhoto, sendVideo) and disable_web_page_preview, which you can use when in message you send link and you don't want to see preview of it. 
I hope you got it, let's move to telegraf.

## Telegraf
Let's talk about telegram bot library which I prefer to use in big projects.
To download:
```
npm i telegraf
```
In this library we can see similar methods with node-telegram-bot-api:

```javascript
const { Telegraf } = require('telegraf')

const bot = new Telegraf(process.env.BOT_TOKEN)
bot.start((ctx) => ctx.reply('Welcome'))
bot.help((ctx) => ctx.reply('Send me a sticker'))
bot.on('sticker', (ctx) => ctx.reply('👍'))
bot.hears('hi', (ctx) => ctx.reply('Hey there'))
bot.launch()
```
To initialize we use `new Telegraf(token)`. To react on /start command we type `bot.start` which is more readable than in previous package. Here instead of message object we have ctx - [context object](https://telegraf.js.org/classes/Context.html), which almost doesn't have big difference from first one but has many another methods. 
`ctx.reply` is function with response message, here if we want to parse HTML or reply with audio or photo etc. we can use methods like `ctx.replyWithHTML` or `ctx.replyWithPhoto`, all methods you can see in previous link.
`bot.on` is the same as in node-telegram-bot-api, all params which can receive this function you can see [here](https://telegraf.js.org/classes/Telegraf.html#on). 
`bot.hears` is just a function which reacts on user's message.
And to launch our bot we use `bot.launch()`
It would seem that it is the end, but in fact it is one part of opportunities which this library gives. Here you can use [scenes](https://telegraf.js.org/modules/Scenes.html).

## Telegraf Scenes
Let's imagine that telegram bot is a theatre with one viewer - our user. When performance starts, the first scene is beginning, let's name it start scene. There are some actions take place in this scene for some time and then the scene ends, in another words we left the scene. So this interval from beginning of our scene and ending is our scene.
Telegraf Scene is the same, but let's display it in code. 
This library has two types of scene: [Base Scene](https://telegraf.js.org/classes/Scenes.BaseScene.html) and [Wizard Scene](https://telegraf.js.org/classes/Scenes.WizardScene.html).

To set up our scenes we need to register them with [Stage](https://telegraf.js.org/classes/Scenes.Stage.html). Let's do it in our main file.

app.js
```javascript
const { Scenes, Telegraf } = require("telegraf");
const start = require("./controllers/start"); // scene file 
const about = require("./controllers/about"); // scene file 
const settings = require("./controllers/settings"); // scene file 
const contact = require("./controllers/contact"); // scene file 
const search = require("./controllers/search"); // scene file 


const bot = new Telegraf(process.env.TELEGRAM_TOKEN);
const stage = new Scenes.Stage([start, about, settings, contact, search]); // Register our scenes
bot.use(stage.middleware()); // Stage middleware
bot.hears("settings", Scenes.Stage.enter("settings")); // Entering the settings scene when listener worked
```
Here we imported our scenes and registered them in array. Also we added a stage middleware without which our scenes won't work. And to enter our scene we set bot listener with `bot.hears` and then as a first param we type our scene id(see in next paragraph) and then we enter the scene with `Scenes.Stage.enter`.
### Base Scene 
Here is an example of this scene:
settings.js
```javascript
const { Scenes } = require("telegraf");
const Scene = Scenes.BaseScene;
const { getMainKeyboard, getBackKeyboard } = require("../../util/keyboards");
const { leave } = Scenes.Stage;
const settings = new Scene("settings");

settings.enter(async (ctx) => {
  const { backKeyboard } = getBackKeyboard();
  await ctx.reply("settings.what_to_change", backKeyboard);
});
settings.leave(async (ctx) => {
  const { mainKeyboard } = getMainKeyboard();
  await ctx.reply("shared.what_next", mainKeyboard);
  await ctx.scene.leave();
});
settings.hears("Back", leave());
settings.action("backButtonId", console.log("Back button was clicked"));
module.exports = settings;

```
../../util/keyboards
```javascript
const { Markup } = require("telegraf");

exports.getMainKeyboard = () => {
  let mainKeyboard = Markup.keyboard([
    ["Movies", "Settings"],
  ]);
  mainKeyboard = mainKeyboard.oneTime();

  return mainKeyboard;

};
exports.getBackKeyboard = () => {
  let backKeyboard = Markup.keyboard(["Back"]);
  backKeyboard = backKeyboard.oneTime();
  return backKeyboard;
};

```
It is file with our settings scene. To initialize it, we use `new Scene("scene id")`. When user entered our scene he will receive response from `settings.enter(async function(ctx))`, in which we make our first step of scene. Then all actions in our scene will be in `settings.on` or in `settings.hears`. Then, when scene is ending we use `settings.leave` in which we have function with moving back to default scene(start scene) and `ctx.scene.leave()`, which is required to leave scene. Without it you have a risk to stay in this scene forever. 
Also, if we have keyboard, which can be created by `Markup.keyboard`, all our buttons there are strings in array. If we want to make many rows in our keyboard, we can make many arrays, like if we have two arrays with two strings(buttons), we will get keyboard with 2 rows with 2 buttons in each. 
When we tap on button, we can handle this tap in `scene.action(buttonId)`, where buttonId is our button's second param in `Markup.button.callback`. Also we can create inline keyboard with `Markup.inlineKeyboard`

```javascript
Markup.inlineKeyboard(
  [
    Markup.button.callback("Back", "backButtonId"),
  ],
  {}
)
  .oneTime()
  .resize();

```

### Wizard Scene
All logic of this scene is the same as in previous, but here we have a chain with scene's steps. To go to our next step we use `ctx.wizard.next` and `ctx.wizard.steps[ctx.wizard.cursor](ctx)` too, cause in last versions of Telegraf without it our step won't change. To leave from scene we use `ctx.scene.leave`

```javascript
const { Scenes } = require("telegraf");
const { getMainKeyboard } = require("../../util/keyboards");
const Wizard = Scenes.WizardScene;
const about = new Wizard(
  "about", // Our wizard scene id, which we will use to enter the scene
  async (ctx) => {
    await ctx.reply(ctx.i18n.t("scenes.about.main"));
    ctx.wizard.next();
    return ctx.wizard.steps[ctx.wizard.cursor](ctx);
  },
  async (ctx) => {
    const { mainKeyboard } = getMainKeyboard(ctx);
    await ctx.reply(ctx.i18n.t("shared.what_next"), mainKeyboard);
    return ctx.scene.leave();
  }
);
module.exports = about;

```

### Session
And the last main thing in this library is session. It is like local storage in web but for telegram bots. Here we can save params that we need to save and use them in future. To set up, let's change our main file:

```javascript
const { Scenes, session, Telegraf } = require("telegraf");
const start = require("./controllers/start"); // scene file 
const about = require("./controllers/about"); // scene file 
const settings = require("./controllers/settings"); // scene file 
const contact = require("./controllers/contact"); // scene file 
const search = require("./controllers/search"); // scene file 


const bot = new Telegraf(process.env.TELEGRAM_TOKEN);
const stage = new Scenes.Stage([start, about, settings, contact, search]); // Register our scenes
bot.use(session()); // Session middleware
bot.use(stage.middleware()); // Stage middleware
bot.hears("settings", Scenes.Stage.enter("settings")); // Entering the settings scene when listener worked
```
Here we imported our session from telegraf and made a session middleware. Then we can use it in our scenes. For example:

```javascript
const { Scenes, session } = require("telegraf");
const { getMainKeyboard, getBackKeyboard } = require("../../util/keyboards");
const { User } = require("../../models");
const { getMoviesBySearch, getMovieKeyboard } = require("./helpers");
const Base = Scenes.BaseScene;
const search = new Base("search");
const { leave } = Scenes.Stage;

search.enter(async (ctx) => {
  const backKeyboard = getBackKeyboard(ctx);
  await ctx.replyWithHTML("scenes.search.welcome_to_search",
    backKeyboard
  );
});

search.hears("Back"), leave());
search.on("text", async (ctx) => {
  const user = await User.findById(ctx.from.id);
  const movies = getMoviesBySearch(user.language, ctx.message.text);
  const movieKeyboard = getMovieKeyboard(ctx);
  ctx.session.movies = movies; // Set session field
  ctx.session.index = 0; // Set session field
  ctx.replyWithHTML(``, movieKeyboard);
});

search.leave(async (ctx) => {
  const { mainKeyboard } = getMainKeyboard(ctx);
  await ctx.reply("shared.what_next", mainKeyboard);
  delete ctx.session.index; // Delete session field
  delete ctx.session.movies; // Delete session field
  await ctx.scene.leave();
});
module.exports = search;
```

## Conclusions
In this post I explained how to deal with the most popular js telegram libraries step by step. As I said, I prefer Telegraf to Node-Telegram-Bot-Api because it is more interesting and readable. If you have any thoughts about it, write at the comments below and correct me if I made some mistakes in this post, cause I don't speak English fluently 😅 

## In addition
Today in my country, in Ukraine, is a war, which caused by Russia's aggression. I want to have a peace in my country, our army does their best and it would be great if you can support us writing #nowarinukraine in your dev posts. I hope that soon I will be able to write any posts here and make code projects again, cause now I am sitting in basement and just wait.