06Dec
Building a Telegram Bot
Building a Telegram Bot

TL;DR In this article you’ll learn how to build a Telegram bot using Node.js. You’ll also gain a very practical understanding by developing a bot that retrieves an English word from a user and then returns the various dictionary definitions of that word using the Oxford Dictionary API.

Prerequisites

To follow along with this article, it is expected that you have a basic understanding of Node.js and Javascript. You’ll also need to have Node and NPM installed, If you don’t have the Node package installed you can get it from the official Node.js website, adequate instructions are provided there on how to get it setup. It is also important to note that the code in this article uses certain features of ES2019 which requires Node version 11.6 or higher so you may encounter errors if you are using a lower Node version. If you currently have a lower version you can use nvm to install that version(11.6) alongside your current Node version. You can get the final code of the application here.

What is a Telegram Bot?

Telegram gives a short and concise explanation:

Bots are third-party applications that run inside Telegram. Users can interact with bots by sending them messages, commands and inline requests.

Telegram has an existing bot API that allows us to connect bots to their system and also provides ways for us to use this API to control these bots. Essentially the way we would interact with a Telegram bot is to send it messages or commands through the Telegram chat UI, the bot receives such message or command and interacts with it in a certain way based on how it has been programmed to.

Now that we have a basic understanding of what a Telegram bot is and how it works, let’s go ahead and start building one to give us a more practical understanding.

Scaffolding the Application

Firstly let’s go ahead and create a directory for the application, head over to a convenient directory on your system and run the following code in your shell/terminal:

mkdir telegram-bot

Now, navigate to the newly created directory and run the command below:

cd telegram-bot

// Initializes a package.json file
npm init

You should get a prompt asking for some information about the project when you’re done filling that a new NPM project should be initialized in the application directory. This also creates a package.json file, this file holds necessary information regarding the application and also the project’s dependencies which we’ll install soon.

Next up, let’s take a look at the proposed directory structure, it is fairly simple and straight-forward:

- bot.js 
- parser.js 
- package.json 
- .env

Installing the necessary packages

We’ll need some packages to help us build certain parts of the application. Run the following command in your terminal then we’ll take a look at what purpose each package serves:

npm install axios node-telegram-bot-api dotenv body-parser express

Here’s a brief rundown of what each installed package actually helps us with:

  • axios : is a promise-based Javascript library that enables us to perform HTTP requests in Node, we’ll use this library particularly to communicate with the Oxford Dictionary API from our Node application.
  • node-telegram-bot-api : is a package that wraps around the official Telegram Bot API . It provides methods that help us to interact with the Bot API easily and makes developing our bot seamless.
  • dotenv: This package loads environmental variables from a .env file into Node’s process.env object. It makes specified environmental variables available all through the application.
  • body-parser: is used to parse incoming data from request bodies such as form data and attaches the parsed value to an object which can then be accessed by an express middleware.
  • express: makes it easy to build APIs and server-side applications with Node, providing useful features such as routing, middlewares, etc..

As we go through the article, it’ll become much clearer and transparent what role each package plays in helping us develop our bot. Next, let’s sign up for the Oxford Dictionary API.

Setting up the Oxford Dictionary API

As stated earlier, we’ll be creating a bot that responds with the meaning/definitions of the word a user sends to it. How this works is: When a user sends a word to the bot, the word is retrieved and sent through the Oxford Dictionary API which gets the various definitions of the word returned in a JSON format. We can then parse the response and provide it in a readable format to the bot which then responds to the user.

To use the Oxford Dictionary API we’ll have to sign up and get credentials(API keys), head over to the official Developer website and sign up. There are different plans available, for the purpose of this article you can go ahead and sign up with the free developer plan:

Oxford Dictionary API plans
Oxford Dictionary API plans

When that’s done you get redirected to a dashboard where you’ll need to create an application, this would give you access to credentials such as an application ID and application keys which you’ll use to access the API:

Oxford Dictionary API
Oxford Dictionary API

Store both the application ID and application key, we’ll be using them very soon.

With that sorted out, let’s create a bot through Telegram and get an authorization key.

Creating our Bot with Telegram

To use a Telegram bot, we’ll have to let Telegram create it for us. Telegram provides a bot( Botfather ) that we can use to create bots, there are a few short steps to follow, simply head over to this link and follow the steps listed there:

BotFather
BotFather

