using SimpleTGBot.Logging; using SimpleTGBot.MemeGen; using Telegram.Bot; using Telegram.Bot.Exceptions; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; namespace SimpleTGBot; internal class TelegramBot { private string token; private Logger logger; private Dictionary dialogs; private TempStorage temp; private HttpClient httpClient; public TelegramBot(string token, Logger logger) { this.token = token; this.logger = logger; dialogs = new Dictionary(); temp = new TempStorage(); httpClient = new HttpClient(); } /// /// Инициализирует и обеспечивает работу бота до нажатия клавиши Esc /// public async Task Run() { var botClient = new TelegramBotClient(token); using CancellationTokenSource cts = new CancellationTokenSource(); ReceiverOptions receiverOptions = new ReceiverOptions() { AllowedUpdates = new [] { UpdateType.Message } }; botClient.StartReceiving( updateHandler: OnMessageReceived, pollingErrorHandler: OnErrorOccured, receiverOptions: receiverOptions, cancellationToken: cts.Token ); try { var me = await botClient.GetMeAsync(cancellationToken: cts.Token); logger.Info($"Бот @{me.Username} запущен.\r\nДля остановки нажмите клавишу Esc..."); } catch (ApiRequestException) { logger.Fatal("Указан неправильный токен"); goto botQuit; } while (Console.ReadKey().Key != ConsoleKey.Escape){} botQuit: cts.Cancel(); temp.Dispose(); } /// /// Обработчик события получения сообщения. /// /// Клиент, который получил сообщение /// Событие, произошедшее в чате. Новое сообщение, голос в опросе, исключение из чата и т. д. /// Служебный токен для работы с многопоточностью async Task OnMessageReceived(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { if (update.Message is not { } message) return; if (message.Chat.Type != ChatType.Private) return; DialogData dialogData; if (!dialogs.ContainsKey(message.Chat.Id)) { dialogData = new DialogData() { state = DialogState.Initial }; dialogs[message.Chat.Id] = dialogData; } else { dialogData = dialogs[message.Chat.Id]; } switch (dialogData.state) { case DialogState.Initial: { bool replied = false; if (message.Photo is { } picture) { replied = true; await DialogHandleDemotivatorPicture(botClient, dialogData, message, picture, cancellationToken); } else if (message.Text is { } messageText) { if (Interactions.IsStartCommand(messageText)) { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.greetingMessage, replyMarkup: Interactions.mainReplyMarkup); replied = true; } else if (Interactions.IsHello(messageText)) { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.backButtonReplyMarkup); dialogData.state = DialogState.AwaitingPicture; replied = true; } else if (Interactions.IsGotoSettings(messageText)) { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.settingsMessage, replyMarkup: Interactions.settingsReplyMarkup); dialogData.state = DialogState.Settings; replied = true; } } if (!replied) { _ = botClient.SendTextMessageAsync(message.Chat.Id, Interactions.sayHelloMessage, replyMarkup: Interactions.mainReplyMarkup); } break; } case DialogState.AwaitingPicture: { if (message.Photo is { } picture) { await DialogHandleDemotivatorPicture(botClient, dialogData, message, picture, cancellationToken); } else { bool reacted = false; if (message.Text is { } messageText) { if (Interactions.IsCancellation(messageText)) { dialogData.state = DialogState.Initial; _ = botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.mainReplyMarkup); reacted = true; } } if (!reacted) _ = botClient.SendTextMessageAsync(message.Chat.Id, Interactions.sendPictureOrQuitMessage, replyMarkup: Interactions.backButtonReplyMarkup); } break; } case DialogState.AwaitingTitle: { if (message.Text is { } messageText) { if (!Interactions.IsCancelButton(messageText)) { await DialogHandleDemotivatorText(botClient, dialogData, message, messageText, cancellationToken); } else { DialogCancelDemotivatorCreation(dialogData); await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.mainReplyMarkup); } } else { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.sendTitleOrQuitMessage, replyMarkup: Interactions.backButtonReplyMarkup); } break; } case DialogState.AwaitingSubtitle: { if (message.Text is { } messageText) { if (!Interactions.IsCancelButton(messageText)) { await DialogHandleDemotivatorSubtitle(botClient, dialogData, message, messageText, cancellationToken); } else { DialogCancelDemotivatorCreation(dialogData); await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.mainReplyMarkup); } } break; } case DialogState.ShowingResult: { bool replied = false; if (message.Text is { } messageText) { if (messageText == Interactions.doneButtonText) { replied = true; DialogFinishDemotivatorCreation(dialogData); await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.mainReplyMarkup); } } if (!replied) { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.chooseActionMessage, replyMarkup: Interactions.resultActionReplyMarkup); } break; } case DialogState.Settings: { bool replied = false; if (message.Text is { } messageText) { if (messageText == Interactions.gotoPresetsButtonText) { replied = true; dialogData.state = DialogState.ViewingPresets; await botClient.SendTextMessageAsync(message.Chat.Id, "<заглушка>", replyMarkup: Interactions.backButtonReplyMarkup); } else if (messageText == Interactions.backButtonText) { replied = true; dialogData.state = DialogState.Initial; await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.mainReplyMarkup); } } if (!replied) { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.chooseActionMessage, replyMarkup: Interactions.settingsReplyMarkup); } break; } case DialogState.ViewingPresets: { bool replied = false; if (message.Text is { } messageText) { if (messageText == Interactions.backButtonText) { replied = true; dialogData.state = DialogState.Settings; await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.settingsMessage, replyMarkup: Interactions.settingsReplyMarkup); } } if (!replied) { await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.chooseActionMessage, replyMarkup: Interactions.settingsReplyMarkup); } break; } } } async Task DialogHandleDemotivatorPicture(ITelegramBotClient botClient, DialogData dialogData, Message message, PhotoSize[] picture, CancellationToken cancellationToken) { string largestSizeId = picture[picture.Length - 1].FileId; Telegram.Bot.Types.File pictureFile = await botClient.GetFileAsync(largestSizeId, cancellationToken); string pictureExtension = pictureFile.FilePath.Substring(pictureFile.FilePath.LastIndexOf('.') + 1); try { using (HttpResponseMessage response = await httpClient.GetAsync(FilePathToUrl(pictureFile.FilePath), cancellationToken)) { response.EnsureSuccessStatusCode(); (string tempFileName, FileStream tempFile) = temp.newTemporaryFile("pic", pictureExtension); await response.Content.CopyToAsync(tempFile); tempFile.Dispose(); logger.Info($"Файл картинки {tempFileName} загружен от пользователя {message.From.FirstName}[{message.From.Id}]"); dialogData.inputPictureFilename = tempFileName; dialogData.state = DialogState.AwaitingTitle; await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingTitleMessage, replyMarkup: Interactions.backButtonReplyMarkup); } } catch (Exception e) { logger.Error("Ошибка при скачивании картинки от пользователя: " + e.GetType().Name + ": " + e.Message); logger.Error(e.StackTrace ?? ""); dialogData.state = DialogState.Initial; await botClient.SendTextMessageAsync(message.Chat.Id, "Ошибка :("); await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.mainReplyMarkup); } } async Task DialogHandleDemotivatorText(ITelegramBotClient botClient, DialogData dialogData, Message message, string text, CancellationToken cancellationToken) { string[] lines = text.Split('\n'); string title; string? subtitle = null; if (lines.Length > 1) { title = lines[0]; subtitle = string.Join(' ', lines.Skip(1)); } else { title = lines.First(); } if (subtitle != null) { logger.Info($"Генерирую простой демотиватор: [\"{title}\", \"{subtitle}\"]"); MemoryStream demotivator = DemotivatorGen.MakePictureDemotivator( dialogData.inputPictureFilename!, [new DemotivatorText() { Title = title, Subtitle = subtitle }], DemotivatorGen.DefaultStyle()); dialogData.state = DialogState.ShowingResult; await botClient.SendPhotoAsync(message.Chat.Id, new InputFile(demotivator, "dem.png"), caption: Interactions.showingResultMessage, replyMarkup: Interactions.resultActionReplyMarkup, cancellationToken: cancellationToken); demotivator.Dispose(); } else { logger.Info($"Пользователь ввёл заголовок: \"{title}\""); dialogData.inputTitle = title; dialogData.state = DialogState.AwaitingSubtitle; await botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingSubtitleMessage, replyMarkup: Interactions.backButtonReplyMarkup, cancellationToken: cancellationToken); } } async Task DialogHandleDemotivatorSubtitle(ITelegramBotClient botClient, DialogData dialogData, Message message, string subtitle, CancellationToken cancellationToken) { subtitle = subtitle != "." ? subtitle.Replace('\n', ' ') : ""; string title = dialogData.inputTitle!; logger.Info($"Генерирую простой демотиватор: [\"{title}\", \"{subtitle}\"]"); MemoryStream demotivator = DemotivatorGen.MakePictureDemotivator( dialogData.inputPictureFilename!, [new DemotivatorText() { Title = title, Subtitle = subtitle }], DemotivatorGen.DefaultStyle()); dialogData.state = DialogState.ShowingResult; await botClient.SendPhotoAsync(message.Chat.Id, new InputFile(demotivator, "dem.png"), caption: Interactions.showingResultMessage, replyMarkup: Interactions.resultActionReplyMarkup, cancellationToken: cancellationToken); demotivator.Dispose(); } void DialogCancelDemotivatorCreation(DialogData dialogData) { dialogData.state = DialogState.Initial; if (dialogData.inputPictureFilename != null) temp.deleteTemporaryFile(dialogData.inputPictureFilename); } void DialogFinishDemotivatorCreation(DialogData dialogData) { dialogData.state = DialogState.Initial; if (dialogData.inputPictureFilename != null) temp.deleteTemporaryFile(dialogData.inputPictureFilename); } /// /// Обработчик исключений, возникших при работе бота /// /// Клиент, для которого возникло исключение /// Возникшее исключение /// Служебный токен для работы с многопоточностью /// Task OnErrorOccured(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken) { var errorMessage = exception switch { ApiRequestException apiRequestException => $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}", _ => exception.ToString() }; logger.Error(errorMessage); return Task.CompletedTask; } private string FilePathToUrl(string filePath) { return $"https://api.telegram.org/file/bot{token}/{filePath}"; } }