Почистить комментарии от упоминаний процесса

Удалены <remarks>, <summary> сжаты до короткой фразы, вырезаны
ссылки на Task/Ruling/этап/python/прототип; //-комментарии со ссылками
на процесс удалены; то же в .proto. Правила обновлены в
docs/spec/Код-стайл-Дейл.md. Строк комментариев 27210 -> ~19100.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:39:39 +03:00
parent 5f5538d33b
commit b053d58335
902 changed files with 3902 additions and 12074 deletions
@@ -9,25 +9,10 @@ using RpcException = TL.RpcException;
namespace Deal.Telegram.Telegram;
#pragma warning disable CS0618 // Auth_SendCode/Auth_SignIn используются осознанно: ручной веб-вход 1:1 с прототипом
// (start_phone/submit_code/submit_password L134–176); Obsolete-метки библиотеки ведут на
// LoginUserIfNeeded, который умеет только интерактивный конфиг-ввод, а не наш пошаговый API.
/// <summary>
/// Реальная реализация <see cref="ISessionClient"/> поверх WTelegramClient (план Task 9; Ruling 3).
///
/// Сессия библиотеки живёт в памяти процесса: байты сессии (внутренне зашифрованы WTelegramClient
/// ключом api_hash) подаются в конструктор и обновляются колбэком при каждом сохранении библиотекой;
/// at-rest файл data/sessions/&lt;tenant&gt;.session (AES-GCM-обёртка, Ruling 3) пишет SessionStore —
/// расшифрованного файла на диске нет ни в какой момент (Ruling 3: только в памяти процесса).
/// Шаги входа повторяют python-прототип на уровне TL-методов:
/// Auth_SendCode → (код) Auth_SignIn → 2FA: Account_GetPassword + Auth_CheckPassword; QR —
/// LoginWithQRCode с колбэком новых URL. Ошибки переводятся в <see cref="Sessions.SessionException"/>.
/// Операции каталога (план Task 10) ходят TL-методами messages.getDialogs/getHistory и readHistory
/// (ReadHistory клиента — generic-хелпер channels/messages); discovery (план Task 11) — contacts.search,
/// getFullChannel/getFullChat (участники/forum), getForumTopics+getReplies (чтение форумов по темам) и
/// channels.joinChannel/leaveChannel; realtime-сообщения нормализует штатный
/// <see cref="UpdateManager"/> библиотеки (UpdateNewMessage для всех типов, включая каналы и короткие
/// UpdateShort*) и поднимаются событием <see cref="MessageReceived"/>.
/// Реальная реализация <see cref="ISessionClient"/> поверх WTelegramClient.
/// </summary>
public sealed class WTelegramSessionClient : ISessionClient
{
@@ -52,10 +37,8 @@ public sealed class WTelegramSessionClient : ISessionClient
// LRU-кэш access_hash сущностей (ключ — подписанный id диалога; заполняется из ответов).
private readonly LruCache<string, long> _entityAccessHashes = new(AccessHashCacheCapacity);
// LRU-кэш сущностей чатов/каналов (ключ — raw id; для имён и forum-флага discovery, Task 11).
private readonly LruCache<long, ChatBase> _chatsById = new(ChatEntityCacheCapacity);
// LRU-кэш сущностей пользователей (ключ — raw id; для имён discovery, Task 11).
private readonly LruCache<long, User> _usersById = new(UserEntityCacheCapacity);
// Защита кэшей сущностей (обновляются из потоков reactor/вызовов).
@@ -234,7 +217,6 @@ public sealed class WTelegramSessionClient : ISessionClient
return _client.DisposeAsync();
}
// --- Каталог и сообщения (план Task 10; Ruling 3/7; TL-методы getDialogs/getHistory/readHistory) ---
/// <inheritdoc />
public event Func<TelegramMessage, Task>? MessageReceived;
@@ -288,11 +270,9 @@ public sealed class WTelegramSessionClient : ISessionClient
{
InputPeer peer = await ResolvePeerAsync(dialogId, cancellationToken).ConfigureAwait(false);
// Generic-хелпер библиотеки: для канала — channels.readHistory, иначе — messages.readHistory;
// max_id=0 (default) — «снять новое» по всему диалогу (1:1 send_read_acknowledge прототипа).
await RunTlCallAsync(() => _client.ReadHistory(peer), cancellationToken).ConfigureAwait(false);
}
// --- Discovery (план Task 11; discovery_search/info/read/join/leave L622–873; Ruling 3/7) ---
/// <inheritdoc />
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(
@@ -321,9 +301,7 @@ public sealed class WTelegramSessionClient : ISessionClient
/// <inheritdoc />
public async Task<TelegramSourceInfo> GetInfoAsync(string dialogId, CancellationToken cancellationToken)
{
// discovery_info L666–716: определение никогда не бросает наружу — недоступная сущность/полный
// чат дают инфо по умолчанию (name=id, kind пуст, participants пуст), сбой участников не роняет
// остальные поля (прототип: исключение только логируется).
TelegramSourceInfo unknown = DefaultSourceInfo(dialogId);
if (!TryParseSignedId(dialogId, out bool isChannel, out bool isChat, out bool isUser, out long rawId))
{
@@ -361,7 +339,6 @@ public sealed class WTelegramSessionClient : ISessionClient
int limit,
CancellationToken cancellationToken)
{
// discovery_read L718–760: limit ≤ 0 — пустой ok без сетевых вызовов (L730–732).
if (limit <= 0)
{
return DiscoveryReadResult.Empty;
@@ -379,7 +356,6 @@ public sealed class WTelegramSessionClient : ISessionClient
}
catch (SessionException)
{
// Сущность не разрешилась (приватный/закрытый источник без членства) → no_history (L736–739).
return DiscoveryReadResult.NoHistory();
}
@@ -388,7 +364,6 @@ public sealed class WTelegramSessionClient : ISessionClient
IReadOnlyList<DiscoveryMessage> forumMessages = await ReadForumTopicsAsync(peer, limit, cancellationToken).ConfigureAwait(false);
if (forumMessages.Count > 0)
{
// Форум прочитан по темам (L746–747: непустой результат тем — ответ, без ленты).
return new DiscoveryReadResult(true, null, forumMessages);
}
}
@@ -403,7 +378,6 @@ public sealed class WTelegramSessionClient : ISessionClient
}
catch (SessionException)
{
// История недоступна (приватный/закрытый) → ok=false no_history (L751–753), не ошибка RPC.
return DiscoveryReadResult.NoHistory();
}
}
@@ -411,7 +385,6 @@ public sealed class WTelegramSessionClient : ISessionClient
/// <inheritdoc />
public async Task JoinAsync(string username, CancellationToken cancellationToken)
{
// discovery_join L818–839: username → сущность → channels.JoinChannel (для мегагрупп/каналов).
Contacts_ResolvedPeer resolved = await RunTlCallAsync(() => _client.Contacts_ResolveUsername(username), cancellationToken).ConfigureAwait(false);
CacheEntities(resolved.chats.Values, resolved.users.Values);
@@ -427,7 +400,6 @@ public sealed class WTelegramSessionClient : ISessionClient
/// <inheritdoc />
public async Task LeaveAsync(string dialogId, CancellationToken cancellationToken)
{
// discovery_leave L841–848: channels.LeaveChannel по подписанному id (каналы/супергруппы).
if (!TryParseSignedId(dialogId, out bool isChannel, out _, out _, out long rawId) || !isChannel)
{
throw new SessionException(StatusCode.InvalidArgument, SessionErrorMessages.InvalidDialogId);
@@ -438,7 +410,6 @@ public sealed class WTelegramSessionClient : ISessionClient
}
// Инфо о канале/супергруппе: entity из кэша/полного чата, участники — GetFullChannel best-effort
// (1:1 L686–715: entity недоступен → default; участники недоступны → остальные поля остаются).
// dialogId: Подписанный id (для имени по умолчанию).
// rawId: Raw id канала.
// unknown: Инфо по умолчанию (сущность недоступна).
@@ -457,12 +428,10 @@ public sealed class WTelegramSessionClient : ISessionClient
if (cached is not null)
{
// Сущность известна (поиск/каталог): имя/kind/forum — сразу, участники — best-effort (L700–715).
int? participants = await TryFetchChannelParticipantsAsync(dialogId, rawId, cancellationToken).ConfigureAwait(false);
return DescribeChannel(dialogId, cached, participants);
}
// Неизвестная сущность: полный чат принесёт её; недоступен → default (как entity-not-found L686–690).
InputChannel input = await ResolveInputChannelAsync(dialogId, rawId, cancellationToken).ConfigureAwait(false);
Messages_ChatFull full = await RunTlCallAsync(() => _client.Channels_GetFullChannel(input), cancellationToken).ConfigureAwait(false);
CacheEntities(full.chats.Values, full.users.Values);
@@ -477,7 +446,6 @@ public sealed class WTelegramSessionClient : ISessionClient
return DescribeChannel(dialogId, channel, fullParticipants);
}
// Участники канала best-effort: сбой полного чата → null, имя/kind не роняются (L713–715).
// dialogId: Подписанный id.
// rawId: Raw id канала.
// cancellationToken: Отмена операции.
@@ -537,7 +505,6 @@ public sealed class WTelegramSessionClient : ISessionClient
return DescribeGroup(dialogId, group, fullParticipants);
}
// Список участников базовой группы best-effort: сбой → null (нет членства/приватная, L713–715).
// rawId: Raw id группы.
// cancellationToken: Отмена операции.
private async Task<int?> TryFetchBasicParticipantsAsync(long rawId, CancellationToken cancellationToken)
@@ -575,7 +542,6 @@ public sealed class WTelegramSessionClient : ISessionClient
// Инфо о личном чате/боте: имя из кэша сущности (полного чата у людей нет).
// dialogId: Подписанный id.
// rawId: Raw id пользователя.
// unknown: Инфо по умолчанию (сущность неизвестна — как entity-not-found прототипа).
private TelegramSourceInfo GetUserInfo(
string dialogId,
long rawId,
@@ -602,7 +568,6 @@ public sealed class WTelegramSessionClient : ISessionClient
return new TelegramSourceInfo(dialogId, name, username, DialogKinds.Chat, participants: null, isForum: false);
}
// Выборка по активным темам форума: GetForumTopics + по каждой теме getReplies (L762–800).
// peer: Peer форума.
// limit: Размер выборки (раскладывается по темам).
// cancellationToken: Отмена операции.
@@ -615,7 +580,6 @@ public sealed class WTelegramSessionClient : ISessionClient
var outMessages = new List<DiscoveryMessage>();
try
{
// channels/messages.getForumTopics (L771–775): до 5 активных тем, без смещения.
Messages_ForumTopics forum = await RunTlCallAsync(
() => _client.Messages_GetForumTopics(peer, offset_date: default, offset_id: 0, offset_topic: 0, limit: ForumTopicsLimit),
cancellationToken).ConfigureAwait(false);
@@ -627,14 +591,12 @@ public sealed class WTelegramSessionClient : ISessionClient
return outMessages;
}
// На тему минимум 3 сообщения, cap 10 (прототип L784); суммарно выборка может слегка превысить limit.
int perTopic = Math.Min(Math.Max(3, (int)Math.Ceiling(limit / (double)topics.Length)), ForumMessagesPerTopicCap);
DateTimeOffset now = DateTimeOffset.UtcNow;
foreach (ForumTopic topic in topics)
{
try
{
// get_messages(reply_to=topic.id) эквивалент: messages.getReplies (L792, Fix round 1).
Messages_MessagesBase result = await RunTlCallAsync(
() => _client.Messages_GetReplies(peer, topic.id, limit: perTopic),
cancellationToken).ConfigureAwait(false);
@@ -651,13 +613,11 @@ public sealed class WTelegramSessionClient : ISessionClient
}
catch (SessionException)
{
// Тема не прочиталась — пропуск (прототип L793–795: continue).
}
}
}
catch (SessionException)
{
// getForumTopics недоступен — безопасный фолбэк на обычную ленту (L777–779).
outMessages.Clear();
}
@@ -703,7 +663,6 @@ public sealed class WTelegramSessionClient : ISessionClient
participants,
isForum: (channel.flags & Channel.Flags.forum) != 0);
// Отображаемое имя сущности (title/first_name) или id (как get_display_name прототипа).
// dialogId: Подписанный id (фолбэк имени).
// chat: Сущность чата/канала.
private static string DisplayName(string dialogId, ChatBase chat)
@@ -712,7 +671,6 @@ public sealed class WTelegramSessionClient : ISessionClient
return title.Length > 0 ? title : dialogId;
}
// True — канал из кэша сущностей является форумом (темы; entity.forum прототипа L698).
// rawId: Raw id канала.
private bool IsForumChannel(long rawId)
{
@@ -724,15 +682,11 @@ public sealed class WTelegramSessionClient : ISessionClient
}
}
// Сколько активных тем форума запрашивает чтение выборки (getForumTopics limit=5, L773).
private const int ForumTopicsLimit = 5;
// Потолок сообщений на тему форума (cap 10, прототип L784).
private const int ForumMessagesPerTopicCap = 10;
// --- Realtime-события (план Task 10; прототип _on_message L255–283) ---
// Единый колбэк штатного UpdateManager (см. _updateManager): вызывается
// последовательно на каждое обновление в правильном порядке (без пропусков/дублей по pts).
// Все типы новых сообщений библиотека нормализует в TL.UpdateNewMessage:
// * UpdateNewChannelMessage (каналы/супергруппы) — подкласс UpdateNewMessage;
@@ -810,7 +764,6 @@ public sealed class WTelegramSessionClient : ISessionClient
private void OnSessionSaved(byte[] sessionBytes)
=> Volatile.Write(ref _latestSessionBytes, sessionBytes);
// Запрашивает код с одним повтором при AUTH_RESTART (как LoginUserIfNeeded L1202–1205).
private async Task<Auth_SentCodeBase> SendCodeOnceAsync(string phone, CancellationToken cancellationToken)
{
try
@@ -849,7 +802,6 @@ public sealed class WTelegramSessionClient : ISessionClient
// Переводит RpcException Telegram в SessionException: FloodWait — RESOURCE_EXHAUSTED
// (detail с префиксом "flood", контракт telegram.proto); 400-ошибки входных данных —
// INVALID_ARGUMENT (текст RPC как detail, как у прототипа: ошибка показывается как есть);
// остальные серверные/сетевые сбои — UNAVAILABLE «Telegram недоступен…» (безопасный повтор).
// exception: Исключение RPC Telegram.
private static SessionException MapRpcException(RpcException exception)
@@ -867,7 +819,6 @@ public sealed class WTelegramSessionClient : ISessionClient
return new SessionException(StatusCode.Unavailable, SessionErrorMessages.TelegramUnavailable, exception);
}
// --- Приватные помощники каталога/сообщений (план Task 10) ---
// Исполняет TL-вызов с единым переводом ошибок (RpcException Telegram → SessionException;
// прочие сбои — UNAVAILABLE «Telegram недоступен…»).