Back to Home

Telegram bot framework

bot · telegram · bot · framework · framework

Telegram bot framework

  • Tutorial
It just so happened that my line of business is closely intertwined with the creation of bots for Telegram. I started writing them right after the Telegram Bot API appeared , then there were no tools for this. I had to write a library for working with the API myself, as I partially discussed in my previous article . Over time, the library was rewritten several times and eventually overgrown with various chips. In the article I will try to talk about how to write bots with it.





To work with the API, you will first need token to get it, just write to this bot and follow its instructions.

I'll start right away with an example of a simple bot:

'use strict'
var tg = require('telegram-node-bot')('YOUR_TOKEN')
tg.router.
    when(['ping'], 'PingController')
tg.controller('PingController', ($) => {
    tg.for('ping', () => {
        $.sendMessage('pong')
    })
}) 


Works!



Now let's look at what is written there:

var tg = require('telegram-node-bot')('YOUR_TOKEN') 

Everything is clear here, just declare the module and pass it our token.

tg.router.
    when(['ping'], 'PingController')

Next, we announce the teams and routers that are responsible for these teams.

tg.controller('PingController', ($) => {
    tg.for('ping', () => {
        $.sendMessage('pong')
    })
}) 

After that we create the “PingController” controller and declare the ping command handler in it.

When the bot receives a command from the user, it will understand that the ping controller is responsible for the ping command and execute the ping command handler.

On this, you can close the article and start writing simple bots, and I will continue to tell.

Router


As I wrote above, the router is responsible for communicating the commands and controllers that process these commands.
Obviously, one controller can handle several commands.
The router also has an otherwise function , with which you can declare a controller that will handle unexpected commands:

tg.router.
    when(['test', 'test2'], 'TestController'). // "TestController" будет обрабатывать как команду test, так и команду test2
    when(['ping'], 'PingController'). 
    otherwise('OtherController') //"OtherController" будет вызван для всех остальных команд.



Controller


When the bot received a command from the user, the controllers enter the game. As you can see from the example above, the controllers process user commands. Within one controller, you can process several commands:

tg.controller('TestController', ($) => {
    tg.for('test', () => {
        $.sendMessage('test')
    })
    tg.for('test2', () => {
        $.sendMessage('test2')
    })
}) 

You can also write any functions, variables in the controller.

Scope


Each controller accepts a special $ - scope variable .
It stores everything we need to know about the request:


  • chatId - chat id where the request came from
  • user - information about the user who sent the request, more details here
  • message - all information about the message, more details
  • args - a message that the user sent, but without the command itself. (If the user sends "/ start 1" to args it will be - "1")


You may have noticed that, for example, we called the sendMessage function with scope . The fact is that in addition to the fields described above, scope also contains all the library functions with chatId already prescribed for a specific chat. The same sendMessage function can be called directly:

tg.controller('TestController', ($) => {
    tg.for('test', () => {
        tg.sendMessage($.chatId, 'test')
    })
    tg.for('test2', () => {
        tg.sendMessage($.chatId, 'test2')
    })
}) 

Agree, it’s not very convenient to write the chat id, given that we write in the same chat from where the message came.

Call chain


Sometimes we ask the user something and expect some information from him. How to implement this? Of course, we can store the state of users and, depending on it, process it in the “OtherController”, but this is not entirely beautiful, and breaks the structure and readability.
For such cases, scope has a waitForRequest function :
tg.controller('TestController', ($) => {
    tg.for('/reg', ($) => {
         $.sendMessage('Send me your name!')
         $.waitForRequest(($) => {
             $.sendMessage('Hi ' + $.message.text + '!')
         }) 
    })   
}) 

The waitForRequest function takes one argument - a callback , which it will call when the user sends the next message. A new scope is passed to this callback . As can be seen from the example above - we ask the user to enter his name, wait for his next message and welcome him.

Navigation



Suppose our bot has authorization, in the main controller we need to somehow check whether the user is authorized and redirect him to the login, but how? To do this, there is a routeTo function that takes a command and executes it as if the user had sent it to us:
tg.controller('StartController', ($) => {
    tg.for('/profile', ($) => {
        if(!logined){ // какая то логика авторизации
            $.routeTo("/login") // перенаправляем пользователя
        }       
    }) 
})


