Почистить комментарии от ссылок на ТЗ и обрывков

Удаление целых //-блоков со ссылками (Task/Ruling/этап/ТЗ/§/
дизайн-док/api-map/python/прототип) вместо построчного вырезания —
без обрывков фраз; снят боилерплейт <param>/<returns>; то же в
.proto.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:49:26 +03:00
parent b053d58335
commit 79c931d88e
220 changed files with 32 additions and 1259 deletions
@@ -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 &gt; 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,