add part of the dialog and implement logging to console

This commit is contained in:
Slavasil 2025-04-20 16:41:30 +03:00
parent 252a372477
commit df6268323c
8 changed files with 238 additions and 23 deletions

16
SimpleTGBot/DialogData.cs Normal file
View File

@ -0,0 +1,16 @@
namespace SimpleTGBot;
internal class DialogData
{
public DialogState state;
public string? inputPictureFilename;
}
enum DialogState
{
Initial,
AwaitingPicture,
AwaitingTitle,
AwaitingSubtitle,
ShowingResult,
}

View File

@ -0,0 +1,34 @@
using Telegram.Bot.Types.ReplyMarkups;
namespace SimpleTGBot;
internal static class Interactions
{
public const string awaitingPictureMessage = "Привет. Я - бот, который умеет делать демотиваторы. Присылай картинку, а я скажу, что делать дальше. Или можешь нажать на кнопку в меню.";
public const string sayHelloMessage = "Напиши \"привет\" или нажми на кнопку в меню, чтобы начать.";
public const string sendPictureOrQuitMessage = "Пришли мне картинку для демотиватора, чтобы продолжить. Чтобы отменить, напиши \"назад\" или \"\"";
public static readonly IReplyMarkup initialReplyMarkup = new ReplyKeyboardMarkup([[new KeyboardButton("▶️Начать")]]);
public static readonly IReplyMarkup backButtonReplyMarkup = new ReplyKeyboardMarkup(new KeyboardButton("↩️Назад"));
public static readonly IReplyMarkup quickActionReplyMarkup = new ReplyKeyboardRemove();
static readonly string[] helloWords = ["прив","привет","▶️начать","ку","хай","приветик","превед","привки","хаюхай","здравствуй","здравствуйте","здорово","дарова","дороу","здарова","здорова"];
static readonly string[] cancelWords = ["↩️назад", "назад", "выйти", "отмена", "отменить", "отменяй", "галя", "галина", "стоп"];
public static bool IsStartCommand(string message)
{
return message.Split(' ').FirstOrDefault() == "/start";
}
public static bool IsHello(string message)
{
string[] messageWords = message.ToLower().Split(new char[] { ' ', ',', '.', ';', '(', ')' });
return helloWords.Any(word => messageWords.Contains(word));
}
public static bool IsCancellation(string message)
{
string[] messageWords = message.ToLower().Split(new char[] { ' ', ',', '.', ';', '(', ')' });
return cancelWords.Any(word => messageWords.Contains(word));
}
}

View File

@ -0,0 +1,19 @@
namespace SimpleTGBot.Logging;
internal enum LogLevel : int
{
Debug = 0, Info, Warning, Error, Fatal
}
static class LogLevelExt
{
public static string GetName(this LogLevel level)
{
return level switch {
LogLevel.Debug => "DEBUG",
LogLevel.Info => "INFO",
LogLevel.Warning => "WARN",
LogLevel.Error => "ERROR",
LogLevel.Fatal => "FATAL"
};
}
}

View File

@ -0,0 +1,6 @@
namespace SimpleTGBot.Logging;
internal interface ILogSink : IDisposable
{
public void Log(DateTime time, LogLevel level, string message);
}

View File

@ -0,0 +1,34 @@
namespace SimpleTGBot.Logging;
internal class Logger : IDisposable
{
public List<ILogSink> Sinks;
public Logger(params ILogSink[] sinks)
{
Sinks = new List<ILogSink>(sinks);
}
public void Log(LogLevel level, string message)
{
DateTime now = DateTime.Now;
foreach (var sink in Sinks)
{
sink.Log(now, level, message);
}
}
public void Debug(string message) => Log(LogLevel.Debug, message);
public void Info(string message) => Log(LogLevel.Info, message);
public void Warn(string message) => Log(LogLevel.Warning, message);
public void Error(string message) => Log(LogLevel.Error, message);
public void Fatal(string message) => Log(LogLevel.Fatal, message);
public void Dispose()
{
foreach (var sink in Sinks)
{
sink.Dispose();
}
}
}

View File

@ -0,0 +1,23 @@
namespace SimpleTGBot.Logging;
internal class StdoutSink : ILogSink
{
private readonly ConsoleColor[] colors = [ConsoleColor.White, ConsoleColor.Cyan, ConsoleColor.Yellow, ConsoleColor.DarkRed, ConsoleColor.Red];
private ConsoleColor originalConsoleColor;
public StdoutSink()
{
originalConsoleColor = Console.ForegroundColor;
}
public void Log(DateTime time, LogLevel level, string message)
{
Console.ForegroundColor = colors[(int)level];
foreach (string line in message.Split(Environment.NewLine))
Console.WriteLine($"({time:u}) [{level.GetName()}] {line}");
}
public void Dispose() {
Console.ForegroundColor = originalConsoleColor;
}
}

View File

@ -1,4 +1,5 @@
using System.Text; using System.Text;
using SimpleTGBot.Logging;
namespace SimpleTGBot; namespace SimpleTGBot;
@ -29,8 +30,11 @@ public static class Program
return 1; return 1;
} }
TelegramBot telegramBot = new TelegramBot(botToken); Logger logger = new Logger();
logger.Sinks.Add(new StdoutSink());
TelegramBot telegramBot = new TelegramBot(botToken, logger);
await telegramBot.Run(); await telegramBot.Run();
logger.Dispose();
return 0; return 0;
} }

