📜 ⬆️ ⬇️

Telegram conference bot (start)

Bot


Good day, Habrahabr! In this article I want to share a little freelancing experience in developing chat bots for business. The article will be useful to novice developers, and can also throw a couple of interesting ideas for a more experienced audience.


Further material is designed for people who imagine how to create a simple express server, and also have basic experience with MongoDB.


A few years ago, I and my team of acquaintances faced an interesting order. It was necessary to implement a tool for one IT conference. This service should have been able to collect instant feedback from the audience and share information about the event. As a result of discussions, we came to the creation of Telegram bot. It was the simplest and cheapest solution at the time.


Today we will try to implement something similar, as well as deal with the basic principle of the chatbots.


What should be able to our bot?



We implement the voting system in the next section.


What do we need for the project?



We get everything you need from Telegram


Botfather


First, you need to create a bot on the side of Telegram and get its token.


To do this, we find in the Telegram bot itself called @BotFather.


After a short survey, BotFather will give us a token and a link to the created bot.


Expand base and connect Mongoose


Mlab


You can use the service mlab.com to create a free mongo database.
In the screenshot, I highlighted the place where the token is located.


Note: dbuser and dbpassword need to be replaced with the login data specified when creating the database.


To work with the database, we will use the mongoose.js library.


At the beginning of the main app.js file, connect mongoose with a base through the token received from mlab.


var mongoose = require('mongoose'); mongoose.connect('   ', { useMongoClient: true }); 

Bot-backend


Before we start developing our project, let's see how to create a simple Telgram bot.


To clearly separate the bot, with which the client is working on the Telegram side and its logic of event processing on the side of our server, let's call the server part Bot-backend. The whole essence of the implementation of the bot-backend is to handle the response to the user's messages.


Handlers handlers themselves come in several types. We can respond to a specific text, click a button, or the user sends files. Let's see how this works with an example.


First, let's import the library:


 var TelegramBot = require('node-telegram-bot-api'); 

After that, we will create a concrete implementation of api, sending a token received from the telegram to the constructor.


 var bot = new TelegramBot(telegramToken, { polling: true }); 

Now we have an object on which we can hang our event handlers.


Sending a message to the user


To send a message, we need three things:



 bot.sendMessage(clientId, ', !', messageOptions); 

What are the message options? This is an object that contains settings for displaying a message, as well as a set of buttons. In this case, we say that the message can be parsed as html markup, turn off the preview of the links, if any are contained in the message, and pass a set of buttons. In more detail with the buttons, we will understand further.


 var messageOptions = { parse_mode: "HTML", disable_web_page_preview: false, reply_markup: JSON.stringify({ inline_keyboard: [[{ text: ' ', callback_data: 'do_something' }]] }) } 

Note that callback_data has a limited size. Read more about this in Telegram's documentation for BotApi.


Event handlers


I recommend that all event handlers be placed in a separate directory of handlers.


