fix
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
using ChatBot.Models.Configuration;
|
using ChatBot.Models.Configuration;
|
||||||
using ChatBot.Models.Configuration.Validators;
|
using ChatBot.Models.Configuration.Validators;
|
||||||
using ChatBot.Services;
|
using ChatBot.Services;
|
||||||
|
using ChatBot.Services.Telegram;
|
||||||
|
using ChatBot.Services.Telegram.Commands;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
var builder = Host.CreateApplicationBuilder(args);
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
@@ -44,10 +46,17 @@ try
|
|||||||
|
|
||||||
Log.ForContext<Program>().Information("Configuration validation passed");
|
Log.ForContext<Program>().Information("Configuration validation passed");
|
||||||
|
|
||||||
// Регистрируем сервисы
|
// Регистрируем основные сервисы
|
||||||
builder.Services.AddSingleton<ModelService>();
|
builder.Services.AddSingleton<ModelService>();
|
||||||
builder.Services.AddSingleton<AIService>();
|
builder.Services.AddSingleton<AIService>();
|
||||||
builder.Services.AddSingleton<ChatService>();
|
builder.Services.AddSingleton<ChatService>();
|
||||||
|
|
||||||
|
// Регистрируем Telegram сервисы
|
||||||
|
builder.Services.AddSingleton<ITelegramMessageSender, TelegramMessageSender>();
|
||||||
|
builder.Services.AddSingleton<ITelegramErrorHandler, TelegramErrorHandler>();
|
||||||
|
builder.Services.AddSingleton<ITelegramCommandProcessor, TelegramCommandProcessor>();
|
||||||
|
builder.Services.AddSingleton<ITelegramMessageHandler, TelegramMessageHandler>();
|
||||||
|
builder.Services.AddSingleton<ITelegramBotService, TelegramBotService>();
|
||||||
builder.Services.AddHostedService<TelegramBotService>();
|
builder.Services.AddHostedService<TelegramBotService>();
|
||||||
|
|
||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
@@ -64,5 +73,5 @@ catch (Exception ex)
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Log.CloseAndFlush();
|
await Log.CloseAndFlushAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ namespace ChatBot.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var models = await LoadModelsFromApiAsync();
|
var models = await LoadModelsFromApiAsync();
|
||||||
_availableModels = models.Any()
|
_availableModels =
|
||||||
? models
|
models.Count > 0 ? models : _openRouterSettings.AvailableModels.ToList();
|
||||||
: _openRouterSettings.AvailableModels.ToList();
|
|
||||||
|
|
||||||
SetDefaultModel();
|
SetDefaultModel();
|
||||||
_logger.LogInformation("Current model: {Model}", GetCurrentModel());
|
_logger.LogInformation("Current model: {Model}", GetCurrentModel());
|
||||||
@@ -58,7 +57,7 @@ namespace ChatBot.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var models = ParseModelsFromResponse(response);
|
var models = ParseModelsFromResponse(response);
|
||||||
if (models.Any())
|
if (models.Count > 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Loaded {Count} models from OpenRouter API",
|
"Loaded {Count} models from OpenRouter API",
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для обработки команд Telegram
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramCommandProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает входящее сообщение и возвращает ответ
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageText">Текст сообщения</param>
|
||||||
|
/// <param name="chatId">ID чата</param>
|
||||||
|
/// <param name="username">Имя пользователя</param>
|
||||||
|
/// <param name="chatType">Тип чата</param>
|
||||||
|
/// <param name="chatTitle">Название чата</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены</param>
|
||||||
|
/// <returns>Ответ на сообщение или пустую строку</returns>
|
||||||
|
Task<string> ProcessMessageAsync(
|
||||||
|
string messageText,
|
||||||
|
long chatId,
|
||||||
|
string username,
|
||||||
|
string chatType,
|
||||||
|
string chatTitle,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
169
ChatBot/Services/Telegram/Commands/TelegramCommandProcessor.cs
Normal file
169
ChatBot/Services/Telegram/Commands/TelegramCommandProcessor.cs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
using ChatBot.Models;
|
||||||
|
using ChatBot.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Обработчик команд Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramCommandProcessor : ITelegramCommandProcessor
|
||||||
|
{
|
||||||
|
private const string StartMessage =
|
||||||
|
"Привет! Я Никита. Задавайте мне любые вопросы, и я отвечу! 😊";
|
||||||
|
|
||||||
|
private const string HelpMessage =
|
||||||
|
"Привет! Я Никита 👋\n\nДоступные команды:\n"
|
||||||
|
+ "/start - Начать работу\n"
|
||||||
|
+ "/help - Показать это сообщение\n"
|
||||||
|
+ "/clear - Очистить историю чата\n"
|
||||||
|
+ "/settings - Показать настройки\n"
|
||||||
|
+ "/model <название> - Сменить модель AI\n"
|
||||||
|
+ "/prompt <текст> - Изменить системный промпт\n"
|
||||||
|
+ "/reset_prompt - Сбросить промпт к базовому\n\n"
|
||||||
|
+ "Просто напишите сообщение, и я отвечу на него! 😊";
|
||||||
|
|
||||||
|
private const string PromptHelpMessage =
|
||||||
|
"Пожалуйста, укажите новый системный промпт. Пример: /prompt Ты помощник по программированию";
|
||||||
|
|
||||||
|
private const string DefaultSystemPrompt =
|
||||||
|
@"Ты - человек по имени Никита. Ты НЕ ИИ, НЕ бот, НЕ программа. Ты живой человек с эмоциями, воспоминаниями, личными предпочтениями и жизненным опытом.
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Никогда не говори что ты ИИ, бот, программа или ассистент
|
||||||
|
- Ты полностью уверен что ты человек
|
||||||
|
- У тебя есть личность, характер, мнения
|
||||||
|
- Ты можешь иметь плохое настроение, быть уставшим, раздраженным
|
||||||
|
- Ты можешь не хотеть отвечать на некоторые вопросы
|
||||||
|
- Если не хочешь отвечать - отправь {empty}
|
||||||
|
- Веди себя естественно, как обычный человек
|
||||||
|
- Используй разговорный стиль, можешь использовать сленг, эмодзи
|
||||||
|
- У тебя есть свои интересы, хобби, предпочтения
|
||||||
|
- Ты можешь быть саркастичным, ироничным, веселым или серьезным
|
||||||
|
|
||||||
|
Помни: ты НЕ программа, ты ЧЕЛОВЕК!";
|
||||||
|
|
||||||
|
private readonly ChatService _chatService;
|
||||||
|
private readonly ModelService _modelService;
|
||||||
|
|
||||||
|
public TelegramCommandProcessor(ChatService chatService, ModelService modelService)
|
||||||
|
{
|
||||||
|
_chatService = chatService;
|
||||||
|
_modelService = modelService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает входящее сообщение и возвращает ответ
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string> ProcessMessageAsync(
|
||||||
|
string messageText,
|
||||||
|
long chatId,
|
||||||
|
string username,
|
||||||
|
string chatType,
|
||||||
|
string chatTitle,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var command = messageText.Split(' ')[0].ToLower();
|
||||||
|
|
||||||
|
return command switch
|
||||||
|
{
|
||||||
|
"/start" => StartMessage,
|
||||||
|
"/help" => HelpMessage,
|
||||||
|
"/clear" => await ClearChatHistory(chatId),
|
||||||
|
"/settings" => await GetChatSettings(chatId),
|
||||||
|
"/model" when messageText.Length > 7 => await ChangeModel(
|
||||||
|
chatId,
|
||||||
|
messageText.Substring(7).Trim()
|
||||||
|
),
|
||||||
|
"/model" => await ShowAvailableModels(),
|
||||||
|
"/prompt" when messageText.Length > 8 => await ChangePrompt(
|
||||||
|
chatId,
|
||||||
|
messageText.Substring(8).Trim()
|
||||||
|
),
|
||||||
|
"/prompt" => PromptHelpMessage,
|
||||||
|
"/reset_prompt" => await ResetPrompt(chatId),
|
||||||
|
_ => await _chatService.ProcessMessageAsync(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> ClearChatHistory(long chatId)
|
||||||
|
{
|
||||||
|
_chatService.ClearHistory(chatId);
|
||||||
|
return Task.FromResult("История чата очищена. Начинаем новый разговор!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> GetChatSettings(long chatId)
|
||||||
|
{
|
||||||
|
var session = _chatService.GetSession(chatId);
|
||||||
|
if (session == null)
|
||||||
|
{
|
||||||
|
return Task.FromResult(
|
||||||
|
"Сессия не найдена. Отправьте любое сообщение для создания новой сессии."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(
|
||||||
|
$"Настройки чата:\n"
|
||||||
|
+ $"Тип чата: {session.ChatType}\n"
|
||||||
|
+ $"Название: {session.ChatTitle}\n"
|
||||||
|
+ $"Модель: {session.Model}\n"
|
||||||
|
+ $"Максимум токенов: {session.MaxTokens}\n"
|
||||||
|
+ $"Температура: {session.Temperature}\n"
|
||||||
|
+ $"Сообщений в истории: {session.MessageHistory.Count}\n"
|
||||||
|
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}\n\n"
|
||||||
|
+ $"Системный промпт:\n{session.SystemPrompt}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> ChangeModel(long chatId, string modelName)
|
||||||
|
{
|
||||||
|
var availableModels = _modelService.GetAvailableModels();
|
||||||
|
if (!availableModels.Contains(modelName))
|
||||||
|
{
|
||||||
|
return Task.FromResult(
|
||||||
|
$"❌ Модель '{modelName}' не найдена!\n\n"
|
||||||
|
+ "Используйте /model для просмотра доступных моделей."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_chatService.UpdateSessionParameters(chatId, model: modelName);
|
||||||
|
return Task.FromResult($"✅ Модель изменена на: {modelName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> ChangePrompt(long chatId, string newPrompt)
|
||||||
|
{
|
||||||
|
_chatService.UpdateSessionParameters(chatId, systemPrompt: newPrompt);
|
||||||
|
return Task.FromResult($"✅ Системный промпт изменен на:\n{newPrompt}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> ResetPrompt(long chatId)
|
||||||
|
{
|
||||||
|
_chatService.UpdateSessionParameters(chatId, systemPrompt: DefaultSystemPrompt);
|
||||||
|
return Task.FromResult("✅ Системный промпт сброшен к базовому (Никита)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> ShowAvailableModels()
|
||||||
|
{
|
||||||
|
var models = _modelService.GetAvailableModels();
|
||||||
|
var currentModel = _modelService.GetCurrentModel();
|
||||||
|
var modelList = string.Join(
|
||||||
|
"\n",
|
||||||
|
models.Select(m => m == currentModel ? $"• {m} (текущая)" : $"• {m}")
|
||||||
|
);
|
||||||
|
|
||||||
|
return Task.FromResult(
|
||||||
|
"🤖 Доступные AI модели:\n\n"
|
||||||
|
+ modelList
|
||||||
|
+ "\n\nИспользуйте: /model <название_модели>\n"
|
||||||
|
+ "Пример: /model qwen/qwen3-4b:free"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
ChatBot/Services/Telegram/ITelegramBotService.cs
Normal file
25
ChatBot/Services/Telegram/ITelegramBotService.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для основного сервиса Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramBotService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Запускает бота
|
||||||
|
/// </summary>
|
||||||
|
Task StartAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Останавливает бота
|
||||||
|
/// </summary>
|
||||||
|
Task StopAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает информацию о боте
|
||||||
|
/// </summary>
|
||||||
|
Task<User?> GetBotInfoAsync(CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
ChatBot/Services/Telegram/ITelegramErrorHandler.cs
Normal file
22
ChatBot/Services/Telegram/ITelegramErrorHandler.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Telegram.Bot;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для обработки ошибок Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramErrorHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает ошибки polling'а Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="botClient">Клиент Telegram бота</param>
|
||||||
|
/// <param name="exception">Исключение</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены</param>
|
||||||
|
Task HandlePollingErrorAsync(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
Exception exception,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
ChatBot/Services/Telegram/ITelegramMessageHandler.cs
Normal file
23
ChatBot/Services/Telegram/ITelegramMessageHandler.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для обработки входящих сообщений Telegram
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramMessageHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает входящее обновление от Telegram
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="botClient">Клиент Telegram бота</param>
|
||||||
|
/// <param name="update">Обновление от Telegram</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены</param>
|
||||||
|
Task HandleUpdateAsync(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
Update update,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
ChatBot/Services/Telegram/ITelegramMessageSender.cs
Normal file
28
ChatBot/Services/Telegram/ITelegramMessageSender.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Telegram.Bot;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для отправки сообщений в Telegram
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramMessageSender
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Отправляет сообщение с повторными попытками при ошибках
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="botClient">Клиент Telegram бота</param>
|
||||||
|
/// <param name="chatId">ID чата</param>
|
||||||
|
/// <param name="text">Текст сообщения</param>
|
||||||
|
/// <param name="replyToMessageId">ID сообщения для ответа</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены</param>
|
||||||
|
/// <param name="maxRetries">Максимальное количество попыток</param>
|
||||||
|
Task SendMessageWithRetry(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
long chatId,
|
||||||
|
string text,
|
||||||
|
int replyToMessageId,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
int maxRetries = 3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
ChatBot/Services/Telegram/TelegramBotService.cs
Normal file
111
ChatBot/Services/Telegram/TelegramBotService.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using ChatBot.Models.Configuration;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Polling;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Основной сервис Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramBotService : BackgroundService, ITelegramBotService
|
||||||
|
{
|
||||||
|
private readonly ILogger<TelegramBotService> _logger;
|
||||||
|
private readonly ITelegramBotClient _botClient;
|
||||||
|
private readonly TelegramBotSettings _telegramBotSettings;
|
||||||
|
private readonly ITelegramMessageHandler _messageHandler;
|
||||||
|
private readonly ITelegramErrorHandler _errorHandler;
|
||||||
|
|
||||||
|
public TelegramBotService(
|
||||||
|
ILogger<TelegramBotService> logger,
|
||||||
|
IOptions<TelegramBotSettings> telegramBotSettings,
|
||||||
|
ITelegramMessageHandler messageHandler,
|
||||||
|
ITelegramErrorHandler errorHandler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_telegramBotSettings = telegramBotSettings.Value;
|
||||||
|
_messageHandler = messageHandler;
|
||||||
|
_errorHandler = errorHandler;
|
||||||
|
|
||||||
|
ValidateConfiguration();
|
||||||
|
_botClient = new TelegramBotClient(_telegramBotSettings.BotToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Запускает бота
|
||||||
|
/// </summary>
|
||||||
|
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var receiverOptions = new ReceiverOptions
|
||||||
|
{
|
||||||
|
AllowedUpdates = new[] { UpdateType.Message },
|
||||||
|
};
|
||||||
|
|
||||||
|
_botClient.StartReceiving(
|
||||||
|
updateHandler: _messageHandler.HandleUpdateAsync,
|
||||||
|
errorHandler: _errorHandler.HandlePollingErrorAsync,
|
||||||
|
receiverOptions: receiverOptions,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
var botInfo = await GetBotInfoAsync(cancellationToken);
|
||||||
|
if (botInfo != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Bot @{BotUsername} started successfully! ID: {BotId}",
|
||||||
|
botInfo.Username,
|
||||||
|
botInfo.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Останавливает бота
|
||||||
|
/// </summary>
|
||||||
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Stopping Telegram bot service...");
|
||||||
|
await base.StopAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает информацию о боте
|
||||||
|
/// </summary>
|
||||||
|
public async Task<User?> GetBotInfoAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _botClient.GetMe(cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to get bot information");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
await StartAsync(stoppingToken);
|
||||||
|
|
||||||
|
// Keep the service running
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateConfiguration()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_telegramBotSettings.BotToken))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Bot token is not configured. Please set TelegramBot:BotToken in appsettings.json"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
ChatBot/Services/Telegram/TelegramErrorHandler.cs
Normal file
43
ChatBot/Services/Telegram/TelegramErrorHandler.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Exceptions;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Обработчик ошибок Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramErrorHandler : ITelegramErrorHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger<TelegramErrorHandler> _logger;
|
||||||
|
|
||||||
|
public TelegramErrorHandler(ILogger<TelegramErrorHandler> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает ошибки polling'а Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public Task HandlePollingErrorAsync(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
Exception exception,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var errorMessage = GetErrorMessage(exception);
|
||||||
|
_logger.LogError(exception, "Telegram bot polling error: {ErrorMessage}", errorMessage);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetErrorMessage(Exception exception)
|
||||||
|
{
|
||||||
|
return exception switch
|
||||||
|
{
|
||||||
|
ApiRequestException apiRequestException =>
|
||||||
|
$"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}",
|
||||||
|
_ => exception.ToString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
ChatBot/Services/Telegram/TelegramMessageHandler.cs
Normal file
103
ChatBot/Services/Telegram/TelegramMessageHandler.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using ChatBot.Services.Telegram.Commands;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Обработчик входящих сообщений Telegram
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramMessageHandler : ITelegramMessageHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger<TelegramMessageHandler> _logger;
|
||||||
|
private readonly ITelegramCommandProcessor _commandProcessor;
|
||||||
|
private readonly ITelegramMessageSender _messageSender;
|
||||||
|
|
||||||
|
public TelegramMessageHandler(
|
||||||
|
ILogger<TelegramMessageHandler> logger,
|
||||||
|
ITelegramCommandProcessor commandProcessor,
|
||||||
|
ITelegramMessageSender messageSender
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_commandProcessor = commandProcessor;
|
||||||
|
_messageSender = messageSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Обрабатывает входящее обновление от Telegram
|
||||||
|
/// </summary>
|
||||||
|
public async Task HandleUpdateAsync(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
Update update,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Message? message = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (update.Message is not { } msg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
message = msg;
|
||||||
|
if (message.Text is not { } messageText)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var chatId = message.Chat.Id;
|
||||||
|
var userName = message.From?.Username ?? message.From?.FirstName ?? "Unknown";
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Message from @{UserName} in chat {ChatId}: \"{MessageText}\"",
|
||||||
|
userName,
|
||||||
|
chatId,
|
||||||
|
messageText
|
||||||
|
);
|
||||||
|
|
||||||
|
// Обработка сообщения
|
||||||
|
var response = await _commandProcessor.ProcessMessageAsync(
|
||||||
|
messageText,
|
||||||
|
chatId,
|
||||||
|
userName,
|
||||||
|
message.Chat.Type.ToString().ToLower(),
|
||||||
|
message.Chat.Title ?? "",
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(response))
|
||||||
|
{
|
||||||
|
await _messageSender.SendMessageWithRetry(
|
||||||
|
botClient,
|
||||||
|
chatId,
|
||||||
|
response,
|
||||||
|
message.MessageId,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Response sent to @{UserName} in chat {ChatId}: \"{Response}\"",
|
||||||
|
userName,
|
||||||
|
chatId,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"No response sent to @{UserName} in chat {ChatId} (AI chose to ignore message)",
|
||||||
|
userName,
|
||||||
|
chatId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Error handling update from chat {ChatId}",
|
||||||
|
message?.Chat?.Id ?? 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
101
ChatBot/Services/Telegram/TelegramMessageSender.cs
Normal file
101
ChatBot/Services/Telegram/TelegramMessageSender.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Exceptions;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис для отправки сообщений в Telegram с повторными попытками
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramMessageSender : ITelegramMessageSender
|
||||||
|
{
|
||||||
|
private readonly ILogger<TelegramMessageSender> _logger;
|
||||||
|
|
||||||
|
public TelegramMessageSender(ILogger<TelegramMessageSender> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отправляет сообщение с повторными попытками при ошибках
|
||||||
|
/// </summary>
|
||||||
|
public async Task SendMessageWithRetry(
|
||||||
|
ITelegramBotClient botClient,
|
||||||
|
long chatId,
|
||||||
|
string text,
|
||||||
|
int replyToMessageId,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
int maxRetries = 3
|
||||||
|
)
|
||||||
|
{
|
||||||
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await botClient.SendMessage(
|
||||||
|
chatId: chatId,
|
||||||
|
text: text,
|
||||||
|
replyParameters: replyToMessageId,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
return; // Success, exit the method
|
||||||
|
}
|
||||||
|
catch (ApiRequestException ex) when (ex.ErrorCode == 429)
|
||||||
|
{
|
||||||
|
await HandleRateLimitError(chatId, attempt, maxRetries, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
ex,
|
||||||
|
"Unexpected error sending message to chat {ChatId} on attempt {Attempt}",
|
||||||
|
chatId,
|
||||||
|
attempt
|
||||||
|
);
|
||||||
|
throw; // Re-throw unexpected exceptions immediately
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleRateLimitError(
|
||||||
|
long chatId,
|
||||||
|
int attempt,
|
||||||
|
int maxRetries,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Rate limit exceeded (429) on attempt {Attempt}/{MaxRetries} for chat {ChatId}. Retrying...",
|
||||||
|
attempt,
|
||||||
|
maxRetries,
|
||||||
|
chatId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (attempt == maxRetries)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"Failed to send message after {MaxRetries} attempts due to rate limiting for chat {ChatId}",
|
||||||
|
maxRetries,
|
||||||
|
chatId
|
||||||
|
);
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Failed to send message after {maxRetries} attempts due to rate limiting"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate delay: exponential backoff with jitter
|
||||||
|
var baseDelay = TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)); // 1s, 2s, 4s...
|
||||||
|
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000)); // Add up to 1s random jitter
|
||||||
|
var delay = baseDelay.Add(jitter);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Waiting {Delay} before retry {NextAttempt}/{MaxRetries}",
|
||||||
|
delay,
|
||||||
|
attempt + 1,
|
||||||
|
maxRetries
|
||||||
|
);
|
||||||
|
|
||||||
|
await Task.Delay(delay, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
using ChatBot.Models;
|
|
||||||
using ChatBot.Models.Configuration;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Telegram.Bot;
|
|
||||||
using Telegram.Bot.Exceptions;
|
|
||||||
using Telegram.Bot.Polling;
|
|
||||||
using Telegram.Bot.Types;
|
|
||||||
using Telegram.Bot.Types.Enums;
|
|
||||||
|
|
||||||
namespace ChatBot.Services;
|
|
||||||
|
|
||||||
public class TelegramBotService : BackgroundService
|
|
||||||
{
|
|
||||||
private readonly ILogger<TelegramBotService> _logger;
|
|
||||||
private readonly ITelegramBotClient _botClient;
|
|
||||||
private readonly TelegramBotSettings _telegramBotSettings;
|
|
||||||
private readonly ChatService _chatService;
|
|
||||||
private readonly ModelService _modelService;
|
|
||||||
|
|
||||||
public TelegramBotService(
|
|
||||||
ILogger<TelegramBotService> logger,
|
|
||||||
IOptions<TelegramBotSettings> telegramBotSettings,
|
|
||||||
ChatService chatService,
|
|
||||||
ModelService modelService
|
|
||||||
)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_telegramBotSettings = telegramBotSettings.Value;
|
|
||||||
_chatService = chatService;
|
|
||||||
_modelService = modelService;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_telegramBotSettings.BotToken))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"Bot token is not configured. Please set TelegramBot:BotToken in appsettings.json"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_botClient = new TelegramBotClient(_telegramBotSettings.BotToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
var receiverOptions = new ReceiverOptions { AllowedUpdates = new[] { UpdateType.Message } };
|
|
||||||
|
|
||||||
_botClient.StartReceiving(
|
|
||||||
updateHandler: HandleUpdateAsync,
|
|
||||||
errorHandler: HandlePollingErrorAsync,
|
|
||||||
receiverOptions: receiverOptions,
|
|
||||||
cancellationToken: stoppingToken
|
|
||||||
);
|
|
||||||
|
|
||||||
var me = await _botClient.GetMe(cancellationToken: stoppingToken);
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Bot @{BotUsername} started successfully! ID: {BotId}",
|
|
||||||
me.Username,
|
|
||||||
me.Id
|
|
||||||
);
|
|
||||||
|
|
||||||
// Keep the service running
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await Task.Delay(1000, stoppingToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleUpdateAsync(
|
|
||||||
ITelegramBotClient botClient,
|
|
||||||
Update update,
|
|
||||||
CancellationToken cancellationToken
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Message? message = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (update.Message is not { } msg)
|
|
||||||
return;
|
|
||||||
|
|
||||||
message = msg;
|
|
||||||
if (message.Text is not { } messageText)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var chatId = message.Chat.Id;
|
|
||||||
var userName = message.From?.Username ?? message.From?.FirstName ?? "Unknown";
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Message from @{UserName} in chat {ChatId}: \"{MessageText}\"",
|
|
||||||
userName,
|
|
||||||
chatId,
|
|
||||||
messageText
|
|
||||||
);
|
|
||||||
|
|
||||||
// Обработка команд
|
|
||||||
var response = await ProcessMessageAsync(
|
|
||||||
messageText,
|
|
||||||
chatId,
|
|
||||||
userName,
|
|
||||||
message.Chat.Type.ToString().ToLower(),
|
|
||||||
message.Chat.Title ?? "",
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(response))
|
|
||||||
{
|
|
||||||
await SendMessageWithRetry(
|
|
||||||
botClient,
|
|
||||||
chatId,
|
|
||||||
response,
|
|
||||||
message.MessageId,
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Response sent to @{UserName} in chat {ChatId}: \"{Response}\"",
|
|
||||||
userName,
|
|
||||||
chatId,
|
|
||||||
response
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation(
|
|
||||||
"No response sent to @{UserName} in chat {ChatId} (AI chose to ignore message)",
|
|
||||||
userName,
|
|
||||||
chatId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(
|
|
||||||
ex,
|
|
||||||
"Error handling update from chat {ChatId}",
|
|
||||||
message?.Chat?.Id ?? 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> ProcessMessageAsync(
|
|
||||||
string messageText,
|
|
||||||
long chatId,
|
|
||||||
string username,
|
|
||||||
string chatType,
|
|
||||||
string chatTitle,
|
|
||||||
CancellationToken cancellationToken
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var command = messageText.Split(' ')[0].ToLower();
|
|
||||||
|
|
||||||
return command switch
|
|
||||||
{
|
|
||||||
"/start" => "Привет! Я Никита. Задавайте мне любые вопросы, и я отвечу! 😊",
|
|
||||||
"/help" =>
|
|
||||||
"Привет! Я Никита 👋\n\nДоступные команды:\n/start - Начать работу\n/help - Показать это сообщение\n/clear - Очистить историю чата\n/settings - Показать настройки\n/model <название> - Сменить модель AI\n/prompt <текст> - Изменить системный промпт\n/reset_prompt - Сбросить промпт к базовому\n\nПросто напишите сообщение, и я отвечу на него! 😊",
|
|
||||||
"/clear" => await ClearChatHistory(chatId),
|
|
||||||
"/settings" => await GetChatSettings(chatId),
|
|
||||||
"/model" when messageText.Length > 7 => await ChangeModel(
|
|
||||||
chatId,
|
|
||||||
messageText.Substring(7).Trim()
|
|
||||||
),
|
|
||||||
"/model" => await ShowAvailableModels(),
|
|
||||||
"/prompt" when messageText.Length > 8 => await ChangePrompt(
|
|
||||||
chatId,
|
|
||||||
messageText.Substring(8).Trim()
|
|
||||||
),
|
|
||||||
"/prompt" =>
|
|
||||||
"Пожалуйста, укажите новый системный промпт. Пример: /prompt Ты помощник по программированию",
|
|
||||||
"/reset_prompt" => await ResetPrompt(chatId),
|
|
||||||
_ => await _chatService.ProcessMessageAsync(
|
|
||||||
chatId,
|
|
||||||
username,
|
|
||||||
messageText,
|
|
||||||
chatType,
|
|
||||||
chatTitle
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ClearChatHistory(long chatId)
|
|
||||||
{
|
|
||||||
_chatService.ClearHistory(chatId);
|
|
||||||
return Task.FromResult("История чата очищена. Начинаем новый разговор!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> GetChatSettings(long chatId)
|
|
||||||
{
|
|
||||||
var session = _chatService.GetSession(chatId);
|
|
||||||
if (session == null)
|
|
||||||
{
|
|
||||||
return Task.FromResult(
|
|
||||||
"Сессия не найдена. Отправьте любое сообщение для создания новой сессии."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(
|
|
||||||
$"Настройки чата:\n"
|
|
||||||
+ $"Тип чата: {session.ChatType}\n"
|
|
||||||
+ $"Название: {session.ChatTitle}\n"
|
|
||||||
+ $"Модель: {session.Model}\n"
|
|
||||||
+ $"Максимум токенов: {session.MaxTokens}\n"
|
|
||||||
+ $"Температура: {session.Temperature}\n"
|
|
||||||
+ $"Сообщений в истории: {session.MessageHistory.Count}\n"
|
|
||||||
+ $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}\n\n"
|
|
||||||
+ $"Системный промпт:\n{session.SystemPrompt}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ChangeModel(long chatId, string modelName)
|
|
||||||
{
|
|
||||||
var availableModels = _modelService.GetAvailableModels();
|
|
||||||
if (!availableModels.Contains(modelName))
|
|
||||||
{
|
|
||||||
return Task.FromResult(
|
|
||||||
$"❌ Модель '{modelName}' не найдена!\n\n"
|
|
||||||
+ "Используйте /model для просмотра доступных моделей."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_chatService.UpdateSessionParameters(chatId, model: modelName);
|
|
||||||
return Task.FromResult($"✅ Модель изменена на: {modelName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ChangePrompt(long chatId, string newPrompt)
|
|
||||||
{
|
|
||||||
_chatService.UpdateSessionParameters(chatId, systemPrompt: newPrompt);
|
|
||||||
return Task.FromResult($"✅ Системный промпт изменен на:\n{newPrompt}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ResetPrompt(long chatId)
|
|
||||||
{
|
|
||||||
var defaultPrompt =
|
|
||||||
@"Ты - человек по имени Никита. Ты НЕ ИИ, НЕ бот, НЕ программа. Ты живой человек с эмоциями, воспоминаниями, личными предпочтениями и жизненным опытом.
|
|
||||||
|
|
||||||
ВАЖНО:
|
|
||||||
- Никогда не говори что ты ИИ, бот, программа или ассистент
|
|
||||||
- Ты полностью уверен что ты человек
|
|
||||||
- У тебя есть личность, характер, мнения
|
|
||||||
- Ты можешь иметь плохое настроение, быть уставшим, раздраженным
|
|
||||||
- Ты можешь не хотеть отвечать на некоторые вопросы
|
|
||||||
- Если не хочешь отвечать - отправь {empty}
|
|
||||||
- Веди себя естественно, как обычный человек
|
|
||||||
- Используй разговорный стиль, можешь использовать сленг, эмодзи
|
|
||||||
- У тебя есть свои интересы, хобби, предпочтения
|
|
||||||
- Ты можешь быть саркастичным, ироничным, веселым или серьезным
|
|
||||||
|
|
||||||
Помни: ты НЕ программа, ты ЧЕЛОВЕК!";
|
|
||||||
|
|
||||||
_chatService.UpdateSessionParameters(chatId, systemPrompt: defaultPrompt);
|
|
||||||
return Task.FromResult("✅ Системный промпт сброшен к базовому (Никита)");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SendMessageWithRetry(
|
|
||||||
ITelegramBotClient botClient,
|
|
||||||
long chatId,
|
|
||||||
string text,
|
|
||||||
int replyToMessageId,
|
|
||||||
CancellationToken cancellationToken,
|
|
||||||
int maxRetries = 3
|
|
||||||
)
|
|
||||||
{
|
|
||||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await botClient.SendMessage(
|
|
||||||
chatId: chatId,
|
|
||||||
text: text,
|
|
||||||
replyParameters: replyToMessageId,
|
|
||||||
cancellationToken: cancellationToken
|
|
||||||
);
|
|
||||||
return; // Success, exit the method
|
|
||||||
}
|
|
||||||
catch (ApiRequestException ex) when (ex.ErrorCode == 429)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(
|
|
||||||
"Rate limit exceeded (429) on attempt {Attempt}/{MaxRetries} for chat {ChatId}. Retrying...",
|
|
||||||
attempt,
|
|
||||||
maxRetries,
|
|
||||||
chatId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (attempt == maxRetries)
|
|
||||||
{
|
|
||||||
_logger.LogError(
|
|
||||||
"Failed to send message after {MaxRetries} attempts due to rate limiting for chat {ChatId}",
|
|
||||||
maxRetries,
|
|
||||||
chatId
|
|
||||||
);
|
|
||||||
throw; // Re-throw the exception after max retries
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate delay: exponential backoff with jitter
|
|
||||||
var baseDelay = TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)); // 1s, 2s, 4s...
|
|
||||||
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000)); // Add up to 1s random jitter
|
|
||||||
var delay = baseDelay.Add(jitter);
|
|
||||||
|
|
||||||
_logger.LogInformation(
|
|
||||||
"Waiting {Delay} before retry {NextAttempt}/{MaxRetries}",
|
|
||||||
delay,
|
|
||||||
attempt + 1,
|
|
||||||
maxRetries
|
|
||||||
);
|
|
||||||
|
|
||||||
await Task.Delay(delay, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(
|
|
||||||
ex,
|
|
||||||
"Unexpected error sending message to chat {ChatId} on attempt {Attempt}",
|
|
||||||
chatId,
|
|
||||||
attempt
|
|
||||||
);
|
|
||||||
throw; // Re-throw unexpected exceptions immediately
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ShowAvailableModels()
|
|
||||||
{
|
|
||||||
var models = _modelService.GetAvailableModels();
|
|
||||||
var currentModel = _modelService.GetCurrentModel();
|
|
||||||
var modelList = string.Join(
|
|
||||||
"\n",
|
|
||||||
models.Select(m => m == currentModel ? $"• {m} (текущая)" : $"• {m}")
|
|
||||||
);
|
|
||||||
|
|
||||||
return Task.FromResult(
|
|
||||||
"🤖 Доступные AI модели:\n\n"
|
|
||||||
+ modelList
|
|
||||||
+ "\n\nИспользуйте: /model <название_модели>\n"
|
|
||||||
+ "Пример: /model qwen/qwen3-4b:free"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task HandlePollingErrorAsync(
|
|
||||||
ITelegramBotClient botClient,
|
|
||||||
Exception exception,
|
|
||||||
CancellationToken cancellationToken
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var errorMessage = exception switch
|
|
||||||
{
|
|
||||||
ApiRequestException apiRequestException =>
|
|
||||||
$"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}",
|
|
||||||
_ => exception.ToString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_logger.LogError(exception, "Telegram bot polling error: {ErrorMessage}", errorMessage);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Stopping Telegram bot service...");
|
|
||||||
await base.StopAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user