Tutorial

How To Build a Telegram Quotes Generator Bot With Node.js, Telegraf, Jimp, and Pexels

Node.jsAPIJavaScriptCustom Images

The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

Introduction

In this tutorial, you will use Node.js, telegraf, jimp, and the Pexels API to build a Telegram chatbot that will send you a randomly selected image with a fact overlayed. A Telegram bot is a bot you can interact with using custom slash commands through your preferred Telegram client. You will create the bot through Telegram, and define its logic to select a random animal image and a fact on the animal using JavaScript.

At the end of this tutorial you will have a Telegram chatbot that looks like the following:

Imgur

Once you’ve completed your bot, you will receive a fact about an animal whenever you send a custom Telegram slash command.

Prerequisites

In order to follow this tutorial the reader will need the following tools:

This tutorial was verified with Node v12.18.2 and npm v6.14.8.

Step 1 — Creating the Project Root Directory

In this section, you will create the directory where you will build the chatbot, create a Node project, and install the required dependencies.

Open a terminal window and create a new directory called facts-bot:

  • mkdir facts-bot

Navigate into the directory:

  • cd facts-bot

Create a directory named temp:

  • mkdir temp

With the command above, you created a directory named temp. In this directory, you will temporarily store the images that your bot will send to the user.

Now, you’ll create a new Node.js project. Running npm’s init command will create a package.json file, which will manage your dependencies and metadata.

Run the initialization command:

  • npm init

To accept the default values, press ENTER to all prompts. Alternately, you can personalize your responses. To do this, review npm’s initialization settings in Step 1 of the tutorial How To Use Node.js Modules with npm and package.json.

Open the package.json file and edit it:

  • nano package.json

Now, you’ll update the properties in your package.json file. Replace the contents inside the file with the highlighted code:

package.json
{
  "name": "facts-bot",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "nodemon main.js"
  },
  "author": "",
  "license": "ISC"
}

Here you changed the main and scripts properties. By changing the main property, you have set the application main file to main.js. This will inform Node the main.js file is the primary entry point to your program. In the scripts property you have have added a script named start, which allows you to set the command that is supposed to run when you start the application. Once you call the script the command nodemon will run the main.js file you will create in the next step.

With your settings now defined in your package.json file, you will now create a file that will store your environment variables. In your terminal, create a file named .env:

touch .env

In your .env file, you will store your Telegram bot token and Pexels API key. A Telegram Bot token allows you to interact with your Telegram bot. The Pexels API key allows you to interact with the Pexels API. You will store your environment variables in a later step.

This time, you’ll use npm to install the dependencies telegraf, dotenv, pexels, jimp, and uuid. You’ll also use the --save flag to save the dependencies. In your terminal, run the following command:

  • npm install telegraf dotenv pexels jimp uuid --save

In this command, you have installed:

  • telegraf: a library that helps you develop your own Telegram bots using JavaScript or TypeScript. You are going to use it to build your bot.
  • dotenv: a zero-dependency module that loads environment variables from a .env file into process.env. You are going to use this module to retrieve the bot token and Pexels API key from the .env file you created.
  • pexels: a convenient wrapper around the Pexels API that can be used both on the server in Node.js and the browser. You are going to use this module to retrieve animal images from Pexels.
  • jimp: an image processing library written entirely in JavaScript for Node, with zero external or native dependencies. You are going to use this library to edit images retrieved from Pexels and insert a fact about an animal in them.
  • uuid: a module that allows you to generate RFC-compliant UUIDs in JavaScript. You are going to use this module to create a unique name for the image retrieved from Pexels.

Now, install nodemon as a dev dependency:

  • npm install nodemon --save-dev

nodemon is a tool that develops Node.js based applications by automatically restarting the Node application when it detects file changes in the directory. You will use this module to start and keep your app running as you test your bot.

Note: At the time of writing these are the versions of the modules that are being used:telegraf : 4.3.0 ; dotenv : 8.2.0; pexels : 1.2.1 ;jimp : 0.16.1 ; uuid : 8.3.2; nodemon : 2.0.12.

