[Из песочницы] Ещё один telegram-бот или реализация бота для знакомств
Меня зовут Влад, по роду деятельности я — военный, но подрабатываю в компании автоматизации бизнеса, где и занимаюсь программированием на .net.
Краткая предыстория
В момент очередного схождения-расхождения с моей бывшей (очередной) невестой, она обмолвилась, что от скуки искала себе парней на свидания через telegram-бота, которому можно было отправить фото и геолокацию и тебе подбирало людей, которые находятся поблизости. Я пообещал, что напишу что-то похожее, если мы опять разбежимся. Яра — это для тебя.
Я благополучно забыл про это, но в момент очередного безделья, я скролил сайт с кексом на зелёном фоне, где наткнулся на раздел, где люди просто выкладывали пост с фото, коротко писали о себе и указывали контакты.
Так и появилась идея написать своего бота, который бы помог людям найти друзей или кого-либо ещё.
Задача
Создать бот, где каждый, кто желает пообщаться, мог бы выложить короткие данные о себе и просто ждать, пока ему напишут.
Подготовка к работе
Как создавать бота через BotFather не знает только тот, кому это не интересно, так что наполнять статью лишней информацией я не стану.
Хранение «анкет»
Первоначально я создал проект ClassLibrary, который и собрался использовать для работы с данными.
Сначала нужно решить, как хранить данные пользователей. Для этого нам нужно «описать» юзера.
public class user
{
[Key]
public string tg_id { get; set; }//Уникальный айди пользователя
public string name { get; set; }//Отображаемое имя
public string age { get; set; }// Возраст
public string country { get; set; }// Страна
public string city { get; set; }//Город
public string gender { get; set; }//Пол
public string photo { get; set; }//Ссылка на фото
public string tg_username { get; set; }//Телеграмовский ник-нейм, по которому можно будет перейти к пользователю в личную переписку
public string tg_chat_id { get; set; }//Айди чата, куда отправлять ответ
}
Для хранения была выбрана БД PostgreSQL, которая была развернута на удаленном сервере.
Предварительно, устанавливаем EntityFramework, через NuGet. Это нереально упрощает жизнь в работе с БД.
Для работы требуется пакет:
NpgSQL.EntityFrameworkCore.PostgreSQL
А для миграций требуется пакет:
Microsoft.EntityFrameworkCore.Tools
Чтобы не заниматься рутинным созданием таблицы, просто создаем саму модель данных (наш класс выше) и задаем подключение к БД.
public DbSet user { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(connectionString);
}
В дальнейшем выполняем миграцию.
Для этого в Консоли диспетчера пакетов выполняем команды: enable-migrations
Включаем механизм миграцийAdd-migration *Имя миграции*
Создаем миграциюupdate-database
Обновляем БД
Теперь у нас в БД появятся две таблицы: История миграций и сама таблица user.
После решения вопроса хранения данных и коннекта к БД, можно переходить к написанию самого бота-обработчика.
Бот-обработчик
Сам telegram предлагает два варианта получения обновлений: webhook или постоянно дергать сервера, проверяя обновления. Webhook имеет большое количество сложностей, поэтому проще просто проверять обновления.
Получение обновлений их обработка
Для того, чтобы не создавать велосипед (иногда бывает полезно), проще использовать готовое решение: Telegram.Bot by MrRoundRobin — отличная, очень удобная библиотека для работы с Telegram.
Создаем в решение новый проект ConsoleApp, куда и устанавливаем этот пакет.
private static readonly TelegramBotClient Bot = new TelegramBotClient(token);//Инициализация бота
static void Main(string[] args)
{
var me = Bot.GetMeAsync().Result;//Получаем имя бота, чтобы обозвать окошко консоли(когда ботов несколько, то так проще)
Console.Title = me.Username;
//Создаем обработчики событий
Bot.OnMessage += BotOnMessageReceived;
Bot.OnCallbackQuery += BotOnCallbackQueryReceived;
Bot.OnReceiveError += BotOnReceiveError;
//Начинаем проверять обновления
Bot.StartReceiving(Array.Empty());
Console.WriteLine($"Start listening for @{me.Username}");
Console.ReadLine();
Bot.StopReceiving();
}
Таким образом, мы начали проверку на обновления и поставили свои обработчики ошибок.BotOnMessageReceived
— обработчик получения «обычных» сообщенийBotOnCallbackQueryReceived
— обработчик нажатия кнопок, которые появляются под сообщением.
Дело за малым, возможность оставить анкету и возможность пролистать остальные. Так что нужно отправить пользователю две кнопки: Регистрация и Дальше. Кнопка представляет объект InlineKeyboardButton
, а все кнопки нужно упаковать в
IEnumerable>
При открытии бота, ему сразу же отправляется сообщение с текстом »/start», так что нам нужно в BotOnMessageReceived
обработать это сообщение и отправить в ответ наши кнопки.
if (message.Text == "/start")
{
var inlineKeyboard = new InlineKeyboardMarkup(new[]
{
new [] // first row
{
InlineKeyboardButton.WithCallbackData("Начать!", "Next"),
InlineKeyboardButton.WithCallbackData("Регистрация", "Registration")
}
});
Bot.SendTextMessageAsync(message.Chat.Id, "Добро пожаловать в lovebot! \r\nЧтобы перезапустить бота - /start\r\nЧтобы зарегистроваться или же изменить свою анкету - /register\r\nЧтобы посмотреть количество пользователей бота - /stats\r\n По поводу возникших вопросов - @hahah2016", replyMarkup: inlineKeyboard);
return;
}
Регистрация
Для регистрации, нужно запоминать, что пользователь ввел ранее. То есть нам нужно создать хранилище памяти бота. Я просто создал класс, где описал логику заполнения данных.
public class RegForm
{
public string tg_id { get; set; }
public string name { get; set; }
public string age { get; set; }
public string country { get; set; }
public string city { get; set; }
public string gender { get; set; }
public string photo { get; set; }
public string tg_username { get; set; }
public string tg_chat_id { get; set; }
public int stage;
public RegForm(string id, string chat_id, string username)
{
stage = 1;
tg_id = id;
tg_username = username;
}
public (string, int) StageText(string id)
{
if (stage == 1)
return ("Введите отображаемое имя:", stage);
if (stage == 2)
return ("Введите возраст:", stage);
if (stage == 3)
return ("Введите Вашу страну:", stage);
if (stage == 4)
return ("Введите Ваш город:", stage);
if (stage == 5)
return ("Введите Ваш пол:", stage);
else
return ("Отправьте боту Ваше фото:", stage);
}
public bool SetParam(string param)
{
if (stage == 1)
name = param;
if (stage == 2)
age = param;
if (stage == 3)
country = param;
if (stage == 4)
city = param;
if (stage == 5)
gender = param;
if (stage == 6)
photo = param;
stage++;
return true;
}
}
В данном классе можно реализовать валидацию данных, например, не пропустить возраст в виде текста и т.п.
А самой памятью выступает static Dictionary
, в который мы добавляем новый KeyValuePair, при нажатии на кнопку.
Чтобы бот знал, как ему реагировать на нажатие, нужно в BotOnCallbackQueryReceived
добавить
var message = e.CallbackQuery;
if (message.Data == "Registration")
{
RegForm form = new RegForm(message.From.Id.ToString(), message.Message.Chat.Id.ToString(), message.From.Username);//Создаем новую форму регистрации
registrations.Add(message.From.Id.ToString(), form);//Добавляем форму в "память", где ключом будет telegram_id пользователя.
var t = form.StageText(form.tg_id); //Получаем текст, который отправим пользователю, в зависимости от стадии регистрации.
Bot.SendTextMessageAsync(message.Message.Chat.Id, t.Item1);//отправляем сообщение пользователю.
return;
}
И таким же образом, обрабатывая полученные данные, можно заполнить форму и сохранить данные.
using (Context db = new Context())
{
IMapper mapper = new MapperConfiguration(cfg => cfg.CreateMap()).CreateMapper();
if (db.user.Where(x => x.tg_id == message.From.Id.ToString()).Count() != 0)
db.user.Update(mapper.Map(u));
else
{
db.user.Add(mapper.Map(u));
}
db.SaveChanges();
}
Если пользователь уже имеет анкету, то просто обновим данные.
if (message.Type == MessageType.Photo)
{
string file = Bot.GetFileAsync(message.Photo[message.Photo.Count() - 1].FileId).Result.FilePath;
string filepath = message.From.Id + "." + file.Split('.').Last();
using (FileStream fileStream = new FileStream("C:\\images\\" + filepath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
var st = Bot.DownloadFileAsync(file).Result;
st.Position = 0;
st.CopyTo(fileStream);
}
u.SetParam("C:\\images\\" + filepath);
}
Показ остальных анкет
Для этого просто нужно брать данные из БД и отправлять пользователю.
Для этого пишем простенький метод, который и будет брать данные из БД и возвращать их в удобном формате:
public static User GetRandom()
{
Stopwatch s = new Stopwatch();
s.Start();
User u;
using (Context db = new Context())
{
Random r = new Random();
int count = db.user.Count();
if (count > 1)
count = count - 1;
List users = mapper.Map, List>(db.user.ToList());
u = users.ElementAt(r.Next(0, count));
}
Console.WriteLine("[" + DateTime.Now + "] For finding " + s.ElapsedMilliseconds + " ms");
s = null;
return u;
}
Обработчик нажатия кнопки Next:
if (message.Data == "Next")
{
if (searchForms.Count != 0)
{
searchForms.Remove(message.From.Id.ToString());
}
IMapper mapper = new MapperConfiguration(cfg => cfg.CreateMap()).CreateMapper();
User user = BaseWorker.GetRandom();
SendAnket(user, message.Message.Chat.Id.ToString());//Метод, который создает форматирование в сообщении.
return;
}
Заключение
При всей своей простоте, бот понравился публике.
Меньше чем за сутки, 134 юзера оставили свои анкеты, есть положительные отклики. И без особой рекламы — лишь один пост на сайте, который особо плюсов не набрал.
Боты — это давно забытое старое, которое обрело новую жизнь. Они помогают реально автоматизировать многие процессы и даже искать себе пару в интернете. В обход забитых монетизацией сайтов знакомств.
Спасибо, что дочитали до конца.
Good luck, have fun, dont eat yellow snow.