Пример работы бота
Хотелось бы сразу отметить: в статье не будет описана реализация входа на сайт по одноразовым кодам.
Весь код есть в репозитории на GitHub
Оглавление:
- Необходимое ПО
- Получение API ключа
- Структура проекта
- Файл настроек
- Инициализация бота и соединения с БД
- Основной код
- Запуск бота
Для начала нужно установить всё необходимое ПО:
- Golang (я использую версию 1.8)
- Библиотека для работы с API Telegram
- MongoDB
- Библиотека для работы с базой данных
Далее следует получить API ключ для нашего будущего бота. Для этого нужно написать боту BotFather. Что примерно должно получиться:
Перед тем как начать программировать нужно определится со структурой проекта, у меня она получилась такой:
/project/
/conf/
settings.go
/src/
database.go
telegramBot.go
main.go
Приступаем к написанию кода! Для начала файл настроек (settings.go):
const (
TELEGRAM_BOT_API_KEY = "paste your key here" // API ключ, который мы получили у BotFather
MONGODB_CONNECTION_URL = "localhost" // Адрес сервера MongoDB
MONGODB_DATABASE_NAME = "regbot" // Название базы данных
MONGODB_COLLECTION_USERS = "users" // Название таблицы
)
Для каждого пользователя в БД хранятся: ид чата (chat_id) и номер мобильного телефона (phone_number). Поэтому сразу создаём структуру User:
type User struct {
Chat_ID int64
Phone_Number string
}
Соединение с MongoDB реализуем с помощью структуры DatabaseConnection:
type DatabaseConnection struct {
Session *mgo.Session // Соединение с сервером
DB *mgo.Database // Соединение с базой данных
}
Бота представим в качестве структуры TelegramBot:
type TelegramBot struct {
API *tgbotapi.BotAPI // API телеграмма
Updates tgbotapi.UpdatesChannel // Канал обновлений
ActiveContactRequests []int64 // ID чатов, от которых мы ожидаем номер
}
Функция инициализации соединения с MongoDB:
func (connection *DatabaseConnection) Init() {
session, err := mgo.Dial(conf.MONGODB_CONNECTION_URL) // Подключение к серверу
if err != nil {
log.Fatal(err) // При ошибке прерываем выполнение программы
}
connection.Session = session
db := session.DB(conf.MONGODB_DATABASE_NAME) // Подключение к базе данных
connection.DB = db
}
Функция инициализации бота:
func (telegramBot *TelegramBot) Init() {
botAPI, err := tgbotapi.NewBotAPI(conf.TELEGRAM_BOT_API_KEY) // Инициализация API
if err != nil {
log.Fatal(err)
}
telegramBot.API = botAPI
botUpdate := tgbotapi.NewUpdate(0) // Инициализация канала обновлений
botUpdate.Timeout = 64
botUpdates, err := telegramBot.API.GetUpdatesChan(botUpdate)
if err != nil {
log.Fatal(err)
}
telegramBot.Updates = botUpdates
}
Бот инициализируется, но делать он ещё ничего не умеет. Давайте двигаться дальше!
Следующий шаг — «основной цикл бота»:
func (telegramBot *TelegramBot) Start() {
for update := range telegramBot.Updates {
if update.Message != nil {
// Если сообщение есть -> начинаем обработку
telegramBot.analyzeUpdate(update)
}
}
}
Это бесконечный цикл. Обработка всех входящих сообщений начинается с него. В начале обработки мы должны проверить, есть ли пользователь у нас в базе. Нет — создаём и запрашиваем его номер, есть — продолжаем обработку.
// Начало обработки сообщения
func (telegramBot *TelegramBot) analyzeUpdate(update tgbotapi.Update) {
chatID := update.Message.Chat.ID
if telegramBot.findUser(chatID) { // Есть ли пользователь в БД?
telegramBot.analyzeUser(update)
} else {
telegramBot.createUser(User{chatID, ""}) // Создаём пользователя
telegramBot.requestContact(chatID) // Запрашиваем номер
}
}
func (telegramBot *TelegramBot) findUser(chatID int64) bool {
find, err := Connection.Find(chatID)
if err != nil {
msg := tgbotapi.NewMessage(chatID, "Произошла ошибка! Бот может работать неправильно!")
telegramBot.API.Send(msg)
}
return find
}
func (telegramBot *TelegramBot) createUser(user User) {
err := Connection.CreateUser(user)
if err != nil {
msg := tgbotapi.NewMessage(user.Chat_ID, "Произошла ошибка! Бот может работать неправильно!")
telegramBot.API.Send(msg)
}
}
func (telegramBot *TelegramBot) requestContact(chatID int64) {
// Создаём сообщение
requestContactMessage := tgbotapi.NewMessage(chatID, "Согласны ли вы предоставить ваш номер телефона для регистрации в системе?")
// Создаём кнопку отправки контакта
acceptButton := tgbotapi.NewKeyboardButtonContact("Да")
declineButton := tgbotapi.NewKeyboardButton("Нет")
// Создаём клавиатуру
requestContactReplyKeyboard := tgbotapi.NewReplyKeyboard([]tgbotapi.KeyboardButton{acceptButton, declineButton})
requestContactMessage.ReplyMarkup = requestContactReplyKeyboard
telegramBot.API.Send(requestContactMessage) // Отправляем сообщение
telegramBot.addContactRequestID(chatID) // Добавляем ChatID в лист ожидания
}
func (telegramBot *TelegramBot) addContactRequestID(chatID int64) {
telegramBot.ActiveContactRequests = append(telegramBot.ActiveContactRequests, chatID)
}
Начальная обработка написана, теперь давайте напишем общение с базой данных:
var Connection DatabaseConnection // Переменная, через которую бот будет обращаться к БД
// Проверка на существование пользователя
func (connection *DatabaseConnection) Find(chatID int64) (bool, error) {
collection := connection.DB.C(conf.MONGODB_COLLECTION_USERS) // Получаем коллекцию "users"
count, err := collection.Find(bson.M{"chat_id": chatID}).Count() // Считаем количество записей с заданным ChatID
if err != nil || count == 0 {
return false, err
} else {
return true, err
}
}
// Получение пользователя
func (connection *DatabaseConnection) GetUser(chatID int64) (User, error) {
var result User
find, err := connection.Find(chatID) // Сначала проверяем, существует ли он
if err != nil {
return result, err
}
if find { // Если да -> получаем
collection := connection.DB.C(conf.MONGODB_COLLECTION_USERS)
err = collection.Find(bson.M{"chat_id": chatID}).One(&result)
return result, err
} else { // Нет -> возвращаем NotFound
return result, mgo.ErrNotFound
}
}
// Создание пользователя
func (connection *DatabaseConnection) CreateUser(user User) error {
collection := connection.DB.C(conf.MONGODB_COLLECTION_USERS)
err := collection.Insert(user)
return err
}
// Обновление номера мобильного телефона
func (connection *DatabaseConnection) UpdateUser(user User) error {
collection := connection.DB.C(conf.MONGODB_COLLECTION_USERS)
err := collection.Update(bson.M{"chat_id": user.Chat_ID}, &user)
return err
}
Это все функции, которые нам пригодятся. Для бота осталось написать функции продолжения обработки сообщения и анализа контакта:
func (telegramBot *TelegramBot) analyzeUser(update tgbotapi.Update) {
chatID := update.Message.Chat.ID
user, err := Connection.GetUser(chatID) // Вытаскиваем данные из БД для проверки номера
if err != nil {
msg := tgbotapi.NewMessage(chatID, "Произошла ошибка! Бот может работать неправильно!")
telegramBot.API.Send(msg)
return
}
if len(user.Phone_Number) > 0 {
msg := tgbotapi.NewMessage(chatID, "Ваш номер: " + user.Phone_Number) // Если номер у нас уже есть, то пишем его
telegramBot.API.Send(msg)
return
} else {
// Если номера нет, то проверяем ждём ли мы контакт от этого ChatID
if telegramBot.findContactRequestID(chatID) {
telegramBot.checkRequestContactReply(update) // Если да -> проверяем
return
} else {
telegramBot.requestContact(chatID) // Если нет -> запрашиваем его
return
}
}
}
// Проверка принятого контакта
func (telegramBot *TelegramBot) checkRequestContactReply(update tgbotapi.Update) {
if update.Message.Contact != nil { // Проверяем, содержит ли сообщение контакт
if update.Message.Contact.UserID == update.Message.From.ID { // Проверяем действительно ли это контакт отправителя
telegramBot.updateUser(User{update.Message.Chat.ID, update.Message.Contact.PhoneNumber}, update.Message.Chat.ID) // Обновляем номер
telegramBot.deleteContactRequestID(update.Message.Chat.ID) // Удаляем ChatID из списка ожидания
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Спасибо!")
msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(false) // Убираем клавиатуру
telegramBot.API.Send(msg)
} else {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Номер телефона, который вы предоставили, принадлежит не вам!")
telegramBot.API.Send(msg)
telegramBot.requestContact(update.Message.Chat.ID)
}
} else {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Если вы не предоставите ваш номер телефона, вы не сможете пользоваться системой!")
telegramBot.API.Send(msg)
telegramBot.requestContact(update.Message.Chat.ID)
}
}
// Обновление номера мобильного телефона пользователя
func (telegramBot *TelegramBot) updateUser(user User, chatID int64) {
err := Connection.UpdateUser(user)
if err != nil {
msg := tgbotapi.NewMessage(chatID, "Произошла ошибка! Бот может работать неправильно!")
telegramBot.API.Send(msg)
return
}
}
// Есть ChatID в листе ожидания?
func (telegramBot *TelegramBot) findContactRequestID(chatID int64) bool {
for _, v := range telegramBot.ActiveContactRequests {
if v == chatID {
return true
}
}
return false
}
// Удаление ChatID из листа ожидания
func (telegramBot *TelegramBot) deleteContactRequestID(chatID int64) {
for i, v := range telegramBot.ActiveContactRequests {
if v == chatID {
copy(telegramBot.ActiveContactRequests[i:], telegramBot.ActiveContactRequests[i + 1:])
telegramBot.ActiveContactRequests[len(telegramBot.ActiveContactRequests) - 1] = 0
telegramBot.ActiveContactRequests = telegramBot.ActiveContactRequests[:len(telegramBot.ActiveContactRequests) - 1]
}
}
}
Наш бот готов к работе! Осталось только его запустить. Для этого в main.go пишем:
var telegramBot src.TelegramBot
func main() {
src.Connection.Init() // Инициализация соединения с БД
telegramBot.Init() // Инициализация бота
telegramBot.Start()
}
В итоге у нас получился бот, запрашивающий у пользователя его номер, который в дальнейшем будет использоваться в системе. Бота можно и нужно улучшить, добавить интеграцию с сайтом, вход по одноразовым паролям, и.т.д.
Несомненно у данной системы есть минусы, самый очевидный из которых: зависимость от Telegram (изменение API, блокировка). По мне это хорошая альтернатива e-mail и sms, будете ли вы использовать её — решать вам. Спасибо за внимание!
Комментарии (21)
Akuma
27.06.2017 08:47Идея хорошая, а если СМС выходит дорого, то еще и выгодная.
Но если используете только для регистрации… ну неужто у вас так много регистраций, что СМС на них не окупается?
Конечно зависит от аудитории, но если у вас не-ИТ сайт, то такая регистрация будет отсеивать неплохую часть пользователей. Да, не все пользуются Телеграммом.
Собирали статистику, как много людей эта регистрация отпугивает? На какой тематике проекта? Пощелкать можно? :)
frees2
27.06.2017 09:31Можно ли вычислить «игру»?
Наверное, да. Когда возникают некоторые закономерности в логике.
С телеграммом идёт давняя «игра с привязкой к телефону».
Кто её ведёт, црушнеГи, кгбешнеГи, сам Дуров?arvitaly
27.06.2017 17:06Защита от ботов?frees2
27.06.2017 18:36$(document).ready(function() { $('#btn1666').hover(function(){ $(this).remove(); $.getJSON('
Самое примитивное, сам когда то придумал. Имитация мыши у бота есть? Не убрал мышку с экрана, появляется нужная информация, к примеру для регистрации.
mihmig
27.06.2017 09:54+2А зачем я должен указывать на вашем сайте свой телефон?
Дайте имя бота (например @%ProjectName%_RegBot)
Я через него зарегистрируюсь (запрошу код подтверждения) и всё.
x07
27.06.2017 10:04Описывал не так давно нечто подобное, как раз вход на сайт и регистрация автоматом. https://habrahabr.ru/post/321682/ Как видно из комментариев, не так много поддерживают такую идею
gudvinr
27.06.2017 12:28Возможно, из-за излишней сложности. У вас — пароли, тут телефон запрашивают.
У телеграма есть deep linking и на сайте для регистрации/аутентификации можно просто давать ссылки с одноразовыми токенами, которые обрабатывать ботом.
От OAUTH, в принципе, мало чем отличается.
Сам номер телефона, конечно, может быть необходим для подтверждения личности при заказе в онлайн-магазинах, например, но для регистрации в целом — нет.
DorianPeregrim
27.06.2017 13:10А что за странное именование файла с логикой бота и полей структуры пользователя? И почему не захотели делать все в одном пакете, учитывая, что всего 4 файла в проекте?
astec
27.06.2017 15:22Делал тоже самое и тоже на Go для связи моего бота по учёту долгов @DebtsTrackerRuBot с веб-версией и приложением для мобилок https://debtstracker.io/ru/ — альфа, но можно потыкать.
Кстати, если кому интересно делаю фреймворк на Go для ботов под разные платформы: https://github.com/strongo/bots-framework.
Пока только Telegram использую, но планирую добавить Viber, WeChat & FB Messenger в ближайшее время. Пулл реквесты приветствуются.
reinvent
27.06.2017 15:58Можно ли сделать бота, который будет за пользователя проходить регистрацию по «старому» формату, вписывая все данные, которые предоставил ему пользователь, останавливаясь только на месте, где именно человек должен ввести информацию?
Автоматизировать то, что можно автоматизировать при регистрации.
Jokerjar
27.06.2017 16:29Вместо цикла, конечно удобнее использовать WebHook. И номер телефона, имхо — лишняя штука здесь. Лучше использовать внутренний идентификатор юзера в Телеграме.
frees2
Для чего всё это надо?!
De1aY
Когда мы запрашиваем у пользователя номер, мы ожидаем, что следующее его сообщение будет контактом. Но он может отправить просто какой-нибудь текст (например: привет). В этом случае ответ бота будет таким.
dmxrand
Они хотят лишиться части (большей) пользователей.