Consider the basic handler, which is triggered when you first access the bot. At the very beginning of the dialogue with the bot, Telegram automatically sends the message "/ start", which we catch with a regular schedule. After that we get the user's telegram id and send him a response message.


 bot.onText(new RegExp('\/start'), function (message, match) { //  id     var clientId = message.hasOwnProperty('chat') ? message.chat.id : message.from.id; //    bot.sendMessage(clientId, 'Some message', messageOptions); }); 

Below, a different type of handler is implemented, it responds to a button press from those that were sent along with MessageOptions.


 bot.on('callback_query', function (message) { var clientId = message.hasOwnProperty('chat') ? message.chat.id : message.from.id; //      callback_data     message.data if(message.data === 'do_something'){ bot.sendMessage(clientId, 'Button clicked!', messageOptions); } }); 

For convenience, you can make several methods for creating buttons


 var BotUtils = { //  id    getClientIdFromMessage: function (message) { return message.hasOwnProperty('chat') ? message.chat.id : message.from.id; }, //     callback_data buildDefaultButton : function (text, callback_data) { return [{ text: text, callback_data: callback_data }] }, //  -    buildUrlButton : function (text, url) { return [{ text: text, url: url }] }, //    "" buildShareButton: function (text, shareUrl) { return [{ text: text, url: 'https://telegram.me/share/url?url=' + shareUrl }] }, //     buildMessageOptions: function (buttons) { return { parse_mode: "HTML", disable_web_page_preview: false, reply_markup : JSON.stringify({ inline_keyboard: buttons }) } } }; 

Main menu


Hello


After we figured out how to work with the bot, let's implement a more complex logic for our task.


I rendered the handler's registration into a separate class, which takes the bot's register method and the basic set of options.


 var StartHandler = { register: function (bot, messageOptions) { var clientMessage = new RegExp('\/start'); bot.onText(clientMessage, function (message, match) { // Id       getClientIdFromMessage   BotUtils. var clientId = BotUtils.getClientIdFromMessage(message); //      UserService            . bot.saveUser(clientId, function (saveErr, result) { if (saveErr) { bot.sendMessage(clientId, 'Some error! Sorry', messageOptions); return; } //       .       ,     . MessagesService.getByTitle('start', function (getErr, message) { if (getErr) { bot.sendMessage(clientId, 'Some error! Sorry', messageOptions); } else { bot.sendMessage(clientId, message.text, messageOptions); } }); }); }); } }; 

The user will be saved when you first access the bot, we will need this to automatically send private and global messages through the admin area.


Now let's implement the work of all services.


Work with MongoDB


Collections


In the database itself, we need two collections of users and messages . You can create them directly on the mlab website.


Now we describe the basic data models.


The UserModel will contain the telegramId string field. In the future, you can add a name, phone number, a link to the avatar, and other data that will be needed for your tasks.


  var mongoose = require('mongoose'); var Schema = mongoose.Schema; var UserSchema = new Schema({ telegramId: String }); var User = mongoose.model('user', UserSchema); 

MessageModel will contain the title and text of the message. They can be edited from the admin panel. Changing the text does not require intervention in the project itself.


  var mongoose = require('mongoose'); var Schema = mongoose.Schema; var MessageSchema = new Schema({ title : String, text: String }); var Message = mongoose.model('message', MessageSchema); 

Services


Consider saving the user mentioned above. I will make a check method that the user is new (it is not in the database)


 isNew: function (telegramId, callback) { //    ,     ,     true UserModel.findOne({telegramId: telegramId}, function (err, existingUser) { if (err) { callback(err, null); return; } if (existingUser) { callback(null, false); } else { callback(null, true); } }); } 

While saving the user, we use the method implemented above. If the user is new, create a new object and save it to the database.


 saveUser: function (telegramId, callback) { //           // this           UserService this.isNew(telegramId, function (err, result) { if (err) { callback(err, null); return; } if (result) { //          save var newUserDto = new UserModel({ telegramId: telegramId }); newUserDto.save(function (err) { if (err) { callback(err, null); } else { callback(null, true); } }); }else{ callback(null, false); } }); } 

Next we implement saving and receiving messages in the MessageService.


There is nothing difficult. For now, we only need a method for receiving the message by its name. Reacting messages from the admin panel we will do in the next part, while we fill them with pens right in the database.


 var MessagesService = { getByTitle: function (title, callback) { MessageModel.findOne({title: title}, function (err, message) { if (err) { callback(err, null); } else { callback(null, message); } }); } }; 

Express server and admin panel


Admin


Homecontroller


HomeController renders us a page with an input field for sending a global message. On the view we send an array of users to display the list. Later it will be useful to us for sending private messages.


 homeController: function (request, response) { UserService.getAll(function (err, users) { if (err) { return; } response.render('main', {users: users}); }); } 

The view itself (main.ejs) might look something like this:


 <h2> </h2> <form method="POST" action="/globalmessage"> <h3>:</h3> <textarea class="form-control" rows="3" type="text" name="message">" -..."</textarea> <br/><br/> <input align="right" class="btn btn-success" type="submit" value=""> </form> <h2>    :</h2> <ul> <% for(var i=0; i<users.length; i++) {%> <li class="list-group-item list-group-item-info"> <%= users[i].telegramId %> </li> <% } %> </ul> <br/> <a href="/" class="btn btn-success"></a> 

GlobalMessageController


It processes the Post request after clicking the "Send" button, causing the mailing of messages to all users from the database, which we save when we first access the bot.


 globalMessageController: function(request, response) { var message = request.body.message; var bot = this.bot; UserService.getAll(function (err, users) { if (err) { return; } users.forEach(function (user) { bot.sendMessage(user.telegramId, message, {}); }); }); response.redirect('/'); } 

Configuration and Debugging


To run the project, you must have a Telegram token and connection string from the database.
For the development and production version we will create two separate configurations and put them in config.json.


A better solution would be to put them into server environment variables.


  getConfig: function () { var configJson = JSON.parse(fs.readFileSync('./src/config.json', 'utf8')); if(process.env.NODE_ENV === 'production'){ return configJson.production; }else{ return configJson.development; } } 

For debugging, you can use the simple-node-logger library, which will write all actions with Logger.notify () in logfile.log, which can be viewed via ssh or ftp on the server.


 var nodeLogger = require('simple-node-logger'); var Logger = { logger: nodeLogger.createSimpleLogger('logfile.log'), notify: function (data) { this.logger.info(data, new Date().toJSON()); } }; 

Sources


The complete code for the entire project is here: GitHub


You can freely use it as a basis for your projects. I strongly recommend to conduct a large-scale refactorig, as well as rewrite to ES6 or TypeScript.


Your telegram token and mongo connection string must be entered in the file /src/config.json


As a hosting service, I recommend using Heroku. The only inconvenience will be falling asleep server after 30 minutes. To do this, there is an ancient life hacking with pinging (you can google special services for this).


Continuation


In the following parts I will explain how to screw the voting system and the ability to leave applications.


The following part can be found here: Telegram conference bot (Continued)


Thank you for your attention, habrovchane!


')

Source: https://habr.com/ru/post/342560/


All Articles