In this step, you created a project directory and initialized a Node.js project for your bot. You also installed the modules needed to build the bot. In the next step, you will register a bot in Telegram and retrieve an API key for the Pexels API.

Step 2 — Registering Your Bot and Retrieving an API Key From the Pexels API

In this section, you will first register a bot with BotFather, then retrieve an API key for the Pexels API. BotFather is a chatbot managed by Telegram that allows users to create and manage chatbots.

Open your preferred Telegram client, search for @BotFather, and start the chat. Send the /newbot slash command and follow the instructions sent by the BotFather:

Imgur

After choosing your bot name and username you will receive a message containing your bot access token:

Imgur

Copy the bot token, and open your .env file:

  • nano .env

Save the Bot token in a variable named BOT_TOKEN:

.env
BOT_TOKEN = "Your bot token"

Now that you have saved your bot token in the .env file, it’s time to retrieve the Pexels API key.

Navigate to Pexels, and log in to your Pexels account. Click on the Image & Video API tab and create a new API key:

Imgur

Copy the API key, and open your .env file:

  • nano .env

Save the API key in a variable named PEXELS_API_KEY. Your .env should look like the following:

.env
BOT_TOKEN = "Your_bot_token"
PEXELS_API_KEY = "Your_Pexels_API_key"

In this section, You have registered your bot, retrieved your Pexels API key, and saved your bot token and Pexels API key in your .env file. In the next section, you are going to create the file responsible for running the bot.

Step 3 — Creating the main.js File

In this section, you will create and build out your bot. You will create a file with the label main.js, and this will contain your bot’s logic.

In the root directory of your project, create and open the main.js file using your preferred text editor:

  • nano main.js

Within the main.js file, add the following code to import the libraries you’ll use:

main.js
const { Telegraf } = require('telegraf')
const { v4: uuidV4 } = require('uuid')
require('dotenv').config()
let factGenerator = require('./factGenerator')

In this code block, you have required in the telegraf, the uuid, the dotenv module, and a file named factGenerator.js. You are going to use the telegraf module to start and manage the bot, the uuidmodule to generate a unique file name for the image, and the dotenv module to get your Telegram bot token and Pexels API key stored in the .env file. The factGenerator.js file will be used to retrieve a random animal image from Pexels, insert a fact about the animal, and delete the image after it’s sent to the user. You will create this file in the next section.

Below the require statements, add the following code to create an instance of the bot:

main.js
. . .

const bot = new Telegraf(process.env.BOT_TOKEN)

bot.start((ctx) => {
    let message = ` Please use the /fact command to receive a new fact`
    ctx.reply(message)
})

Here, you retrieved and used the BOT_TOKEN that BotFather sent, created a new bot instance, and assigned it to a variable called bot. After creating a new bot instance, you added a command listener for the /start command. This command is responsible for initiating a conversation between a user and the bot. Once a user sends a message containing /start the bot replies with a message asking the user to use the /fact command to receive a new fact.

You have now created the command handler responsible for starting the interaction with your chatbot. Now, let’s create the command handler for generating a fact. Below the .start() command, add the following code:

main.js
. . .

bot.command('fact', async (ctx) => {
    try {
        ctx.reply('Generating image, Please wait !!!')
        let imagePath = `./temp/${uuidV4()}.jpg`
        await factGenerator.generateImage(imagePath)
        await ctx.replyWithPhoto({ source: imagePath })
        factGenerator.deleteImage(imagePath)
    } catch (error) {
        console.log('error', error)
        ctx.reply('error sending image')
    }
})

bot.launch()

In this code block, you created a command listener for the custom /fact slash command. Once this command is triggered from the Telegram user interface, the bot sends a message to the user. The uuid module is used to generate the image name and path. The image will be stored in the /temp directory that you created in Step 1. Afterwards, the image path is passed to a method named generateImage() you’ll define in the factGenerator.js file to generate an image containing a fact about an animal. Once the image is generated, the image is sent to the user. Then, the image path is passed to a method named deleteFile in the factGenerator.js file to delete the image. Lastly, you launched your bot by calling the bot.launch() method.

