Pull to refresh

Telegram бот для мероприятий (Часть 2)

JavaScriptNode.JSNetwork technologiesCommunity managementSales management

Доброго времени суток, Хабрахабр!


Vote


Сегодня мы разберемся как расширить функционал нашего бота. Перейдем сразу к сути...


Чему мы научим бота в этот раз?


  • Получать больше информации по пользователям
  • Делать глобальные опросы и хранить их результаты

В прошлой части мы научили бота:


  • Отправлять расписание мероприятия в виде telegra.ph ссылки.
  • Шарить ссылку на сайт или чат мероприятия.
  • Рассылать уведомления пользователям из админки.

Первая часть статьи тут!


Едим слона по частям


Давайте добавим рассылку массовых сообщений с возможностью получить feedback от пользователя. В данном примере мы сделаем реализацию двух кнопок (Да/Нет).


Задача будет состоять из нескольких частей:


  • Описать модель данных результатов для базы
  • Реализовать сервис для их сохранения и получения
  • Подготовить сообщение с двумя кнопками для голосования
  • Отловить нажатие на кнопку пользователем и сохранить его голос в базу
  • Добавить форму для создания опросов в админке
  • Отобразить результаты голосования в админке

Модель данных


Нам потребуется новая модель данных для результатов голосования


Что мы собираемся хранить:


  • Ид пользователя
  • Текст самого вопроса (Вы можете реализовать сохранение id вопроса)
  • Ответ на вопрос (текст кнопки)
  • Время

var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var VoteSchema = new Schema({
    telegramId: String,
    question: String,
    answer: String,
    time: String
});

var voteModel = mongoose.model('vote', VoteSchema);

Vote


Теперь необходимо реализовать сохранение результатов голосования в базу и сделать проверку на то, что пользователь уже проголосовал.
Сделаем для этого отдельный сервис.


function isNew(telegramId, question, callback) {
    voteModel.findOne({ telegramId: telegramId, question: question }, (err, existingVote) => {
        if (err) {
            callback(err, null);
            return;
        }
        if (existingVote) {
            callback(null, false);
        } else {
            callback(null, true);
        }
    });
}

Сохранение результатов голосования будет выглядеть примерно так:


function saveVote(voteInfo, callback) {
    isNew(voteInfo.telegramId, voteInfo.question, (err, result) => {
        if (err) {
            callback(err, null);
            return;
        }

        if (result) {
            var newVoteDto = new voteModel({
                telegramId: voteInfo.telegramId,
                question: voteInfo.question,
                answer: voteInfo.answer,
                time: voteInfo.time
            });

            newVoteDto.save((err) => {
                if (err) {
                    callback(err, null);
                } else {
                    callback(null, true);
                }
            });

        } else {
            callback(null, false);
        }
    })
}

Обработчики событий


Используем Vote сервис в новом Handlerе для кнопок голосования.
(Более подробно о том как устроены Handlers смотрите в предыдущей части)


Реализуем вспомогательный метод getLastMessageText.
В нем мы достаем текст сообщения, на которое ответил пользователь.


getLastMessageText: function (message) {
    return message.message.text;
}

Перейдем к обработчику:


function addVoteHandler(bot, messageOptions) {
    bot.on('callback_query', (query) => {
        var clientInfo = botUtils.getClientInfo(query);
        var lastMessageText = botUtils.getLastMessageText(query);

        if (query.data === 'yes' || query.data === 'no') {
            var voteInfo = {
                telegramId: clientInfo.telegramId,
                question: lastMessageText,
                answer: query.data,
                time: Date.now().toString()
            };

            voteService.saveVote(voteInfo, (saveErr, _) => {
                if (saveErr) {
                    bot.sendMessage(clientInfo.telegramId, 'Some error! Sorry', messageOptions);
                    return;
                }
                messagesService.getByTitle('thanks', (err, message) => {
                    if (err) {
                        bot.sendMessage(clientInfo.telegramId, 'Some error! Sorry', messageOptions);
                    } else {
                        bot.sendMessage(clientInfo.telegramId, message.text, messageOptions);
                    }
                });
            });
        }
    });
}

