181 lines
10 KiB
C#
181 lines
10 KiB
C#
using DailyDigestWorker.Configuration; // Не используется напрямую, но нужно для DI WTelegramClient
|
||
using Microsoft.Extensions.Options;
|
||
using System.Collections.Generic;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using TL; // Основное пространство имен WTelegramClient
|
||
using WTelegram; // Для Client
|
||
|
||
namespace DailyDigestWorker.Services
|
||
{
|
||
public class TelegramChannelReader : ITelegramChannelReader // Реализуем обновленный интерфейс
|
||
{
|
||
private readonly ILogger<TelegramChannelReader> _logger;
|
||
private readonly Client _client; // Наш Singleton WTelegramClient
|
||
// Зависимость от IOptions<TelegramClientSettings> убрана
|
||
// Кэшированное поле _targetPeer убрано
|
||
|
||
// Конструктор теперь принимает только логгер и WTelegramClient
|
||
public TelegramChannelReader(
|
||
ILogger<TelegramChannelReader> logger,
|
||
Client client)
|
||
{
|
||
_logger = logger;
|
||
_client = client;
|
||
}
|
||
|
||
// Метод теперь принимает channelUsername как параметр
|
||
public async Task<List<string>?> GetRecentNewsAsync(
|
||
string channelUsername, // Имя канала для этого конкретного вызова
|
||
int maxAgeHours,
|
||
int limit,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
// Внешний try-catch для диагностики любых ошибок
|
||
try
|
||
{
|
||
_logger.LogInformation("[GetRecentNewsAsync] Попытка чтения новостей из канала {ChannelUsername}...", channelUsername);
|
||
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
_logger.LogWarning("[GetRecentNewsAsync] Операция отменена ПЕРЕД началом работы для канала {ChannelUsername}.", channelUsername);
|
||
return null;
|
||
}
|
||
|
||
_logger.LogDebug("[GetRecentNewsAsync] Шаг 1: Проверка/выполнение авторизации пользователя...");
|
||
User? currentUser = null;
|
||
try
|
||
{
|
||
// Выполняем логин, если необходимо (может запросить ввод в консоль)
|
||
currentUser = await _client.LoginUserIfNeeded().ConfigureAwait(false);
|
||
}
|
||
catch (Exception loginEx)
|
||
{
|
||
_logger.LogError(loginEx, "[GetRecentNewsAsync] Ошибка ВО ВРЕМЯ выполнения LoginUserIfNeeded.");
|
||
return null;
|
||
}
|
||
|
||
if (currentUser == null)
|
||
{
|
||
_logger.LogError("[GetRecentNewsAsync] Авторизация не удалась (LoginUserIfNeeded вернул null или произошла ошибка выше).");
|
||
return null;
|
||
}
|
||
_logger.LogInformation("[GetRecentNewsAsync] Шаг 1 Успех: Авторизация как {UserFirstName} (ID: {UserId})", currentUser.first_name, currentUser.id);
|
||
|
||
|
||
// 2. Найти канал (получить InputPeer) - делаем это каждый раз
|
||
_logger.LogDebug("[GetRecentNewsAsync] Шаг 2: Поиск InputPeer для канала {ChannelUsername}...", channelUsername);
|
||
InputPeer? targetPeer = null; // Локальная переменная
|
||
try
|
||
{
|
||
var resolved = await _client.Contacts_ResolveUsername(channelUsername).ConfigureAwait(false);
|
||
if (resolved?.UserOrChat is Channel channel)
|
||
{
|
||
targetPeer = channel.ToInputPeer();
|
||
_logger.LogInformation("[GetRecentNewsAsync] Шаг 2 Успех: Найден InputPeer для канала '{ChannelTitle}' (ID: {ChannelId})", channel.title, channel.id);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogError("[GetRecentNewsAsync] Шаг 2 Ошибка: Не удалось найти канал по юзернейму {ChannelUsername} или это не канал. Resolved: {@Resolved}", channelUsername, resolved);
|
||
return null;
|
||
}
|
||
}
|
||
catch (RpcException e)
|
||
{
|
||
_logger.LogError(e, "[GetRecentNewsAsync] Ошибка Telegram API ({ErrorCode}) при поиске канала {ChannelUsername}: {ErrorMessage}", e.Code, channelUsername, e.Message);
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "[GetRecentNewsAsync] Неожиданная ошибка при поиске канала {ChannelUsername}.", channelUsername);
|
||
return null;
|
||
}
|
||
|
||
|
||
// Если targetPeer все еще null (не должно произойти при успешном resolve)
|
||
if (targetPeer == null)
|
||
{
|
||
_logger.LogError("[GetRecentNewsAsync] Критическая ошибка: InputPeer остался null после ResolveUsername для {ChannelUsername}", channelUsername);
|
||
return null;
|
||
}
|
||
|
||
|
||
// 3. Получить историю сообщений
|
||
_logger.LogDebug("[GetRecentNewsAsync] Шаг 3: Запрос истории сообщений (limit={Limit})...", limit);
|
||
Messages_MessagesBase? history = null; // Используем nullable тип
|
||
try
|
||
{
|
||
history = await _client.Messages_GetHistory(targetPeer, limit: limit).ConfigureAwait(false);
|
||
}
|
||
catch (RpcException e)
|
||
{
|
||
_logger.LogError(e, "[GetRecentNewsAsync] Ошибка Telegram API ({ErrorCode}) при получении истории для {ChannelUsername}: {ErrorMessage}", e.Code, channelUsername, e.Message);
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "[GetRecentNewsAsync] Неожиданная ошибка при получении истории для {ChannelUsername}.", channelUsername);
|
||
return null;
|
||
}
|
||
|
||
if (history == null)
|
||
{
|
||
_logger.LogWarning("[GetRecentNewsAsync] Шаг 3 Ошибка: Не удалось получить историю сообщений (результат null) для канала {ChannelUsername}.", channelUsername);
|
||
return null;
|
||
}
|
||
_logger.LogInformation("[GetRecentNewsAsync] Шаг 3 Успех: Получено {Count} сообщений/элементов в истории для канала {ChannelUsername}.", history.Messages.Length, channelUsername);
|
||
|
||
|
||
// 4. Отфильтровать сообщения по дате и извлечь текст
|
||
_logger.LogDebug("[GetRecentNewsAsync] Шаг 4: Фильтрация сообщений новее {CutoffDateUtc} UTC...", DateTime.UtcNow.AddHours(-maxAgeHours));
|
||
var newsTexts = new List<string>();
|
||
var cutoffDate = DateTime.UtcNow.AddHours(-maxAgeHours);
|
||
|
||
foreach (var msgBase in history.Messages)
|
||
{
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
_logger.LogWarning("[GetRecentNewsAsync] Операция отменена во время фильтрации сообщений.");
|
||
return null;
|
||
}
|
||
|
||
if (msgBase is Message message)
|
||
{
|
||
if (message.Date >= cutoffDate)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(message.message))
|
||
{
|
||
newsTexts.Add(message.message);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
_logger.LogDebug("[GetRecentNewsAsync] Достигнуто сообщение старше {CutoffDateUtc} UTC, остановка фильтрации.", cutoffDate);
|
||
break; // Оптимизация: сообщения идут от новых к старым
|
||
}
|
||
}
|
||
}
|
||
_logger.LogInformation("[GetRecentNewsAsync] Шаг 4 Успех: Найдено {Count} новостных сообщений за последние {Hours} часов из канала {ChannelUsername}.", newsTexts.Count, maxAgeHours, channelUsername);
|
||
|
||
// Сообщения были от новых к старым, переворачиваем для хронологии
|
||
newsTexts.Reverse();
|
||
return newsTexts;
|
||
|
||
}
|
||
catch (RpcException e) // Ловим ошибки Telegram API на уровне всего метода
|
||
{
|
||
_logger.LogError(e, "[GetRecentNewsAsync] Ошибка Telegram API ({ErrorCode}) во время работы с каналом {ChannelUsername}: {ErrorMessage}", e.Code, channelUsername, e.Message);
|
||
if (e.Code == 420) // FLOOD_WAIT_X
|
||
{
|
||
_logger.LogWarning("[GetRecentNewsAsync] Получен FloodWait на {Seconds} секунд. Пропускаем чтение.", e.X);
|
||
}
|
||
return null;
|
||
}
|
||
catch (Exception ex) // Ловим ЛЮБЫЕ другие ошибки внутри метода
|
||
{
|
||
_logger.LogError(ex, "[GetRecentNewsAsync] Неожиданная ошибка ВНУТРИ метода для канала {ChannelUsername}.", channelUsername);
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
} |