Регистрация с помощью telegram бота

в 22:00, , рубрики: api, bot, Go, golang, Разработка веб-сайтов, метки:

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

image
Пример работы бота

Хотелось бы сразу отметить: в статье не будет описана реализация входа на сайт по одноразовым кодам.

Весь код есть в репозитории на GitHub

Оглавление:

  1. Необходимое ПО
  2. Получение API ключа
  3. Структура проекта
  4. Файл настроек
  5. Инициализация бота и соединения с БД
  6. Основной код
  7. Запуск бота

Для начала нужно установить всё необходимое ПО:

Далее следует получить API ключ для нашего будущего бота. Для этого нужно написать BotFather. Что примерно должно получиться:
image

Перед тем как начать программировать нужно определится со структурой проекта, у меня она получилась такой:

/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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js