Сообщение и кнопки


После того как мы описали регистрирование результатов голосования, сделаем его рассылку из админки.


Начнем с формирования MessageOptions, которые будут содержать две кнопки.
Для этого добавим такой метод в BotUtils:
(В прошлой части можно найти краткий рассказ о том что такое MessageOptions и callback_data)


function buildMessageOptionsForVoting() {
    return {
        parse_mode: "HTML",
        disable_web_page_preview: false,
        reply_markup: JSON.stringify({
            inline_keyboard: [
                [{ text: 'Да', callback_data: 'yes' }, { text: 'Нет', callback_data: 'no' }]
            ]
        })
    };
}

Новый контроллер


Добавим форму на страницу админки:


<h2>Запустить голосование</h2>
<form method="POST" action="/voting">
    <h3>Сообщение:</h3>
    <textarea class="form-control" rows="3" type="text" name="message">"Напишите что-нибудь..."</textarea>
    <input align="right" class="btn btn-success" type="submit" value="Отправить">
</form>

В этом примере реализован только один базовый шаблон для голосования. В дальнейшем, можно
сделать специальный конструктор голосования в админке, добавив возможноть создавать другие варианты ответа. Эту возможность мы реализуем в следующей части, пока ограничимся простыми да и нет.


function votingController(request, response) {
    var message = request.body.message;

    userService.getAll((err, users) => {
        if (err) {
            console.log(err.message);
            return;
        }

        var messageOptionsForOptions = botUtils.buildMessageOptionsForVoting();

        users.forEach((user) => {
            bot.sendMessage(user.telegramId, message, messageOptionsForOptions);
        });
    });

    response.redirect('/');
}

Результаты голосования


Выведем результаты голосования на админку. Пока сделаем это в виде списка, дальше реализуем счетчики голосов, для большей наглядности.


В HomeControllerе достанем все результаты из базы:


function homeController(_, response) {
    userService.getAll((getUsersErr, users) => {
        if (getUsersErr) {
            console.log(getUsersErr.message);
            return;
        }

        voteService.getAll((getVotesErr, votes) => {
            if (getVotesErr) {
                console.log(getVotesErr.message);
                return;
            }

            response.render('main', { users: users, votes: votes });
        });
    });
}

Добавим список результатов на вьюшку:


<h2>Результаты голосования:</h2>
<ul>
    <% for(var i=0; i<votes.length; i++) {%>
    <li class="list-group-item list-group-item-info">
        <%= votes[i].telegramId %>
        <%= votes[i].question %>
        <%= votes[i].answer %>
    </li>
    <% } %>
</ul>

Больше данных о пользователе


Чтобы добавить к своей рассылке обращение по имени, можно расширить получаемую информацию о пользователе. (При первом обращении пользователя к боту)
Для этого можно сделать вот такой метод. Не забудьте добавить новые поля firstName и lastName к модели данных UserModel.


function getClientInfo(message) {
    return {
        firstName: message.from.first_name,
        lastName: message.from.last_name,
        telegramId: message.hasOwnProperty('chat') ? message.chat.id : message.from.id
    };
}

Исходники


Полный код всего проекта лежит тут!


Напомню, telegram token и mongo connection string необходимо прописать в файле /src/config.json


Продолжение


Если появятся интересные предложения, я попробую реализовать что-то новое у нашего Telegram бота.
Спасибо за внимание, хабровчане!

Tags:nodejsjavascripttelegramtelegram botbotsbots api
Hubs: JavaScript Node.JS Network technologies Community management Sales management
Rating +5
Views 8.2k Add to bookmarks 51
Comments
Leave a comment

Popular right now

JavaScript/Node.js
from 80,000 to 120,000 ₽SipuniRemote job
Senior Javascript/Node.JS Разработчик
from 2,500 to 3,300 $MakeomaticRemote job
Full-Stack JavaScript developer
from 5,500 to 6,500 $LuminatiRemote job
JavaScript Software Engineer
from 3,000 to 5,500 $XPOWERОдессаRemote job
JavaScript Разработчик (Fullstack)
from 6,000 $FingerprintJSRemote job