/
anton.bessolitsyn
/
easyMessageHandlerBot
Обзор
Документация
Войти
/
anton.bessolitsyn
/
easyMessageHandlerBot
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
Core/TgMessageHandler.cs
800 строк
32 KB
Anton Bessolitsyn
refactoring
04 апр 2026, 09:37
04 апр 2026, 09:37
1ea1956
Код
Авторство
О чём код?
using easyMessageHandlerBot.Interfaces; using Microsoft.Extensions.Logging; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; namespace easyMessageHandlerBot.Core { internal class TgMessageHandler { private readonly ITelegramBotClient _botClient; private readonly ILogger<TelegramBotWorker> _logger; //Флаг готовности что все сообщени которые не команды будут обработаны и переданы получателю // команда /start переводит в true /stop в false private bool _screaming = false; private readonly IMessageRecipientService<ITgMessageDto> _messageRecipientService; //private readonly IConfiguration _configuration; // Словарь команд и их обработчиков private readonly Dictionary<string, Func<Message, CancellationToken, Task>> _commandHandlers; // Состояния пользователей (для диалогов) private readonly Dictionary<long, UserState> _userStates = new(); public TgMessageHandler( ITelegramBotClient botClient, IMessageRecipientService<ITgMessageDto> messageRecipientService, ILogger<TelegramBotWorker> logger) //IConfiguration configuration) { _botClient = botClient; _logger = logger; _messageRecipientService = messageRecipientService; //_configuration = configuration; // Инициализация обработчиков команд _commandHandlers = new Dictionary<string, Func<Message, CancellationToken, Task>> { { "/start", HandleStartCommand }, { "/stop", HandleStopCommand } //{ "/help", HandleHelpCommand }, //{ "/menu", HandleMenuCommand }, //{ "/settings", HandleSettingsCommand }, //{ "/cancel", HandleCancelCommand } }; } // Основной метод обработки входящих сообщений public async Task HandleInputMessageAsync(Update update, CancellationToken cancellationToken) { try { _logger.LogInformation($"Received update type: {update.Type}"); switch (update.Type) { case UpdateType.Message: await HandleMessageAsync(update.Message!, cancellationToken); break; //case UpdateType.CallbackQuery: // await HandleCallbackQuery(update.CallbackQuery!, cancellationToken); // break; //case UpdateType.InlineQuery: // await HandleInlineQuery(update.InlineQuery!, cancellationToken); // break; //case UpdateType.MyChatMember: // await HandleChatMemberUpdate(update.MyChatMember!, cancellationToken); // break; default: _logger.LogDebug($"Unhandled update type: {update.Type}"); break; } } catch (Exception ex) { _logger.LogError(ex, "Error handling update"); await HandleErrorAsync(ex, cancellationToken); } } // Обработка текстовых сообщений private async Task HandleMessageAsync(Message message, CancellationToken cancellationToken) { //await SendMessageAsync( // message.Chat.Id, // $"Cообщение {message.Id} в обработке ", // //replyMarkup: keyboard, // replyMarkup: null, // cancellationToken: cancellationToken //); //_logger.LogInformation($"Message from {message.From?.Username} (ID: {message.Chat.Id}): {message.Text}"); // Проверяем тип сообщения if (message.Type == MessageType.Text) { await HandleTextMessage(message, cancellationToken); } else if (message.Type == MessageType.Photo) //if (message.Image != Null ) { await HandlePhotoMessageAsync(message, cancellationToken); } //else if (message.Type == MessageType.Document) //{ // await HandleDocumentMessage(message, cancellationToken); //} else { _logger.LogInformation($"Message from {message.From?.Username} has unknown message type"); } // Добавьте обработку других типов по необходимости } // Обработка текстовых сообщений private async Task HandleTextMessage(Message message, CancellationToken cancellationToken) { var text = message.Text?.Trim(); var chatId = message.Chat.Id; if (string.IsNullOrEmpty(text)) return; // Проверяем, является ли сообщение командой if (text.StartsWith("/")) { await HandleCommandAsync(message, text, cancellationToken); return; } else if (!_screaming) await HandleBeforeStartAnyMessage(message, cancellationToken); return; //TODO: DONE зачем Проверяем состояние пользователя ? //Проверка состояния пользователя при получении сообщения — это ключевой механизм для создания интерактивных диалогов в ботах.Вот зачем это нужно: //if (_userStates.TryGetValue(chatId, out var state) && state.IsWaitingForInput) //{ // await ProcessUserStateInput(chatId, text, message, cancellationToken); // return; //} // Обработка обычного текста await HandleRegularText(message, text, cancellationToken); } // Обработка команд private async Task HandleCommandAsync(Message message, string text, CancellationToken cancellationToken) { // Извлекаем команду (убираем параметры) var commandParts = text.Split(' ', StringSplitOptions.RemoveEmptyEntries); var command = commandParts[0].ToLower(); _logger.LogInformation($"Command received: {command} from {message.From?.Username}"); // Ищем обработчик команды if (_commandHandlers.TryGetValue(command, out var handler)) { await handler(message, cancellationToken); } else { await SendMessageAsync( message.Chat.Id, "❌ Неизвестная команда.", cancellationToken: cancellationToken ); } } #region ДругиеОбработчики // Обработка callback запросов от inline кнопок //private async Task HandleCallbackQuery(CallbackQuery callbackQuery, CancellationToken cancellationToken) //{ // var data = callbackQuery.Data; // var chatId = callbackQuery.Message?.Chat.Id ?? callbackQuery.From.Id; // _logger.LogInformation($"Callback query: {data} from {callbackQuery.From.Username}"); // // Обязательно отвечаем на callback query // await _botClient.AnswerCallbackQueryAsync( // callbackQuery.Id, // cancellationToken: cancellationToken // ); // // Разбираем данные callback // var callbackData = ParseCallbackData(data); // switch (callbackData.Action) // { // case "menu": // await ShowMenuAsync(chatId, callbackData.Parameter, cancellationToken); // break; // case "confirm": // await ProcessConfirmationAsync(chatId, callbackData.Parameter, callbackQuery, cancellationToken); // break; // case "cancel": // await ProcessCancellationAsync(chatId, callbackQuery, cancellationToken); // break; // default: // await SendMessageAsync( // chatId, // $"Вы выбрали: {data}", // cancellationToken: cancellationToken // ); // break; // } //} //// Пример обработки inline запросов //private async Task HandleInlineQuery(InlineQuery inlineQuery, CancellationToken cancellationToken) //{ // _logger.LogInformation($"Inline query from {inlineQuery.From.Username}: {inlineQuery.Query}"); // var results = new List<InlineQueryResult>(); // if (!string.IsNullOrEmpty(inlineQuery.Query)) // { // // Пример результата inline запроса // results.Add(new InlineQueryResultArticle( // id: "1", // title: "Пример ответа", // inputMessageContent: new InputTextMessageContent($"Вы искали: {inlineQuery.Query}") // )); // } // await _botClient.AnswerInlineQueryAsync( // inlineQuery.Id, // results, // cacheTime: 0, // isPersonal: true, // cancellationToken: cancellationToken // ); //} // Обработка обновлений статуса бота в чате //private async Task HandleChatMemberUpdate(ChatMemberUpdated chatMemberUpdated, CancellationToken cancellationToken) //{ // var chat = chatMemberUpdated.Chat; // var newStatus = chatMemberUpdated.NewChatMember.Status; // var oldStatus = chatMemberUpdated.OldChatMember?.Status; // _logger.LogInformation($"Chat member updated in {chat.Title}: {oldStatus} -> {newStatus}"); // if (newStatus == ChatMemberStatus.Kicked) // { // // Бота удалили из чата // _logger.LogWarning($"Bot was removed from chat: {chat.Title} ({chat.Id})"); // // Можно очистить данные пользователя/чата // _userStates.Remove(chat.Id); // } // else if (newStatus == ChatMemberStatus.Administrator) // { // // Бота сделали администратором // await SendMessageAsync( // chat.Id, // "✅ Спасибо за права администратора! Теперь я могу полноценно работать.", // cancellationToken: cancellationToken // ); // } //} #endregion // Обработка ошибок public async Task HandleErrorAsync(Exception exception, CancellationToken cancellationToken) { _logger.LogError(exception, "Telegram bot error"); // TODO Можно отправить уведомление администратору var adminId = ""; if (!string.IsNullOrEmpty(adminId) && long.TryParse(adminId, out long adminChatId)) { try { await SendMessageAsync( adminChatId, $"❌ Ошибка бота:\n{exception.Message}\n\n{exception.StackTrace?.Substring(0, Math.Min(500, exception.StackTrace.Length))}", cancellationToken: cancellationToken ); } catch { /* Игнорируем ошибки при отправке администратору */ } } } private async Task HandleStartCommand(Message message, CancellationToken cancellationToken) { _screaming = true; var userName = message.From?.FirstName ?? "Пользователь"; var welcomeText = $""" 👋 Привет, {userName}! Теперь просто отправьте мне сообщение, и я сохраню его в хранилище Введите команду /start и просто отправьте мне сообщение, и я сохраню его в хранилище! Можете отправить еще сообщение. Введите команду /stop - я перестану сохранять сообщения. """; // // Создаем кнопки для быстрого доступа // var keyboard = new ReplyKeyboardMarkup(new[] // { // new[] // { // new KeyboardButton("📋 Помощь"), // new KeyboardButton("⚙️ Настройки") // }, // new[] // { // new KeyboardButton("🌐 Сайт") { Url = "https://example.com" }, // new KeyboardButton("📞 Контакты") // } //}) // { // ResizeKeyboard = true, // OneTimeKeyboard = false // }; await SendMessageAsync( message.Chat.Id, welcomeText, //replyMarkup: keyboard, replyMarkup: null, cancellationToken: cancellationToken ); } private async Task HandleBeforeStartAnyMessage(Message message, CancellationToken cancellationToken) { _screaming = true; var userName = message.From?.FirstName ?? "Пользователь"; var welcomeText = $""" 👋 Привет, {userName}! Я - ваш Telegram бот. Вот что я умею: 📋 **Основные команды:** /start - Начало работы /stop - Конец работы 💡 **Примеры использования:** Введите команду /start и просто отправьте мне сообщение, и я сохраню его в хранилище! Можете отправить еще сообщение. Введите команду /stop - я перестану сохранять сообщения. """; // // Создаем кнопки для быстрого доступа // var keyboard = new ReplyKeyboardMarkup(new[] // { // new[] // { // new KeyboardButton("📋 Помощь"), // new KeyboardButton("⚙️ Настройки") // }, // new[] // { // new KeyboardButton("🌐 Сайт") { Url = "https://example.com" }, // new KeyboardButton("📞 Контакты") // } //}) // { // ResizeKeyboard = true, // OneTimeKeyboard = false // }; await SendMessageAsync( message.Chat.Id, welcomeText, //replyMarkup: keyboard, replyMarkup: null, cancellationToken: cancellationToken ); } private async Task HandleStopCommand(Message message, CancellationToken cancellationToken) { _screaming = false; var userName = message.From?.FirstName ?? "Пользователь"; var welcomeText = "Теперь сообщения не будут сохраняться"; //TODO Stop command await SendMessageAsync( message.Chat.Id, welcomeText, //replyMarkup: keyboard, replyMarkup: null, cancellationToken: cancellationToken ); } // TODO: HandlePhotoMessageAsync private async Task HandlePhotoMessageAsync(Message message, CancellationToken cancellationToken) { //var photo = message.Image!.Last(); // Берем самое качественное фото //var caption = message.Caption ?? "Фото без описания"; //await SendMessageAsync( // message.Chat.Id, // $"📸 Получено фото!\n\n" + // $"Размер: {photo.FileSize / 1024} KB\n" + // $"Описание: {caption}\n\n" + // $"Обрабатываю фото...", // cancellationToken: cancellationToken //); //// Можно скачать фото //// var file = await _botClient.GetFileAsync(photo.FileId, cancellationToken); //// var filePath = Path.Combine("photos", $"{photo.FileId}.jpg"); //// await using var saveImageStream = System.IO.File.OpenWrite(filePath); //// await _botClient.DownloadFileAsync(file.FilePath!, saveImageStream, cancellationToken); if (_screaming) { try { var dtoOfMessage = await TgMessageConverter.GetTgMessageDtoAsync(message, _botClient); await _messageRecipientService.ProcessPhotoMessageAsync(dtoOfMessage); await SendMessageAsync( message.Chat.Id, $"Cообщение {message.Id} обработано успешно", //replyMarkup: keyboard, replyMarkup: null, cancellationToken: cancellationToken); } catch (Exception ex) { await HandleErrorAsync(ex, cancellationToken); } } } private async Task HandleRegularText(Message message, string text, CancellationToken cancellationToken) { // Простая обработка текста var response = text.ToLower() switch { "привет" => "И тебе привет! 👋", "как дела" => "Всё отлично, спасибо! А у вас?", "спасибо" => "Пожалуйста! Обращайтесь ещё 😊", var t when t.Contains("погода") => "Погоду лучше смотреть в специальных приложениях 🌤", var t when t.Contains("время") => $"Сейчас: {DateTime.Now:HH:mm:ss} ⏰", _ => $"Вы написали: {text}\n\nПопробуйте использовать команды или меню для большего функционала." }; await SendMessageAsync( message.Chat.Id, response, cancellationToken: cancellationToken ); } // Вспомогательный метод для отправки сообщений private async Task SendMessageAsync( long chatId, string text, IReplyMarkup? replyMarkup = null, ParseMode parseMode = ParseMode.Markdown, CancellationToken cancellationToken = default) { try { await _botClient.SendTextMessageAsync( chatId: chatId, text: text, parseMode: parseMode, replyMarkup: replyMarkup, cancellationToken: cancellationToken ); _logger.LogDebug($"Message sent to chat {chatId}"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to send message to chat {chatId}"); throw; } } #region ========== ОБРАБОТЧИКИ КОМАНД ========== НЕ ИСПОЛЬЗУЮТСЯ private async Task HandleHelpCommand(Message message, CancellationToken cancellationToken) { var helpText = $""" 📚 **Справка по командам** 🎯 **Основные команды:** /start - Начать работу с ботом /help - Показать эту справку /menu - Открыть главное меню /cancel - Отменить текущее действие 📊 **Команды для работы:** /status - Статус системы /stats - Статистика использования /feedback - Оставить отзыв ⚙️ **Настройки:** /settings - Настройки бота /language - Изменить язык /notifications - Управление уведомлениями 👨💻 **Для администраторов:** /admin - Панель администратора /logs - Просмотр логов /broadcast - Рассылка сообщений 💡 **Советы:** • Используйте кнопки для быстрого доступа • Отправляйте команды без лишних символов • Для отмены любого действия используйте /cancel Версия бота: _configuration["TelegramBot:Version"] ?? "1.0.0" """; await SendMessageAsync( message.Chat.Id, helpText, cancellationToken: cancellationToken ); } private async Task HandleMenuCommand(Message message, CancellationToken cancellationToken) { await ShowMenuAsync(message.Chat.Id, "main", cancellationToken); } private async Task HandleSettingsCommand(Message message, CancellationToken cancellationToken) { var settingsText = """ ⚙️ **Настройки бота** Выберите настройку для изменения: """; var inlineKeyboard = new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("🌐 Язык", "settings:language"), InlineKeyboardButton.WithCallbackData("🔔 Уведомления", "settings:notifications") }, new[] { InlineKeyboardButton.WithCallbackData("🔐 Конфиденциальность", "settings:privacy"), InlineKeyboardButton.WithCallbackData("🗑 Очистить данные", "settings:clear_data") }, new[] { InlineKeyboardButton.WithCallbackData("↩️ Назад в меню", "menu:main") } }); await SendMessageAsync( message.Chat.Id, settingsText, replyMarkup: inlineKeyboard, cancellationToken: cancellationToken ); } private async Task HandleCancelCommand(Message message, CancellationToken cancellationToken) { var chatId = message.Chat.Id; // Сбрасываем состояние пользователя if (_userStates.ContainsKey(chatId)) { _userStates.Remove(chatId); } await SendMessageAsync( chatId, "✅ Текущее действие отменено. Вы можете начать заново.", cancellationToken: cancellationToken ); } #endregion #region ========== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ========== НЕ ИСПОЛЬЗУЮТСЯ private async Task ShowMenuAsync(long chatId, string menuType, CancellationToken cancellationToken) { var menuText = "📱 **Главное меню**\n\nВыберите раздел:"; var inlineKeyboard = new InlineKeyboardMarkup(new[] { new[] { InlineKeyboardButton.WithCallbackData("📊 Статистика", "menu:stats"), InlineKeyboardButton.WithCallbackData("📝 Заметки", "menu:notes") }, new[] { InlineKeyboardButton.WithCallbackData("📅 Календарь", "menu:calendar"), InlineKeyboardButton.WithCallbackData("🎯 Задачи", "menu:tasks") }, new[] { InlineKeyboardButton.WithCallbackData("⚙️ Настройки", "menu:settings"), InlineKeyboardButton.WithCallbackData("❓ Помощь", "menu:help") }, new[] { InlineKeyboardButton.WithUrl("🌐 Наш сайт", "https://example.com") } }); await SendMessageAsync( chatId, menuText, replyMarkup: inlineKeyboard, cancellationToken: cancellationToken ); } private async Task HandleDocumentMessage(Message message, CancellationToken cancellationToken) { var document = message.Document!; await SendMessageAsync( message.Chat.Id, $"📄 Получен документ: {document.FileName}\n" + $"Тип: {document.MimeType}\n" + $"Размер: {document.FileSize / 1024} KB", cancellationToken: cancellationToken ); } private async Task ProcessUserStateInput(long chatId, string text, Message message, CancellationToken cancellationToken) { var state = _userStates[chatId]; switch (state.StateType) { case UserStateType.WaitingForName: await ProcessNameInput(chatId, text, message, cancellationToken); break; case UserStateType.WaitingForEmail: await ProcessEmailInput(chatId, text, message, cancellationToken); break; default: await SendMessageAsync( chatId, "Неизвестное состояние. Используйте /cancel для сброса.", cancellationToken: cancellationToken ); break; } } private async Task ProcessNameInput(long chatId, string name, Message message, CancellationToken cancellationToken) { // Сохраняем имя _userStates[chatId].Data["name"] = name; // Переходим к следующему шагу _userStates[chatId] = new UserState { StateType = UserStateType.WaitingForEmail, IsWaitingForInput = true }; await SendMessageAsync( chatId, $"Отлично, {name}! Теперь введите ваш email:", cancellationToken: cancellationToken ); } private async Task ProcessEmailInput(long chatId, string email, Message message, CancellationToken cancellationToken) { // Валидация email (простая проверка) if (!email.Contains("@")) { await SendMessageAsync( chatId, "❌ Неверный формат email. Попробуйте еще раз:", cancellationToken: cancellationToken ); return; } // Сохраняем email var state = _userStates[chatId]; state.Data["email"] = email; // Завершаем процесс var name = state.Data["name"]; await SendMessageAsync( chatId, $"✅ Регистрация завершена!\n\n" + $"Имя: {name}\n" + $"Email: {email}\n\n" + $"Данные сохранены. Спасибо!", cancellationToken: cancellationToken ); // Очищаем состояние _userStates.Remove(chatId); } // Парсинг callback данных private (string Action, string Parameter) ParseCallbackData(string data) { if (string.IsNullOrEmpty(data)) return ("unknown", ""); var parts = data.Split(':', 2); return parts.Length switch { 1 => (parts[0], ""), 2 => (parts[0], parts[1]), _ => ("unknown", "") }; } // Обработка подтверждений private async Task ProcessConfirmationAsync(long chatId, string parameter, CallbackQuery callbackQuery, CancellationToken cancellationToken) { await SendMessageAsync( chatId, $"✅ Действие '{parameter}' подтверждено!", cancellationToken: cancellationToken ); // Можно обновить сообщение с кнопками if (callbackQuery.Message != null) { await _botClient.EditMessageTextAsync( chatId: chatId, messageId: callbackQuery.Message.MessageId, text: $"✅ {callbackQuery.Message.Text}\n\nПодтверждено: {DateTime.Now:HH:mm:ss}", cancellationToken: cancellationToken ); } } // Обработка отмен private async Task ProcessCancellationAsync(long chatId, CallbackQuery callbackQuery, CancellationToken cancellationToken) { await SendMessageAsync( chatId, "❌ Действие отменено.", cancellationToken: cancellationToken ); if (callbackQuery.Message != null) { await _botClient.EditMessageTextAsync( chatId: chatId, messageId: callbackQuery.Message.MessageId, text: $"{callbackQuery.Message.Text}\n\n❌ Отменено", cancellationToken: cancellationToken ); } } #endregion } // ========== ВСПОМОГАТЕЛЬНЫЕ КЛАССЫ ========== public class UserState { public UserStateType StateType { get; set; } public bool IsWaitingForInput { get; set; } public Dictionary<string, string> Data { get; set; } = new(); } public enum UserStateType { None, WaitingForName, WaitingForEmail, WaitingForConfirmation } }