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

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

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

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

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

UPD 07.02.2018: Вход на сайт с помощью Telegram теперь можно реализовать используя Telegram Login Widget

Оглавление:


  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, будете ли вы использовать её — решать вам. Спасибо за внимание!
Поделиться публикацией

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

    0
    «Если вы не предоставите ваш номер телефона, вы не сможете пользоваться системой!»

    Для чего всё это надо?!
      0
      Когда мы запрашиваем у пользователя номер, мы ожидаем, что следующее его сообщение будет контактом. Но он может отправить просто какой-нибудь текст (например: привет). В этом случае ответ бота будет таким.
        +1
        Они хотят лишиться части (большей) пользователей.
        0
        Данный бот уже запущен и работает где нибудь в вашей системе? Или просто как идею подаете?
          0
          Да, в моём проект данный бот сейчас работает. Он используется для регистрации пользователей и отправки им уведомлений. Проект в альфе.
            0
            Проект доступен с Интернета и обычным пользователям? Можно поиграться?
              0
              Обычным пользователям он к сожалению ещё не доступен, сейчас идёт тестирование среди моих знакомых.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Идея хорошая, а если СМС выходит дорого, то еще и выгодная.
            Но если используете только для регистрации… ну неужто у вас так много регистраций, что СМС на них не окупается?

            Конечно зависит от аудитории, но если у вас не-ИТ сайт, то такая регистрация будет отсеивать неплохую часть пользователей. Да, не все пользуются Телеграммом.

            Собирали статистику, как много людей эта регистрация отпугивает? На какой тематике проекта? Пощелкать можно? :)
              0
              Можно ли вычислить «игру»?
              Наверное, да. Когда возникают некоторые закономерности в логике.
              С телеграммом идёт давняя «игра с привязкой к телефону».
              Кто её ведёт, црушнеГи, кгбешнеГи, сам Дуров?
                0
                Защита от ботов?

                  0
                  $(document).ready(function() { $('#btn1666').hover(function(){ $(this).remove();
                  $.getJSON('
                  

                  Самое примитивное, сам когда то придумал. Имитация мыши у бота есть? Не убрал мышку с экрана, появляется нужная информация, к примеру для регистрации.
                +2
                А зачем я должен указывать на вашем сайте свой телефон?
                Дайте имя бота (например @%ProjectName%_RegBot)
                Я через него зарегистрируюсь (запрошу код подтверждения) и всё.
                  0
                  Описывал не так давно нечто подобное, как раз вход на сайт и регистрация автоматом. https://habrahabr.ru/post/321682/ Как видно из комментариев, не так много поддерживают такую идею
                    0

                    Возможно, из-за излишней сложности. У вас — пароли, тут телефон запрашивают.
                    У телеграма есть deep linking и на сайте для регистрации/аутентификации можно просто давать ссылки с одноразовыми токенами, которые обрабатывать ботом.
                    От OAUTH, в принципе, мало чем отличается.


                    Сам номер телефона, конечно, может быть необходим для подтверждения личности при заказе в онлайн-магазинах, например, но для регистрации в целом — нет.

                    0

                    А что за странное именование файла с логикой бота и полей структуры пользователя? И почему не захотели делать все в одном пакете, учитывая, что всего 4 файла в проекте?

                      0
                      Делал тоже самое и тоже на Go для связи моего бота по учёту долгов @DebtsTrackerRuBot с веб-версией и приложением для мобилок https://debtstracker.io/ru/ — альфа, но можно потыкать.

                      Кстати, если кому интересно делаю фреймворк на Go для ботов под разные платформы: https://github.com/strongo/bots-framework.

                      Пока только Telegram использую, но планирую добавить Viber, WeChat & FB Messenger в ближайшее время. Пулл реквесты приветствуются.
                        0
                        Можно ли сделать бота, который будет за пользователя проходить регистрацию по «старому» формату, вписывая все данные, которые предоставил ему пользователь, останавливаясь только на месте, где именно человек должен ввести информацию?
                        Автоматизировать то, что можно автоматизировать при регистрации.
                          0
                          Вместо цикла, конечно удобнее использовать WebHook. И номер телефона, имхо — лишняя штука здесь. Лучше использовать внутренний идентификатор юзера в Телеграме.
                            0
                            Почему Go?
                              –1
                              Всё просто — я знаком с ним лучше, чем с другими языками

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

                            Самое читаемое