When you’re done creating your bot you should get a token/authorization key, store that somewhere because through that authorization key we would be able to listen to commands sent to the bot and also give adequate responses. Let’s get to the main part of the article which is coupling up the bot and allowing it to receive specific commands.

Creating the bot

Now we have everything all set, let’s program our bot to respond to specific commands from the user. First of all, create a .env file in the current directory, this file will hold all environmental variables needed by our application, these environmental variables will then get loaded onto Node’s process.env object and will be accessible throughout the application. Go ahead and create the .env file if you haven’t:

// .env

TELEGRAM_TOKEN=YOUR_TELEGRAM_TOKEN
OXFORD_APP_ID=YOUR_OXFORD_APP_ID
OXFORD_APP_KEY=YOUR_OXFORD_APP_KEY
OXFORD_API_URL=https://od-api.oxforddictionaries.com/api/v2

Replace the placeholder values above with the corresponding values that you have previously gotten and stored.

Next up, let’s add some functionality to our bot, create a bot.js file if you haven’t, copy and paste the following code below and we’ll go into details right after:

// bot.js

const TelegramBot = require('node-telegram-bot-api');
const axios = require('axios');
// This file would be created soon
const parser = require('./parser.js');

require('dotenv').config();

const token = process.env.TELEGRAM_TOKEN;
let bot;

if (process.env.NODE_ENV === 'production') {
   bot = new TelegramBot(token);
   bot.setWebHook(process.env.HEROKU_URL + bot.token);
} else {
   bot = new TelegramBot(token, { polling: true });
}

...

In the code above, we did some basic setup. If you notice, we’re looking for a particular environmental variable called NODE_ENV, that variable would be automatically set when we deploy our bot to Heroku. When running our bot locally, long polling is a better mechanism to listen for requests however when we deploy to Heroku we would want to switch to using webhooks instead. Don’t worry if you don’t get this now, we’ll go in-depth into the reasons why this is necessary very soon, just follow along for now.

When we send a word through the Dictionary API it returns the various definitions found for that word in a JSON format, you could take a look at a sample response here. The response is quite bulky and unreadable so we have to parse it into something much more user-friendly. I went through the trouble of creating a little parser to format the response into HTML. Create a file called parser.js and paste the following code into it:

// parser.js

function mainEntriesToHtml(entries) {
  return entries.map(entry => {
    let subDefinitionsHtml;
    const mainDefinitionHtml = `« ${entry.mainDefinitions}`
    if (entry.subDefinitions) {
      subDefinitionsHtml = entry.subDefinitions.map(subDefinition => {
        return `
        » ${subDefinition}`
      }).join('')
    }
    if (!subDefinitionsHtml) {
      return `
      <b>DEFINITION</b>:
        ${mainDefinitionHtml}
        `
    } else {
      return `
      <b>DEFINITION</b>:
        ${mainDefinitionHtml}
          <strong>SUB-DEFINITION</strong>: ${subDefinitionsHtml}
          `
    }
  })
}

function parser(json) {
  let definitionCount = 0;
  const parseEntries = json.results[0].lexicalEntries.flatMap(lexicalEntry => {
    const constructObject = { lexicalCategory: lexicalEntry.lexicalCategory.text }
    constructObject.entries = lexicalEntry.entries.map(entry => {
      return entry.senses.map(sense => {
        definitionCount++
        const definitions = { mainDefinitions: sense.definitions }
        if (sense.subsenses && Array.isArray(sense.subsenses)) {
          definitions.subDefinitions = sense.subsenses.map(subsense => subsense.definitions).flat()
        }
        return definitions
      }).flat()
    }).flat()
    return constructObject
  })

  const parsedHtml = parseEntries.map(entry => {
    return `
    <i>CATEGORY: ${entry.lexicalCategory}</i>
    ${mainEntriesToHtml(entry.entries).join('')}`
  }).join('');

  return `
  Word: <b>${json.word}</b>
  <b>${definitionCount}</b> definition(s) found for the word: <b>${json.word}</b>

  ${parsedHtml}`
}

module.exports = parser

Array.flatMap is an ES2019 feature, this method is only available on Node version 11.6 and above so you may run into an error if you’re running a lower Node version.

With that done, paste the following code at the bottom of the bot.js file and we’ll go through it right after:

...