Forms


It often happens that you need to find out some information from the user, for this there is a form generator:
var form = {
    name: {
        q: 'Send me your name',
        error: 'sorry, wrong input',
        validator: (input, callback) => {
            if(input['text']) {
                callback(true)
                return
            }
            callback(false)
        }
    },
    age: {
        q: 'Send me your age',
        error: 'sorry, wrong input',
        validator: (input, callback) => {
            if(input['text'] && IsNumeric(input['text'])) {
                callback(true)
                return
            }
            callback(false)
        }
    },
    sex: {
        q: 'Select your sex',
        keyboard: [['male'],['famale'], ['UFO']],
        error: 'sorry, wrong input',
        validator: (input, callback) => {
            if(input['text'] && ['male', 'famale', 'UFO'].indexOf(input['text']) > -1) {
                callback(true)
                return
            }
            callback(false)
        }
    },         
}
$.runForm(form, (result) => {
    console.log(result)
})  

As you can see from the code, each field has a message sent to the user, a validator, which receives a message from the user and an error message in case of incorrect input data. You can also send keyboard ( keyboard ).
The runForm function will return an object with the same fields that we wrote in the form itself, in our case it is name and age.

Menu



There is a similar tool for the menu:
$.runMenu({
    message: 'Select:',
    'Exit': {
        message: 'Do you realy want to exit?',
        'yes': () => {
        },
        'no': () => {
        }
    } 
})  

The menu automatically creates a keyboard with field names and sends it along with the message that we specify in the message field .
A menu item can be either an object or a function. If it is an object, then the user will receive a submenu, and if it is a function, it will be called and will allow us to process the user’s request.

It is also possible to specify the location of the buttons in the menu keyboard, the layout field is responsible for this , if you do not pass it at all, then there will be one button on each line. You can pass the maximum number of buttons per line or an array of the number of buttons for each line:
$.runMenu({
    message: 'Select:',
    layout: 2,
    'test1': () => {}, //будет на первой строке
    'test2': () => {}, //будет на первой строке
    'test3': () => {}, //будет на второй строке
    'test4': () => {}, //будет на третей строке
    'test5': () => {}, //будет на четвертой строке
})  


$.runMenu({
    message: 'Select:',
    layout: [1, 2, 1, 1],
    'test1': () => {}, //будет на первой строке
    'test2': () => {}, //будет на второй строке
    'test3': () => {}, //будет на второй строке
    'test4': () => {}, //будет на третей строке
    'test5': () => {}, ///будет на четвертой строке
}) 


API Functions


All functions for working with the API have both mandatory and optional parameters. For example, the sendMessage function is described in the documentation for the required parameters (chatId, photo), but we can pass any additional parameters:
var options = {
     reply_markup: JSON.stringify({
         one_time_keyboard: true,
          keyboard: [['test']]
    })
}	            
$.sendMessage('test', options)


In this case, the last parameter is always callback.

Here is a list of currently supported API functions with required parameters (I remind you that if you call them from scope , the chatId parameter is not needed):

  • sendPhoto (chatId, photo)
  • sendDocument (chatId, document)
  • sendMessage (chatId, text)
  • sendLocation (chatId, latitude, longitude)
  • sendAudio (chatId, audio)
  • forwardMessage (chatId, fromChatId, messageId)
  • getFile (fileId)
  • sendChatAction (chatId, action)
  • getUserProfilePhotos (userId)
  • sendSticker (chatId, sticker)
  • sendVoice (chatId, voice)
  • sendVideo (chatId, video)


Examples of calling some functions:
var doc =  {
    value: fs.createReadStream('file.png'), //stream
    filename: 'photo.png',
    contentType: 'image/png'
}
$.sendDocument(doc)
$.sendPhoto(fs.createReadStream('photo.jpeg'))
$.sendAudio(fs.createReadStream('audio.mp3'))
$.sendVoice(fs.createReadStream('voice.ogg'))
$.sendVideo(fs.createReadStream('video.mp4'))
$.sendSticker(fs.createReadStream('sticker.webp'))



That's all, thanks to everyone who mastered the article, and here is the link to GitHub - github.com/Naltox/telegram-node-bot and NPM: npmjs.com/package/telegram-node-bot

Read Next