The main.js file will look like the following:

main.js
const { Telegraf } = require('telegraf')
const { v4: uuidV4 } = require('uuid')
require('dotenv').config()
let factGenerator = require('./factGenerator')


const bot = new Telegraf(process.env.BOT_TOKEN)

bot.start((ctx) => {
    let message = ` Please use the /fact command to receive a new fact`
    ctx.reply(message)
})


bot.command('fact', async (ctx) => {
    try {
        ctx.reply('Generating image, Please wait !!!')
        let imagePath = `./temp/${uuidV4()}.jpg`
        await factGenerator.generateImage(imagePath)
        await ctx.replyWithPhoto({ source: imagePath })
        factGenerator.deleteImage(imagePath)
    } catch (error) {
        console.log('error', error)
        ctx.reply('error sending image')
    }
});


bot.launch()

You have created the file responsible for running and managing your bot. You will now set facts for the animal and build out the bot’s logic in the factGenerator.js file.

Step 4 — Creating the Fact Generator File and Building the Bot Logic

In this section, you will create files named fact.js and factGenerator.js. fact.js will store facts about animals in one data source. The factGenerator.js file will contain the code needed to retrieve a random fact about an animal from a file, retrieve an image from Pexels, use jimp to write the fact in the retrieved image, and delete the image.

In the root directory of your project, create and open the facts.js file using your preferred text editor:

  • nano facts.js

Within the facts.js file add the following code to create your data source:

facts.js
const facts = [
    {
        fact: "Mother pandas keep contact with their cub nearly 100% of the time during their first month - with the cub resting on her front and remaining covered by her paw, arm or head.",
        animal: "Panda"
    },
    {
        fact: "The elephant's temporal lobe (the area of the brain associated with memory) is larger and denser than that of people - hence the saying 'elephants never forget'.",
        animal: "Elephant"
    },
    {
        fact: "On average, males weigh 190kg and females weigh 126kg . They need this weight and power behind them to hunt large prey and defend their pride.  ",
        animal: "Lion"
    },
    {
        fact: "The Amazon river is home to four species of river dolphin that are found nowhere else on Earth. ",
        animal: "Dolphin"
    },
]

module.exports = { facts }

In this code block, you defined an object with an array containing facts about animals and stored in a variable named facts. Each object has the following properties: fact and animal. In the property named fact, its value is a fact about an animal, while the property animal stores the name of the animal. Lastly, you are exporting the facts array.

Now, create a file named factGenerator.js:

  • nano factGenerator.js

Inside the factGenerator.js file, add the following code to require in the dependencies you’ll use to build out the logic to make your animal image:

factGenerator.js
let { createClient } = require('pexels')
let Jimp = require('jimp')
const fs = require('fs')
let { facts } = require('./facts')

Here, you required in the pexels, the jimp, the fs module, and your facts.js file. You will use the pexels module to retrieve animal images from Pexels, the jimp module to edit the image retrieved from Pexels, and the fs module to delete the image from your file directory after it’s sent to the user.

Below the require statements, add the following code to generate an image:

factGenerator.js
. . .

async function generateImage(imagePath) {
  let fact = randomFact()
  let photo = await getRandomImage(fact.animal)
  await editImage(photo, imagePath, fact.fact)
}

In this code block, you created a function named generateImage(). This function takes as an argument the path of the Pexel image in your file directory. Once this function is called a function named randomFact() is called and the value returned is stored in a variable named fact. The randomFact() function randomly selects an object in the facts.js file. After receiving the object, its property animal is passed to a function named getRandomImage(). The getRandomImage() function will use the pexels module to search for images containing the name of the animal passed, and selects a random image. The value returned is stored in a variable named photo. Finally, the photo, imagePath, and the fact property from the facts.js file are passed to a function named editImage(). The editImage() function uses the jimp module to insert the random fact in the random image and then save the edited image in the imagePath.