// Matches "/word whatever"
bot.onText(/\/word (.+)/, (msg, match) => {
  const chatId = msg.chat.id;
  const word = match[1];
  axios
    .get(`${process.env.OXFORD_API_URL}/entries/en-gb/${word}`, {
      params: {
        fields: 'definitions',
        strictMatch: 'false'
      },
      headers: {
        app_id: process.env.OXFORD_APP_ID,
        app_key: process.env.OXFORD_APP_KEY
      }
    })
    .then(response => {
      const parsedHtml = parser(response.data);
      bot.sendMessage(chatId, parsedHtml, { parse_mode: 'HTML' });
    })
    .catch(error => {
      const errorText = error.response.status === 404 ? `No definition found for the word: <b>${word}</b>` : `<b>An error occured, please try again later</b>`;
      bot.sendMessage(chatId, errorText, { parse_mode:'HTML'})
    });
});

In the code above, through a Regex expression /\/word (.+)/ we’re telling our bot to only respond to user messages that start with /word followed by the word the user wants to get the meaning of e.g /word bot. When there’s a match the anonymous function attached to bot.onText gets invoked with two arguments, the first argument is an object containing information about the message sent and extra properties such as the user chat ID. The second argument, on the other hand, is an array of the words the user sent to the bot, So if the user sent a message to the bot: /word bot and there’s a match, the second argument would be an array: ['/word', 'bot'].

Through the second argument, we can extract the word the user sent to the bot which in this case should be bot, that word is passed over to the Oxford Dictionary API which then returns the various definitions of the word in a JSON format. The JSON response is passed through our parser which returns the result in HTML format, and then we can use that to reply to our users.

There’s a bot.sendMessage method which allows us to send the response to the specific user using their chatId. The parse_mode is currently set to HTML, so when we send back a message to the user in HTML it will get rendered properly in the chat UI. We could also set the parse_mode property to markdown if we ever decide to send our response in that format.

That’s really all, let’s go ahead and test our bot locally. In your terminal, run:

node bot.js

Try sending a message to your bot starting with /word followed by the word you want to get the definition of e.g bot and it should respond with the various definitions of the word. Here’s an example:

Biblio bot
Biblio bot

Superb, I know!

Deploying to Heroku

Currently, we have our bot working locally. However, the bot will only respond to messages as long as our Node server is up and running locally, so if the Node process gets shut down, the bot inevitably goes down and we really don’t want that. In this last part, we want to make our bot run on the cloud and be available every time, a PaaS such as Heroku makes this possible.

If you don’t have a Heroku account, you can sign up for one here. When you’re done signing up you should get redirected to your Heroku dashboard, you would need to create a new Heroku application from the dashboard:

Create Application from Heroku Dashboard
Create Application from Heroku Dashboard

When you’re done supplying the necessary information, a new application should be created for you and you’d get access to an application URL. You can click on the Open App button at the top right corner of the application dashboard to get your application URL.  The URL should normally be in this format: https://{your_app_name}.herokuapp.com. Heroku provides a suitable GUI that allows us to add environmental variables to our application, go over to the settings tab of the newly created application and click on Reveal Config Vars... then copy over the values from your .env file and add each of them as well as your Heroku app’s URL:

Add Environmental Variables
Add Environmental Variables

We now have our Heroku app set up, however, there are some changes we would have to make to our application so we won’t have any issues after we deploy.

Currently, our bot uses long polling locally, it’s a fairly complex mechanism but it works by having to check for updates very frequently, you can think of it as a loop. This works pretty fine locally but when we deploy our bot to Heroku, the server is going to get shut down after 30 minutes of inactivity meaning the polling loop inevitably gets shut down too. A way to fix this issue is by using webhooks instead, how this works is we simply set a location in the form of a URL where we can use to inform our bot of updates. When an update is available, it is sent through that URL and informs our bot, which then processes the update and gives an adequate response to the user.

So we have to make sure that when we deploy to Heroku, our bot uses webhooks instead.

For us to do that, paste the code below into the bottom of the bot.js file and we’ll go through it in detail right after:

// bot.js

// Move the package imports to the top of the file
const express = require('express')
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());

app.listen(process.env.PORT);

app.post('/' + bot.token, (req, res) => {
  bot.processUpdate(req.body);
  res.sendStatus(200);
});