View File

@ -1,4 +1,5 @@
using SimpleTGBot.MemeGen; using SimpleTGBot.Logging;
using SimpleTGBot.MemeGen;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Exceptions; using Telegram.Bot.Exceptions;
using Telegram.Bot.Polling; using Telegram.Bot.Polling;
@ -7,15 +8,21 @@ using Telegram.Bot.Types.Enums;
namespace SimpleTGBot; namespace SimpleTGBot;
public class TelegramBot internal class TelegramBot
{ {
private string token; private string token;
private Logger logger;
private Dictionary<long, DialogData> dialogs;
private TempStorage temp; private TempStorage temp;
private HttpClient httpClient;
public TelegramBot(string token) public TelegramBot(string token, Logger logger)
{ {
this.token = token; this.token = token;
this.logger = logger;
dialogs = new Dictionary<long, DialogData>();
temp = new TempStorage(); temp = new TempStorage();
httpClient = new HttpClient();
} }
/// <summary> /// <summary>
@ -40,11 +47,11 @@ public class TelegramBot
try try
{ {
var me = await botClient.GetMeAsync(cancellationToken: cts.Token); var me = await botClient.GetMeAsync(cancellationToken: cts.Token);
Console.WriteLine($"Бот @{me.Username} запущен.\nДля остановки нажмите клавишу Esc..."); logger.Info($"Бот @{me.Username} запущен.\r\nДля остановки нажмите клавишу Esc...");
} }
catch (ApiRequestException) catch (ApiRequestException)
{ {
Console.WriteLine("Указан неправильный токен"); logger.Fatal("Указан неправильный токен");
goto botQuit; goto botQuit;
} }
@ -63,27 +70,94 @@ public class TelegramBot
/// <param name="cancellationToken">Служебный токен для работы с многопоточностью</param> /// <param name="cancellationToken">Служебный токен для работы с многопоточностью</param>
async Task OnMessageReceived(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) async Task OnMessageReceived(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{ {
var message = update.Message; if (update.Message is not { } message) return;
if (message is null) if (message.Chat.Type != ChatType.Private) return;
DialogData dialogData;
if (!dialogs.ContainsKey(message.Chat.Id))
{ {
return; dialogData = new DialogData() { state = DialogState.Initial };
} dialogs[message.Chat.Id] = dialogData;
if (message.Text is not { } messageText) } else
{ {
return; dialogData = dialogs[message.Chat.Id];
} }
var chatId = message.Chat.Id;
Console.WriteLine($"Получено сообщение в чате {chatId}: '{messageText}'"); 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) || Interactions.IsHello(messageText))
{
_ = botClient.SendTextMessageAsync(message.Chat.Id, Interactions.awaitingPictureMessage, replyMarkup: Interactions.backButtonReplyMarkup);
dialogData.state = DialogState.AwaitingPicture;
replied = true;
}
}
if (!replied)
{
_ = botClient.SendTextMessageAsync(message.Chat.Id, Interactions.sayHelloMessage, replyMarkup: Interactions.initialReplyMarkup);
}
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.quickActionReplyMarkup);
reacted = true;
}
}
if (!reacted)
_ = botClient.SendTextMessageAsync(message.Chat.Id, Interactions.sendPictureOrQuitMessage, replyMarkup: Interactions.backButtonReplyMarkup);
}
break;
}
}
}
Message sentMessage = await botClient.SendTextMessageAsync( async Task DialogHandleDemotivatorPicture(ITelegramBotClient botClient, DialogData dialogData, Message message, PhotoSize[] picture, CancellationToken cancellationToken)
chatId: chatId, {
text: "Ты написал:\n" + messageText, string largestSizeId = picture[picture.Length - 1].FileId;
cancellationToken: cancellationToken); Telegram.Bot.Types.File pictureFile = await botClient.GetFileAsync(largestSizeId, cancellationToken);
string pictureExtension = pictureFile.FilePath.Substring(pictureFile.FilePath.LastIndexOf('.') + 1);
// грязный тест try
MemoryStream demotivatorPng = DemotivatorGen.MakePictureDemotivator("pic.png", [new DemotivatorText() {Title=messageText, Subtitle=messageText}], DemotivatorGen.DefaultStyle()); {
await botClient.SendPhotoAsync(message.Chat.Id, new InputFile(demotivatorPng, "dem.png")); 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.Close();
logger.Info($"Файл картинки {tempFileName} загружен от пользователя {message.From.FirstName}[{message.From.Id}]");
dialogData.inputPictureFilename = tempFileName;
}
}
catch (Exception e)
{
logger.Error("Ошибка при скачивании картинки от пользователя: " + e.GetType().Name + ": " + e.Message);
logger.Error(e.StackTrace ?? "");
_ = botClient.SendTextMessageAsync(message.Chat.Id, "Ошибка :(");
dialogData.state = DialogState.Initial;
}
} }
/// <summary> /// <summary>
@ -103,8 +177,13 @@ public class TelegramBot
_ => exception.ToString() _ => exception.ToString()
}; };
Console.WriteLine(errorMessage); logger.Error(errorMessage);
return Task.CompletedTask; return Task.CompletedTask;
} }
private string FilePathToUrl(string filePath)
{
return $"https://api.telegram.org/file/bot{token}/{filePath}";
}
} }