The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.
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:
Once you’ve completed your bot, you will receive a fact about an animal whenever you send a custom Telegram slash command.
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.
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:
{
"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.
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:
After choosing your bot name and username you will receive a message containing your bot access token:
Copy the bot token, and open your .env
file:
- nano .env
Save the Bot token in a variable named BOT_TOKEN
:
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:
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:
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.
main.js
FileIn 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:
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 uuid
module 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:
. . .
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:
. . .
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:
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.
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:
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:
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:
. . .
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:
. . .
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:
. . .
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:
. . .
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:
. . .
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:
. . .
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:
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:
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.
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
.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Any idea how one can do this sort of thing on DO’s serverless functions?
This comment has been deleted
This comment has been deleted
This comment has been deleted