If you recall, in the bot.js file, we specified that for production environments a method bot.setWebHook(process.env.HEROKU_URL + bot.token) should be called allowing us to use webhooks instead. Whenever there is a message/update for the bot, a POST request will get sent to a specified URL which in this case should be https://YOUR_HEROKU_URL/YOUR_TELEGRAM_TOKEN, a JSON-serialized update would also be sent along attached to the body.

The bodyParser package intercepts and parses the JSON-serialized update before attaching it onto the req.body object which can then be accessed by our middleware. There’s a handy processUpdate method that gets the available update, processes it and invokes the registered callback attached to the bot.onText method. This way even when the Heroku application idles out due to inactivity, once an update is ready, that URL gets hit and the application restarts.

With that sorted out, let’s deploy our application to Heroku. There are multiple ways to do this and you can find the various ways in the deploy tab of your Heroku application. However, for the sake of simplicity in this tutorial, we’ll be using the Heroku CLI. You can go ahead and install Heroku CLI if you don’t have it. When you’ve downloaded and installed the Heroku CLI, make sure you’re in your project’s directory, open up the terminal and run the following command:

// Initializes a git repository
git init

heroku login

You should also create a .gitignore file and add the node_modules directory, telling git to not track changes made within that directory:

// .gitgnore

node_modules/
.env

After that, set your git remote to point to the Heroku application previously created. All you need to do this is your Heroku app’s name:

heroku git:remote -a YOUR_APPLICATION_NAME

When deploying to Heroku, the npm start command is used to start the application so we want the bot.js file to be executed whenever that command is run. In your package.json file, there should be an existing scripts object, simply add "start": "node bot.js" as shown below:

// package.json

...

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   // Add the code below
   "start": "node bot.js"
}

...

Lastly, commit the changes made and push the code to Heroku using:

git add .
git commit -am "Deployed to Heroku!!"
git push heroku master

That’s all! your application should be up and running. Head over to your application dashboard, you can view the logs  by clicking on the More button at the top right-hand corner, you should see that the bot is up and running:

Application Logs
Application Logs

You can try sending your bot a command to get the meaning of a word and it should be working perfectly!.

Conclusion

Over the course of this article, we’ve learned how to create a Telegram bot, send commands to it and also program our bot to respond to commands in a specific way. We were also able to learn about the issue that may arise when deploying our bot to a PAAS platform like Heroku and a recommended way to solve that issue. Most importantly we’ve learned how to deploy our bot to a PAAS platform like Heroku making it always available to respond to requests.

I hope you enjoyed the article!. Let me know in the comment section.

JAMstack Architecture with Next.js

The Jamstack architecture, a term coined by Mathias Biilmann, the co-founder of Netlify, encompasses a set of structural practices that rely on client-side JavaScript, reusable APIs, and prebuilt markup to build websites and applications. At its core, the Jamstack advocates for rendering web applications into static HTML files during the build process and serving them efficiently to clients.

Introduction to Micro Frontends: The New Tech on the Block

Like with all powerful technologies, micro frontends often cause confusion among developers, as evidenced by the ever so popular search queries like “What are micro frontends?” It may be tempting to try and ignore it, but then you’ll be missing out on the amazing opportunities that micro frontends provide — after all, they’re not just a hip trend to follow. In this article, we’ll explore what micro frontends are, what benefits they can bring, how they can be used, and what caveats and pitfalls they pose.

6 Replies to “Building a Telegram Bot with Node.js”

  1. Liked! Please continue, I wanna see something more complex with chatbots

    1. Godson Obielum 5 years ago

      Hello Nikita,
      Thanks for your feedback!, happy you liked the article. I’ll certainly take that under consideration.

  2. rededmin 5 years ago

    Hello,

    The oxford dictionary site asks to add credit card info and currently, I do not have one. Anyway I could bypass it ? or just do my thing without entering my credit card information?

  3. Hello Godson!
    Your article is very useful. I repeated your bot, everything works fine!
    Could you help me to apply your fix to heroku for a simple example in telegraf.js?
    Thank you so much!

    https://telegraf.js.org/#/?id=webhooks

  4. I get an error : Unhandled rejection Error: ETELEGRAM: 400 Bad Request: bad webhook: Failed to resolve host: No address associated with hostname

  5. ahh I forgot to add the HEROKU_URL value to production. Leaving comment in case anyone else makes that mistake. great tutorial! Thanks!!

Leave a Reply