Сейчас почти на всех сайтах есть регистрация. Реализована она чаще всего с помощью e-mail, реже с помощью смс. А что если сделать регистрацию через telegram бота? В качестве логина на сайте мы сможем использовать подтверждённый номер телефона, а сам бот будет посылать одноразовые коды для входа. В данной статье описан процесс создания такого бота на языке Golang.

Пример работы бота
Хотелось бы сразу отметить: в статье не будет описана реализация входа на сайт по одноразовым кодам.
Весь код есть в репозитории на GitHub
UPD 07.02.2018: Вход на сайт с помощью Telegram теперь можно реализовать используя Telegram Login Widget
Для начала нужно установить всё необходимое ПО:
Далее следует получить API ключ для нашего будущего бота. Для этого нужно написать боту BotFather. Что примерно должно получиться:

Перед тем как начать программировать нужно определится со структурой проекта, у меня она получилась такой:
Приступаем к написанию кода! Для начала файл настроек (settings.go):
Для каждого пользователя в БД хранятся: ид чата (chat_id) и номер мобильного телефона (phone_number). Поэтому сразу создаём структуру User:
Соединение с MongoDB реализуем с помощью структуры DatabaseConnection:
Бота представим в качестве структуры TelegramBot:
Функция инициализации соединения с MongoDB:
Функция инициализации бота:
Бот инициализируется, но делать он ещё ничего не умеет. Давайте двигаться дальше!
Следующий шаг — «основной цикл бота»:
Это бесконечный цикл. Обработка всех входящих сообщений начинается с него. В начале обработки мы должны проверить, есть ли пользователь у нас в базе. Нет — создаём и запрашиваем его номер, есть — продолжаем обработку.
Начальная обработка написана, теперь давайте напишем общение с базой данных:
Это все функции, которые нам пригодятся. Для бота осталось написать функции продолжения обработки сообщения и анализа контакта:
Наш бот готов к работе! Осталось только его запустить. Для этого в main.go пишем:
В итоге у нас получился бот, запрашивающий у пользователя его номер, который в дальнейшем будет использоваться в системе. Бота можно и нужно улучшить, добавить интеграцию с сайтом, вход по одноразовым паролям, и.т.д.
Несомненно у данной системы есть минусы, самый очевидный из которых: зависимость от Telegram (изменение API, блокировка). По мне это хорошая альтернатива e-mail и sms, будете ли вы использовать её — решать вам. Спасибо за внимание!

Пример работы бота
Хотелось бы сразу отметить: в статье не будет описана реализация входа на сайт по одноразовым кодам.
Весь код есть в репозитории на GitHub
UPD 07.02.2018: Вход на сайт с помощью Telegram теперь можно реализовать используя Telegram Login Widget
Оглавление:
- Необходимое ПО
- Получение 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, будете ли вы использовать её — решать вам. Спасибо за внимание!
