add part of the dialog and implement logging to console
This commit is contained in:
parent
252a372477
commit
df6268323c
16
SimpleTGBot/DialogData.cs
Normal file
16
SimpleTGBot/DialogData.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace SimpleTGBot;
|
||||||
|
|
||||||
|
internal class DialogData
|
||||||
|
{
|
||||||
|
public DialogState state;
|
||||||
|
public string? inputPictureFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DialogState
|
||||||
|
{
|
||||||
|
Initial,
|
||||||
|
AwaitingPicture,
|
||||||
|
AwaitingTitle,
|
||||||
|
AwaitingSubtitle,
|
||||||
|
ShowingResult,
|
||||||
|
}
|
34
SimpleTGBot/Interactions.cs
Normal file
34
SimpleTGBot/Interactions.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
19
SimpleTGBot/Logging/LogLevel.cs
Normal file
19
SimpleTGBot/Logging/LogLevel.cs
Normal 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"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
6
SimpleTGBot/Logging/LogSink.cs
Normal file
6
SimpleTGBot/Logging/LogSink.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace SimpleTGBot.Logging;
|
||||||
|
|
||||||
|
internal interface ILogSink : IDisposable
|
||||||
|
{
|
||||||
|
public void Log(DateTime time, LogLevel level, string message);
|
||||||
|
}
|
34
SimpleTGBot/Logging/Logger.cs
Normal file
34
SimpleTGBot/Logging/Logger.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
SimpleTGBot/Logging/StdoutSink.cs
Normal file
23
SimpleTGBot/Logging/StdoutSink.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user