- PVSM.RU - https://www.pvsm.ru -
Меня зовут Влад, по роду деятельности я — военный, но подрабатываю в компании автоматизации бизнеса, где и занимаюсь программированием на .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> 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 [1] — отличная, очень удобная библиотека для работы с 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<UpdateType>());
Console.WriteLine($"Start listening for @{me.Username}");
Console.ReadLine();
Bot.StopReceiving();
}
Таким образом, мы начали проверку на обновления и поставили свои обработчики ошибок.
BotOnMessageReceived — обработчик получения «обычных» сообщений
BotOnCallbackQueryReceived — обработчик нажатия кнопок, которые появляются под сообщением.
Дело за малым, возможность оставить анкету и возможность пролистать остальные. Так что нужно отправить пользователю две кнопки: Регистрация и Дальше. Кнопка представляет объект InlineKeyboardButton, а все кнопки нужно упаковать в
IEnumerable<IEnumerable<InlineKeyboardButton>>
При открытии бота, ему сразу же отправляется сообщение с текстом "/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! rnЧтобы перезапустить бота - /startrnЧтобы зарегистроваться или же изменить свою анкету - /registerrnЧтобы посмотреть количество пользователей бота - /statsrn По поводу возникших вопросов - @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<string, RegForm> registrations = new Dictionary<string, RegForm>();, в который мы добавляем новый 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<RegForm, User>()).CreateMapper();
if (db.user.Where(x => x.tg_id == message.From.Id.ToString()).Count() != 0)
db.user.Update(mapper.Map<RegForm, tgbot_base.classes.user>(u));
else
{
db.user.Add(mapper.Map<RegForm, tgbot_base.classes.user>(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<User> users = mapper.Map<List<tgbot_base.classes.user>, List<User>>(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<RegForm, User>()).CreateMapper();
User user = BaseWorker.GetRandom();
SendAnket(user, message.Message.Chat.Id.ToString());//Метод, который создает форматирование в сообщении.
return;
}
При всей своей простоте, бот понравился публике.
Меньше чем за сутки, 134 юзера оставили свои анкеты, есть положительные отклики. И без особой рекламы — лишь один пост на сайте, который особо плюсов не набрал.
Боты — это давно забытое старое, которое обрело новую жизнь. Они помогают реально автоматизировать многие процессы и даже искать себе пару в интернете. В обход забитых монетизацией сайтов знакомств.
Спасибо, что дочитали до конца.
Good luck, have fun, dont eat yellow snow.
Автор: tweekaz
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/299514
Ссылки в тексте:
[1] Telegram.Bot by MrRoundRobin: https://github.com/TelegramBots/Telegram.Bot
[2] Источник: https://habr.com/post/430314/?utm_source=habrahabr&utm_medium=rss&utm_campaign=430314
Нажмите здесь для печати.