Here, you have created the function that is called when you send the /fact slash command to the bot. Now you’ll create the functions getRandomImage() and editImage() and construct the logic behind selecting and editing a random image.

Below the generateImage() function, add the following code to set the randomization logic:

factGenerator.js
. . .

function randomFact() {
  let fact = facts[randomInteger(0, (facts.length - 1))]
  return fact
}


function randomInteger(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

You have now created the functions randomFact() and randomInteger(). The randomFact() function selects a random fact in the facts.js file by calling the randomInteger() function, and returns this object. The randomInteger() function returns a random integer in the interval of 0 and the number of facts in the facts.js file.

Now that you’ve defined functions to return a random fact and random integer, you’ll need to create a function to get a random image from Pexels. Below the randomInteger() function, add the following code to get a random image:

factGenerator.js
. . .

async function getRandomImage(animal) {
  try {
    const client = createClient(process.env.PEXELS_API_KEY)
    const query = animal
    let image

    await client.photos.search({ query, per_page: 10 }).then(res => {
      let images = res.photos
      image = images[randomInteger(0, (images.length - 1))]

    })

    return image

  } catch (error) {
    console.log('error downloading image', error)
    getRandomImage(animal)
  }
}

In this code block, you have created a function named getRandomImage(). This function takes as an argument an animal name. When this function is called a client object is created by using createClient() method object from the pexels module and the Pexels API key stored in the .env file. The animal name is stored in a variable called query, then the client object is used to search for images containing the value in the query. Once the images are found, a random image is selected with the help of the randomInteger() function. Finally, the random image is returned to the generateImage() method in your main.js file.

With your getRandomImage() function in place, the image selected needs to have a text overlay before it’s sent to your Telegram bot. Below the getRandomImage() function, add the following code to set the overlay:

factGenerator.js
. . .

async function editImage(image, imagePath, fact) {
  try {
    let imgURL = image.src.medium
    let animalImage = await Jimp.read(imgURL).catch(error => console.log('error ', error))
    let animalImageWidth = animalImage.bitmap.width
    let animalImageHeight = animalImage.bitmap.height
    let imgDarkener = await new Jimp(animalImageWidth, animalImageHeight, '#000000')
    imgDarkener = await imgDarkener.opacity(0.5)
    animalImage = await animalImage.composite(imgDarkener, 0, 0);


  } catch (error) {
    console.log("error editing image", error)
  } 

}

Here, you created a function named editImage(). This function takes as arguments a random animal labeled image, the imagePath, and a fact about this random animal. In the variable imgURL, the URL for the medium size of the image is retrieved from the Pexels API. Afterwards the read() method of jimp is used to load the image. Once the image is loaded and stored in a variable named animalImage, the image width and height are retrieved and stored in the variables animalImageWidth and animalImageHeight respectively. The variable imgDarkener stores new instance of Jimp() and darkens the image. The opacity() method of jimp is used to set imgDarkener’s opacity to 50%. Finally, the composite() method of jimp is used to put the contents in imgDarkener over the image in animalImage. This in return makes the image in animalImage darker before adding the text stored in the fact variable, and make the text visible over the image.

Note: Jimp by default provides a method named color() that allows you to adjust an image’s tonal levels. For the purpose of this tutorial, you’ll write a custom tonal adjuster as the color() method does not offer the precision necessary here.

At the bottom of the try block inside the editImage() function, add the following code:

factGenerator.js
. . .

async function editImage(image, imagePath,fact) {
  try {
    . . .

    let posX = animalImageWidth / 15
    let posY = animalImageHeight / 15
    let maxWidth = animalImageWidth - (posX * 2)
    let maxHeight = animalImageHeight - posY

    let font = await Jimp.loadFont(Jimp.FONT_SANS_16_WHITE)
    await animalImage.print(font, posX, posY, {
      text: fact,
      alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
      alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
    }, maxWidth, maxHeight)

    await animalImage.writeAsync(imagePath)
    console.log("Image generated successfully")


  } catch (error) {
    . . .
  }
}

In this code block, you used the animalImageWidth, and animalImageHeight to get the values that will be used to center the text in the animalImage. After, you used the loadFont() method of jimp to load the font, and store the font in a variable named font. The font color is white, the type is sans-serif (SANS), and the size is 16. Finally, you used the print() method of jimp to insert the fact in the animalImage, and the write() method to save the animalImage in the imagePath.

Now that you’ve created the function responsible for editing the image, you’ll need a function to delete the image from your file structure after it is sent to the user. Below your editImage() function, add the following code:

factGenerator.js
. . .

const deleteImage = (imagePath) => {
    fs.unlink(imagePath, (err) => {
        if (err) {
            return
        }
        console.log('file deleted')
    })
}


module.exports = { generateImage, deleteImage }

Here, you have created a function named deleteImage(). This function takes as an argument the variable imagePath. Once this function is called, the fs module is used to delete the image stored in the variable imagePath. Lastly, you exported the generateImage() function and the deleteImage() function.

With your functions in place, the factGenerator.js file will look like the following:

factGenerator.js
let { createClient } = require('pexels')
let Jimp = require('jimp')
const fs = require('fs')
let { facts } = require('./facts')

async function generateImage(imagePath) {
  let fact = randomFact()
  let photo = await getRandomImage(fact.animal)
  await editImage(photo, imagePath, fact.fact)
}


function randomFact() {
  let fact = facts[randomInteger(0, (facts.length - 1))]
  return fact
}


function randomInteger(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}


async function getRandomImage(animal) {
  try {
    const client = createClient(process.env.PEXELS_API_KEY)
    const query = animal
    let image

    await client.photos.search({ query, per_page: 10 }).then(res => {
      let images = res.photos
      image = images[randomInteger(0, (images.length - 1))]

    })

    return image

  } catch (error) {
    console.log('error downloading image', error)
    getRandomImage(animal)
  }
}


async function editImage(image, imagePath, fact) {
  try {
    let imgURL = image.src.medium
    let animalImage = await Jimp.read(imgURL).catch(error => console.log('error ', error))
    let animalImageWidth = animalImage.bitmap.width
    let animalImageHeight = animalImage.bitmap.height
    let imgDarkener = await new Jimp(animalImageWidth, animalImageHeight, '#000000')
    imgDarkener = await imgDarkener.opacity(0.5)
    animalImage = await animalImage.composite(imgDarkener, 0, 0);

    let posX = animalImageWidth / 15
    let posY = animalImageHeight / 15
    let maxWidth = animalImageWidth - (posX * 2)
    let maxHeight = animalImageHeight - posY

    let font = await Jimp.loadFont(Jimp.FONT_SANS_16_WHITE)
    await animalImage.print(font, posX, posY, {
      text: fact,
      alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
      alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
    }, maxWidth, maxHeight)

    await animalImage.writeAsync(imagePath)
    console.log("Image generated successfully")

  } catch (error) {
    console.log("error editing image", error)
  }

}


const deleteImage = (imagePath) => {
  fs.unlink(imagePath, (err) => {
    if (err) {
      return
    }
    console.log('file deleted')
  })
}


module.exports = { generateImage, deleteImage }

Save your factGenerator.js file. Return to your terminal, and run the following command to start your bot:

  • npm start

Open your preferred Telegram client, and search for your bot. Send a message with the /start command to initiate the conversation, or click the Start button. Then, send a message with the /fact command to receive your image.

You will receive an image similar to the following:

Imgur

You now see the image in your preferred Telegram client with a fact imposed over the image. You’ve created the file and functions responsible for retrieving a random fact from the facts.js file, retrieving an animal image from Pexels, and inserting a fact onto the image.

Conclusion

In this tutorial, you built a Telegram chatbot that sends an image of an animal with a fact overlayed through a custom slash command. You created the command handlers for the bot through the telegraf module. You also created functions responsible for retrieving a random fact, random images from Pexels using the pexels module, and inserting a fact over the random image using the jimp module. For more information about the Pexels API, telegraf and jimp modules please refer to documentation on the Pexels API, telegraf, jimp.

Creative Commons License