Почистить комментарии от ссылок на ТЗ и обрывков
Удаление целых //-блоков со ссылками (Task/Ruling/этап/ТЗ/§/ дизайн-док/api-map/python/прототип) вместо построчного вырезания — без обрывков фраз; снят боилерплейт <param>/<returns>; то же в .proto.
This commit is contained in:
@@ -109,7 +109,6 @@ public sealed class CoreIngressClient : ICoreIngressClient
|
||||
}
|
||||
}
|
||||
|
||||
// tenantId: Id тенанта.
|
||||
private CallOptions CallOptions(string tenantId)
|
||||
{
|
||||
var metadata = new Metadata
|
||||
@@ -120,8 +119,6 @@ public sealed class CoreIngressClient : ICoreIngressClient
|
||||
return new CallOptions(metadata, deadline: DateTime.UtcNow.AddSeconds(CoreIngressOptions.RpcTimeoutSeconds));
|
||||
}
|
||||
|
||||
// Переводит сбой вызова в SessionException (UNAVAILABLE) со структурированным логом.
|
||||
// exception: Исключение вызова.
|
||||
private SessionException Fail(string action, Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception, "Аудит: ингресс ядра {Action} → сбой ({Endpoint})", action, _options.IngressEndpoint);
|
||||
|
||||
@@ -122,8 +122,6 @@ public sealed class BackfillService
|
||||
}
|
||||
}
|
||||
|
||||
// tenantId: Id тенанта.
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task EnsureDialogSpacingAsync(string tenantId, CancellationToken cancellationToken)
|
||||
{
|
||||
DateTime? lastFinishUtc;
|
||||
@@ -181,7 +179,6 @@ public sealed class BackfillService
|
||||
await _pacer.WaitAsync(MinPerMessageDelaySeconds, MaxPerMessageDelaySeconds, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// не выполняется: неотправленное останется непрочитанным и догонится realtime_sweep.
|
||||
await _sessionFarm.MarkReadAsync(tenantId, dialogId, cancellationToken).ConfigureAwait(false);
|
||||
return processed;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,6 @@ public sealed class DialogCatalog
|
||||
}
|
||||
}
|
||||
|
||||
// tenantId: Id тенанта.
|
||||
private HashSet<string> KnownSet(string tenantId)
|
||||
{
|
||||
if (!_knownByTenant.TryGetValue(tenantId, out HashSet<string>? known))
|
||||
@@ -183,7 +182,6 @@ public sealed class DialogCatalog
|
||||
return known;
|
||||
}
|
||||
|
||||
// tenantId: Id тенанта.
|
||||
private HashSet<string> MonitoredSet(string tenantId)
|
||||
{
|
||||
if (!_monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored))
|
||||
|
||||
@@ -130,9 +130,6 @@ public sealed class RealtimeSweep
|
||||
}
|
||||
}
|
||||
|
||||
// tenantId: Id тенанта.
|
||||
// dialog: Диалог с unread_count > 0.
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task SweepDialogAsync(
|
||||
string tenantId,
|
||||
TelegramDialog dialog,
|
||||
@@ -166,7 +163,6 @@ public sealed class RealtimeSweep
|
||||
added);
|
||||
}
|
||||
|
||||
// цикл увидит unread снова и дочитает то, что не ушло в ядро.
|
||||
await _sessionFarm.MarkReadAsync(tenantId, dialog.Id, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -142,8 +142,6 @@ public sealed class DiscoveryOps
|
||||
public static string NormalizeUsername(string? username)
|
||||
=> (username ?? string.Empty).Trim().TrimStart('@');
|
||||
|
||||
// found: Результат поиска сессии (chats затем users).
|
||||
// limit: Верхняя граница числа записей.
|
||||
private static IReadOnlyList<TelegramDialog> DedupeAndCap(IReadOnlyList<TelegramDialog> found, int limit)
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
//
|
||||
// Kestrel HTTP/2 на порту 5101 (env GRPC_PORT, затем PORT) + AddGrpc с интерцепторами service-token
|
||||
// и access-лога + стандартный gRPC-health (grpc.health.v1.Health). Транспорт: dev — plaintext
|
||||
// Production без mTLS не стартует (GrpcHostEnvironment.RequireMtlsInProduction).
|
||||
// Серверная обвязка (Kestrel/AddGrpc/health) — общий Deal.Grpc.Hosting (C31): хост-фабрика
|
||||
// TelegramServiceHost.Create используется и интеграционными тестами (Deal.Telegram.Tests), которые
|
||||
|
||||
using Deal.Grpc.Hosting.Interceptors;
|
||||
using Deal.Grpc.Hosting.Models;
|
||||
@@ -11,16 +6,12 @@ using Deal.Grpc.Hosting.Options;
|
||||
using Deal.Grpc.Hosting.Services;
|
||||
using Deal.Telegram;
|
||||
|
||||
// или PORT (общий конвенциональный env хостинг-платформ) — см. GrpcHostEnvironment.ResolveGrpcPort.
|
||||
const int defaultGrpcPort = 5101;
|
||||
const string telegramProcessName = "telegram";
|
||||
|
||||
int grpcPort = GrpcHostEnvironment.ResolveGrpcPort(defaultGrpcPort);
|
||||
int metricsPort = DealMetricsHosting.ResolveMetricsPort(DealMetricsHosting.DefaultMetricsPort);
|
||||
|
||||
// конфигурируется production-точкой входа через configureBuilder-хук хоста (тесты хост поднимают
|
||||
// без Serilog, DealLogging.Configure в TelegramServiceHost/Create вызывается только здесь). Метрики
|
||||
// (OTel → Prometheus, /metrics) — тем же хуком до builder.Build().
|
||||
WebApplication app = TelegramServiceHost.Create(
|
||||
grpcPort,
|
||||
configureBuilder: builder =>
|
||||
|
||||
@@ -197,7 +197,6 @@ public sealed class TenantSession : IAsyncDisposable
|
||||
_qrUrl = null;
|
||||
}
|
||||
|
||||
// LoginWithQRCode продолжала бы авторизацию «скрыто» после отмены RPC (замечание code-review).
|
||||
CancelQrFlow();
|
||||
|
||||
throw;
|
||||
@@ -698,7 +697,6 @@ public sealed class TenantSession : IAsyncDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Собственный лимит попытки: linked-токен с CancelAfter на время попытки переподключения.
|
||||
using CancellationTokenSource attemptTimeout =
|
||||
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
attemptTimeout.CancelAfter(_reconnectAttemptTimeout);
|
||||
@@ -838,8 +836,6 @@ public sealed class TenantSession : IAsyncDisposable
|
||||
AttachClientMessages(_client);
|
||||
}
|
||||
|
||||
// Сбой get_me не отменяет готовность — сохраняем сессию без account (готовность важнее имени).
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task CompleteAuthorizationAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_client is null)
|
||||
@@ -1015,8 +1011,6 @@ public sealed class TenantSession : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка ошибки QR после того, как URL уже был выдан (RPC вернулся): фаза idle + текст ошибки
|
||||
// exception: Ошибка QR-входа.
|
||||
private async Task FailQrUnderGateAsync(SessionException exception)
|
||||
{
|
||||
await _gate.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -179,8 +179,6 @@ public static class TlMessageMapper
|
||||
_ => throw new SessionException(StatusCode.InvalidArgument, SessionErrorMessages.InvalidDialogId),
|
||||
};
|
||||
|
||||
// date: Дата сообщения.
|
||||
// now: Текущее время (фолбэк).
|
||||
private static long ToEpochMsOrNow(DateTime date, DateTimeOffset now)
|
||||
=> date == default ? now.ToUnixTimeMilliseconds() : ToEpochMs(date);
|
||||
|
||||
@@ -201,7 +199,6 @@ public static class TlMessageMapper
|
||||
private static UserBase? FindUser(Peer peer, IReadOnlyDictionary<long, User> users)
|
||||
=> peer is PeerUser user && users.TryGetValue(user.user_id, out User? regularUser) ? regularUser : null;
|
||||
|
||||
// date: Время сообщения.
|
||||
private static long ToEpochMs(DateTime date)
|
||||
=> new DateTimeOffset(DateTime.SpecifyKind(date, DateTimeKind.Utc)).ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using RpcException = TL.RpcException;
|
||||
namespace Deal.Telegram.Telegram;
|
||||
|
||||
#pragma warning disable CS0618 // Auth_SendCode/Auth_SignIn используются осознанно: ручной веб-вход 1:1 с прототипом
|
||||
// LoginUserIfNeeded, который умеет только интерактивный конфиг-ввод, а не наш пошаговый API.
|
||||
|
||||
/// <summary>
|
||||
/// Реальная реализация <see cref="ISessionClient"/> поверх WTelegramClient.
|
||||
@@ -269,7 +268,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
public async Task MarkReadAsync(string dialogId, CancellationToken cancellationToken)
|
||||
{
|
||||
InputPeer peer = await ResolvePeerAsync(dialogId, cancellationToken).ConfigureAwait(false);
|
||||
// Generic-хелпер библиотеки: для канала — channels.readHistory, иначе — messages.readHistory;
|
||||
await RunTlCallAsync(() => _client.ReadHistory(peer), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -301,7 +299,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
/// <inheritdoc />
|
||||
public async Task<TelegramSourceInfo> GetInfoAsync(string dialogId, CancellationToken cancellationToken)
|
||||
{
|
||||
// чат дают инфо по умолчанию (name=id, kind пуст, participants пуст), сбой участников не роняет
|
||||
TelegramSourceInfo unknown = DefaultSourceInfo(dialogId);
|
||||
if (!TryParseSignedId(dialogId, out bool isChannel, out bool isChat, out bool isUser, out long rawId))
|
||||
{
|
||||
@@ -409,11 +406,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
await RunTlCallAsync(() => _client.Channels_LeaveChannel(channel), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Инфо о канале/супергруппе: entity из кэша/полного чата, участники — GetFullChannel best-effort
|
||||
// dialogId: Подписанный id (для имени по умолчанию).
|
||||
// rawId: Raw id канала.
|
||||
// unknown: Инфо по умолчанию (сущность недоступна).
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task<TelegramSourceInfo> GetChannelInfoAsync(
|
||||
string dialogId,
|
||||
long rawId,
|
||||
@@ -446,9 +438,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
return DescribeChannel(dialogId, channel, fullParticipants);
|
||||
}
|
||||
|
||||
// dialogId: Подписанный id.
|
||||
// rawId: Raw id канала.
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task<int?> TryFetchChannelParticipantsAsync(
|
||||
string dialogId,
|
||||
long rawId,
|
||||
@@ -505,8 +494,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
return DescribeGroup(dialogId, group, fullParticipants);
|
||||
}
|
||||
|
||||
// rawId: Raw id группы.
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task<int?> TryFetchBasicParticipantsAsync(long rawId, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -539,9 +526,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
participants,
|
||||
isForum: false);
|
||||
|
||||
// Инфо о личном чате/боте: имя из кэша сущности (полного чата у людей нет).
|
||||
// dialogId: Подписанный id.
|
||||
// rawId: Raw id пользователя.
|
||||
private TelegramSourceInfo GetUserInfo(
|
||||
string dialogId,
|
||||
long rawId,
|
||||
@@ -568,10 +552,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
return new TelegramSourceInfo(dialogId, name, username, DialogKinds.Chat, participants: null, isForum: false);
|
||||
}
|
||||
|
||||
// peer: Peer форума.
|
||||
// limit: Размер выборки (раскладывается по темам).
|
||||
// cancellationToken: Отмена операции.
|
||||
// Возвращает: Плоский список сообщений тем (пуст — темы не прочитались; фолбэк на ленту делает вызывающий).
|
||||
private async Task<IReadOnlyList<DiscoveryMessage>> ReadForumTopicsAsync(
|
||||
InputPeer peer,
|
||||
int limit,
|
||||
@@ -663,15 +643,12 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
participants,
|
||||
isForum: (channel.flags & Channel.Flags.forum) != 0);
|
||||
|
||||
// dialogId: Подписанный id (фолбэк имени).
|
||||
// chat: Сущность чата/канала.
|
||||
private static string DisplayName(string dialogId, ChatBase chat)
|
||||
{
|
||||
string title = chat.Title ?? string.Empty;
|
||||
return title.Length > 0 ? title : dialogId;
|
||||
}
|
||||
|
||||
// rawId: Raw id канала.
|
||||
private bool IsForumChannel(long rawId)
|
||||
{
|
||||
lock (_entityCacheGate)
|
||||
@@ -687,15 +664,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
private const int ForumMessagesPerTopicCap = 10;
|
||||
|
||||
|
||||
// последовательно на каждое обновление в правильном порядке (без пропусков/дублей по pts).
|
||||
// Все типы новых сообщений библиотека нормализует в TL.UpdateNewMessage:
|
||||
// * UpdateNewChannelMessage (каналы/супергруппы) — подкласс UpdateNewMessage;
|
||||
// * UpdateShortMessage/UpdateShortChatMessage — синтез UpdateNewMessage в UpdateList (TL.Xtended);
|
||||
// * UpdateShortSentMessage (собственные исходящие) менеджер не поднимает.
|
||||
// Ниже — только фильтр «новое входящее текстовое сообщение» и подъём события MessageReceived;
|
||||
// фильтр по зеркалу мониторинга и read-ack делает служба каталога (единый downstream).
|
||||
// Ошибки отдельного события не роняют цикл (подписчиков изолирует TenantSession).
|
||||
// update: Одно нормализованное обновление.
|
||||
private async Task OnSingleUpdateAsync(Update update)
|
||||
{
|
||||
try
|
||||
@@ -800,10 +768,6 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
}
|
||||
}
|
||||
|
||||
// Переводит RpcException Telegram в SessionException: FloodWait — RESOURCE_EXHAUSTED
|
||||
// (detail с префиксом "flood", контракт telegram.proto); 400-ошибки входных данных —
|
||||
// остальные серверные/сетевые сбои — UNAVAILABLE «Telegram недоступен…» (безопасный повтор).
|
||||
// exception: Исключение RPC Telegram.
|
||||
private static SessionException MapRpcException(RpcException exception)
|
||||
{
|
||||
if (exception.Message.StartsWith("FLOOD_WAIT_", StringComparison.Ordinal))
|
||||
|
||||
@@ -32,18 +32,11 @@ public static class TelegramServiceHost
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args ?? []);
|
||||
|
||||
// Общая серверная обвязка (Deal.Grpc.Hosting, C31): mTLS env DEAL_MTLS_* — загрузка
|
||||
// сертификатов сразу с fail-fast (compose-prod монтирует deploy/certs, scripts/mtls-certs.sh);
|
||||
// Один экземпляр mtlsCertificates используют и Kestrel ниже, и исходящий канал в ядро
|
||||
// (CoreIngressClient).
|
||||
MtlsCertificates? mtlsCertificates = GrpcServer.LoadMtlsCertificates(builder);
|
||||
GrpcServer.ConfigureKestrelHttp2Endpoint(builder, grpcPort, mtlsCertificates);
|
||||
builder.Services.AddDealGrpcServer();
|
||||
builder.Services.AddReadyHealthCheck("хост telegram-service готов");
|
||||
|
||||
// файлов data/sessions/<tenant>.session (AES-GCM, ключ из env), пул 1 аккаунт/тенант и
|
||||
// фоновый цикл auto_resume/heartbeat (30 с). DEAL_TELEGRAM_SESSION_KEY обязателен —
|
||||
// иначе хост не стартует (сессии не могут храниться в открытом виде).
|
||||
TgOptions sessionOptions = TgOptions.FromConfiguration(builder.Configuration, builder.Environment);
|
||||
builder.Services.AddSingleton(sessionOptions);
|
||||
builder.Services.AddSingleton<SessionFileCipher>();
|
||||
|
||||
@@ -205,7 +205,6 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase
|
||||
List<DialogEntry> entries = dialogs.Select(DialogProtoMapper.ToEntry).ToList();
|
||||
_catalog.ReplaceKnown(tenantId, dialogs.Select(dialog => dialog.Id).ToList());
|
||||
|
||||
// ответ — зеркало догонит realtime_sweep следующим циклом.
|
||||
await SyncCatalogSilentlyAsync(tenantId, entries, ct).ConfigureAwait(false);
|
||||
|
||||
var result = new RefreshDialogsReply();
|
||||
@@ -405,7 +404,6 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase
|
||||
return new LeaveReply { Ok = true };
|
||||
}
|
||||
|
||||
// context: Контекст вызова.
|
||||
private static string RequireTenantId(ServerCallContext context)
|
||||
{
|
||||
string? tenantId = context.RequestHeaders.GetValue(TenantIdMetadataKey);
|
||||
@@ -447,10 +445,6 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort-синхронизация зеркала мониторинга с ядром (SyncDialogs): сбой (ядро недоступно)
|
||||
// tenantId: Id тенанта.
|
||||
// entries: Актуальный каталог диалогов.
|
||||
// cancellationToken: Отмена операции.
|
||||
private async Task SyncCatalogSilentlyAsync(
|
||||
string tenantId,
|
||||
IReadOnlyList<DialogEntry> entries,
|
||||
@@ -467,11 +461,6 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase
|
||||
}
|
||||
}
|
||||
|
||||
// Исполняет операцию сессии с единым переводом ошибок: SessionException → RPC-статус контракта,
|
||||
// TResult: Тип результата операции.
|
||||
// tenantId: Id тенанта (для лога аудита).
|
||||
// operation: Операция сессии.
|
||||
// context: Контекст вызова gRPC.
|
||||
private async Task<TResult> ExecuteAsync<TResult>(
|
||||
string tenantId,
|
||||
Func<CancellationToken, Task<TResult>> operation,
|
||||
|
||||
Reference in New Issue
Block a user