Сейчас почти на всех сайтах есть регистрация. Реализована она чаще всего с помощью e-mail, реже с помощью смс. А что если сделать регистрацию через telegram бота? В качестве логина на сайте мы сможем использовать подтверждённый номер телефона, а сам бот будет посылать одноразовые коды для входа. В данной статье описан процесс создания такого бота на языке Golang.
Пример работы бота
Хотелось бы сразу отметить: в статье не будет описана реализация входа на сайт по одноразовым кодам.
Весь код есть в репозитории на 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, будете ли вы использовать её — решать вам. Спасибо за внимание!
Автор: De1aY