Открыть список
Как стать автором
Обновить

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

JavaScriptNode.JSСетевые технологииУправление сообществомУправление продажами

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


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 бота.
Спасибо за внимание, хабровчане!

Теги:nodejsjavascripttelegramtelegram botbotsbots api
Хабы: JavaScript Node.JS Сетевые технологии Управление сообществом Управление продажами
Всего голосов 5: ↑5 и ↓0 +5
Просмотры8.3K

Комментарии 0

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

Похожие публикации

JavaScript разработчик
от 180 000 ₽SportrecsМоскваМожно удаленно
Разработчик JavaScript
от 160 000 до 200 000 ₽ТЭК электрониксМоскваМожно удаленно
JavaScript Разработчик (Fullstack)
от 6 000 $FingerprintJSМожно удаленно
Fullstack javascript разработчик (React, Node.js, Nest.js)
от 150 000 до 250 000 ₽COREМоскваМожно удаленно
NodeJS Developer
от 90 000 до 150 000 ₽Hook ProductionМожно удаленно

Лучшие публикации за сутки