Отформатировать списки параметров по код-стайлу

Больше двух параметров — каждый на отдельной строке (закрывающая
скобка в конце последнего); два и меньше — в одну строку. Правило
добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам
в 253 файлах.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:22:56 +03:00
parent 410194b0cb
commit 492950bdd0
255 changed files with 2672 additions and 668 deletions
@@ -177,13 +177,19 @@ public sealed class BackfillServiceTests
// dialogId: Id диалога.
// id: Id сообщения.
// text: Текст.
private static TelegramMessage Message(string dialogId, int id, string text)
private static TelegramMessage Message(
string dialogId,
int id,
string text)
=> new(dialogId, id, text, dateMs: 1_700_000_000_000 + id, ChannelName, ChannelHandle);
// Служба backfill на пуле, фейк-канале в ядро и фейк-пейсере.
// farm: Пул сессий.
// ingress: Фейк ингресса.
// pacer: Фейк-пейсер (без реальных задержек).
private static BackfillService CreateService(SessionFarm farm, FakeIngress ingress, IBackfillPacer pacer)
private static BackfillService CreateService(
SessionFarm farm,
FakeIngress ingress,
IBackfillPacer pacer)
=> new(farm, ingress, pacer, NullLogger<BackfillService>.Instance);
}
@@ -18,7 +18,10 @@ public sealed class DialogHueTests
[InlineData("-123456", "Уютный чат", "#34d399")]
[InlineData("+79990001122", "Иван Петров", "#fb7185")]
[InlineData("-100999", "", "#34d399")]
public void Compute_MatchesPythonPrototype(string dialogId, string name, string expectedHue)
public void Compute_MatchesPythonPrototype(
string dialogId,
string name,
string expectedHue)
{
Assert.Equal(expectedHue, DialogHue.Compute(dialogId, name));
}
@@ -280,7 +280,11 @@ public sealed class DiscoveryOpsTests
// name: Имя.
// username: Username.
// kind: Тип канона.
private static TelegramDialog Entry(string id, string name, string username, string kind)
private static TelegramDialog Entry(
string id,
string name,
string username,
string kind)
=> new(id, name, username, kind, unreadCount: 0, topMessageId: 0);
// Служба discovery на пуле с фейк-клиентом и фейк-пейсером.
@@ -276,7 +276,10 @@ public sealed class DiscoveryRpcTests
// client: Клиент TelegramService.
// tenantId: Id тенанта сценария.
// phone: Номер телефона.
private static async Task LoginAsync(TelegramService.TelegramServiceClient client, string tenantId = TenantId, string phone = Phone)
private static async Task LoginAsync(
TelegramService.TelegramServiceClient client,
string tenantId = TenantId,
string phone = Phone)
{
await client.StartPhoneAsync(
new StartPhoneRequest { Phone = phone, ApiId = SessionHarness.ApiId, ApiHash = SessionHarness.ApiHash },
@@ -289,7 +292,10 @@ public sealed class DiscoveryRpcTests
// client: Клиент TelegramService.
// tenantId: Id тенанта.
// phase: Ожидаемая фаза ("ready").
private static async Task WaitForPhaseAsync(TelegramService.TelegramServiceClient client, string tenantId, string phase)
private static async Task WaitForPhaseAsync(
TelegramService.TelegramServiceClient client,
string tenantId,
string phase)
{
var deadline = DateTime.UtcNow.AddMilliseconds(PollTimeoutMilliseconds);
while (DateTime.UtcNow < deadline)
@@ -22,7 +22,10 @@ internal sealed class FakeSessionClient : ISessionClient
/// <param name="apiId">api_id приложения.</param>
/// <param name="apiHash">api_hash приложения.</param>
/// <param name="storedSession">Байты «сохранённой сессии» (для проверки передачи в фабрику).</param>
public FakeSessionClient(int apiId, string apiHash, byte[]? storedSession)
public FakeSessionClient(
int apiId,
string apiHash,
byte[]? storedSession)
{
ApiId = apiId;
ApiHash = apiHash;
@@ -274,7 +277,10 @@ internal sealed class FakeSessionClient : ISessionClient
}
/// <inheritdoc />
public Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken)
public Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(
string dialogId,
int limit,
CancellationToken cancellationToken)
{
ReadDialogs.Add(dialogId);
if (MessagesError is not null)
@@ -353,7 +359,10 @@ internal sealed class FakeSessionClient : ISessionClient
public Exception? LeaveError { get; set; }
/// <inheritdoc />
public Task<IReadOnlyList<TelegramDialog>> SearchAsync(string query, int limit, CancellationToken cancellationToken)
public Task<IReadOnlyList<TelegramDialog>> SearchAsync(
string query,
int limit,
CancellationToken cancellationToken)
{
SearchQueries.Add((query, limit));
if (SearchError is not null)
@@ -375,7 +384,10 @@ internal sealed class FakeSessionClient : ISessionClient
}
/// <inheritdoc />
public Task<DiscoveryReadResult> ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken)
public Task<DiscoveryReadResult> ReadForEvalAsync(
string dialogId,
int limit,
CancellationToken cancellationToken)
{
ReadRequests.Add(dialogId);
return Task.FromResult(
@@ -449,7 +461,10 @@ internal sealed class FakeClientFactory : ITelegramClientFactory
public Action<FakeSessionClient>? OnClientCreated { get; set; }
/// <inheritdoc />
public ISessionClient Create(int apiId, string apiHash, byte[]? storedSession)
public ISessionClient Create(
int apiId,
string apiHash,
byte[]? storedSession)
{
var client = new FakeSessionClient(apiId, apiHash, storedSession);
OnClientCreated?.Invoke(client);
@@ -11,7 +11,11 @@ internal sealed class FarmHarness : IAsyncDisposable
{
private const string Phone = "+79990001122";
private FarmHarness(string directory, SessionStore store, FakeClientFactory factory, SessionFarm farm)
private FarmHarness(
string directory,
SessionStore store,
FakeClientFactory factory,
SessionFarm farm)
{
Directory = directory;
Store = store;
@@ -133,6 +133,9 @@ public sealed class RealtimeListenerTests
// session: Ready-сессия тенанта.
// catalog: Зеркало мониторинга.
// ingress: Фейк ингресса.
private static RealtimeListener CreateListener(TenantSession session, DialogCatalog catalog, FakeIngress ingress)
private static RealtimeListener CreateListener(
TenantSession session,
DialogCatalog catalog,
FakeIngress ingress)
=> new(session, catalog, ingress, NullLogger<RealtimeListener>.Instance);
}
@@ -143,6 +143,9 @@ public sealed class RealtimeSweepTests
// farm: Пул сессий.
// catalog: Зеркало каталога.
// ingress: Фейк ингресса.
private static RealtimeSweep CreateSweep(SessionFarm farm, DialogCatalog catalog, FakeIngress ingress)
private static RealtimeSweep CreateSweep(
SessionFarm farm,
DialogCatalog catalog,
FakeIngress ingress)
=> new(farm, catalog, ingress, NullLogger<RealtimeSweep>.Instance);
}
@@ -18,7 +18,11 @@ internal sealed class SessionHarness : IAsyncDisposable
/// </summary>
public const string ApiHash = "0123456789abcdef0123456789abcdef";
private SessionHarness(string directory, SessionStore store, FakeClientFactory factory, TenantSession session)
private SessionHarness(
string directory,
SessionStore store,
FakeClientFactory factory,
TenantSession session)
{
Directory = directory;
Store = store;
@@ -130,7 +130,10 @@ public sealed class TelegramServiceHostTests
// serviceToken: Ожидаемый токен хоста (env DEAL_SERVICE_TOKEN).
// tokenHeader: Значение metadata «service-token» запроса либо null (нет заголовка).
// expected: Ожидаемый StatusCode ответа.
private static async Task AssertDealRpcRejectedAsync(string? serviceToken, string? tokenHeader, StatusCode expected)
private static async Task AssertDealRpcRejectedAsync(
string? serviceToken,
string? tokenHeader,
StatusCode expected)
{
await RunScenarioAsync(serviceToken, channel => AssertRejectedAsync(channel, tokenHeader, expected));
}
@@ -139,7 +142,10 @@ public sealed class TelegramServiceHostTests
// channel: Канал к хосту telegram-service.
// tokenHeader: Значение metadata «service-token» либо null (нет заголовка).
// expected: Ожидаемый StatusCode.
private static async Task AssertRejectedAsync(GrpcChannel channel, string? tokenHeader, StatusCode expected)
private static async Task AssertRejectedAsync(
GrpcChannel channel,
string? tokenHeader,
StatusCode expected)
{
var client = new TelegramService.TelegramServiceClient(channel);
AsyncUnaryCall<GetStatusReply> call = client.GetStatusAsync(
@@ -474,7 +474,11 @@ public sealed class TenantSessionTests
{
private readonly FakeClientFactory _factory;
private SessionContext(string directory, SessionStore store, TenantSession session, FakeClientFactory factory)
private SessionContext(
string directory,
SessionStore store,
TenantSession session,
FakeClientFactory factory)
{
Directory = directory;
Store = store;
@@ -47,7 +47,10 @@ internal sealed class FakeIngress : ICoreIngressClient
public string? LastPushDialogId => Pushes.Count == 0 ? null : Pushes[^1].Message.DialogId;
/// <inheritdoc />
public Task<PushMessageReply> PushMessageAsync(string tenantId, PushMessageRequest message, CancellationToken cancellationToken)
public Task<PushMessageReply> PushMessageAsync(
string tenantId,
PushMessageRequest message,
CancellationToken cancellationToken)
{
if (PushError is not null)
{
@@ -59,7 +62,10 @@ internal sealed class FakeIngress : ICoreIngressClient
}
/// <inheritdoc />
public Task<IReadOnlyList<string>> SyncDialogsAsync(string tenantId, IReadOnlyList<DialogEntry> entries, CancellationToken cancellationToken)
public Task<IReadOnlyList<string>> SyncDialogsAsync(
string tenantId,
IReadOnlyList<DialogEntry> entries,
CancellationToken cancellationToken)
{
if (SyncError is not null)
{
@@ -81,7 +87,10 @@ internal sealed class RecordingPacer : IBackfillPacer
public List<(double MinSeconds, double MaxSeconds)> Waits { get; } = [];
/// <inheritdoc />
public Task WaitAsync(double minSeconds, double maxSeconds, CancellationToken cancellationToken)
public Task WaitAsync(
double minSeconds,
double maxSeconds,
CancellationToken cancellationToken)
{
Waits.Add((minSeconds, maxSeconds));
return Task.CompletedTask;
@@ -173,7 +173,10 @@ public sealed class TlMessageMapperTests
// id: Id сообщения.
// text: Текст.
// flags: Флаги сообщения (по умолчанию входящее).
private static Message ChannelMessage(int id, string text, Message.Flags flags = 0)
private static Message ChannelMessage(
int id,
string text,
Message.Flags flags = 0)
=> new()
{
id = id,
@@ -39,7 +39,10 @@ public sealed class CoreIngressClient : ICoreIngressClient
/// <param name="options">Конфигурация (адрес из env, service-token).</param>
/// <param name="logger">Логгер.</param>
/// <param name="mtlsCertificates">Сертификаты mTLS (Ruling 6, Task 13): null — plaintext-канал (dev).</param>
public CoreIngressClient(CoreIngressOptions options, ILogger<CoreIngressClient> logger, MtlsCertificates? mtlsCertificates = null)
public CoreIngressClient(
CoreIngressOptions options,
ILogger<CoreIngressClient> logger,
MtlsCertificates? mtlsCertificates = null)
{
_options = options;
_logger = logger;
@@ -47,7 +50,10 @@ public sealed class CoreIngressClient : ICoreIngressClient
}
/// <inheritdoc />
public async Task<PushMessageReply> PushMessageAsync(string tenantId, PushMessageRequest message, CancellationToken cancellationToken)
public async Task<PushMessageReply> PushMessageAsync(
string tenantId,
PushMessageRequest message,
CancellationToken cancellationToken)
{
IngressService.IngressServiceClient client = GetClient();
try
@@ -61,7 +67,10 @@ public sealed class CoreIngressClient : ICoreIngressClient
}
/// <inheritdoc />
public async Task<IReadOnlyList<string>> SyncDialogsAsync(string tenantId, IReadOnlyList<DialogEntry> entries, CancellationToken cancellationToken)
public async Task<IReadOnlyList<string>> SyncDialogsAsync(
string tenantId,
IReadOnlyList<DialogEntry> entries,
CancellationToken cancellationToken)
{
IngressService.IngressServiceClient client = GetClient();
var request = new SyncDialogsRequest();
@@ -21,7 +21,10 @@ public interface ICoreIngressClient
/// <param name="message">Сообщение в контракте PushMessageRequest.</param>
/// <param name="cancellationToken">Отмена вызова.</param>
/// <returns>Ответ ядра (accepted/duplicate — дубль dialog+msgId в очереди не растёт).</returns>
public Task<PushMessageReply> PushMessageAsync(string tenantId, PushMessageRequest message, CancellationToken cancellationToken);
public Task<PushMessageReply> PushMessageAsync(
string tenantId,
PushMessageRequest message,
CancellationToken cancellationToken);
/// <summary>
/// Синхронизирует каталог с ядром (Ingress.SyncDialogs → SyncFromTelegram, Ruling 7). Ядро применяет
@@ -32,5 +35,8 @@ public interface ICoreIngressClient
/// <param name="entries">Актуальный каталог диалогов (как refresh_dialogs).</param>
/// <param name="cancellationToken">Отмена вызова.</param>
/// <returns>Список id диалогов с включённым мониторингом (по версии ядра).</returns>
public Task<IReadOnlyList<string>> SyncDialogsAsync(string tenantId, IReadOnlyList<DialogEntry> entries, CancellationToken cancellationToken);
public Task<IReadOnlyList<string>> SyncDialogsAsync(
string tenantId,
IReadOnlyList<DialogEntry> entries,
CancellationToken cancellationToken);
}
@@ -89,7 +89,11 @@ public sealed class BackfillService
/// <param name="force">True — «Перечитать» по кнопке (семантика флага — в ядре, см. класс).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Сколько сообщений отправлено в ядро (0 — диалог уже перечитывается/нет текстов).</returns>
public async Task<int> ExecuteAsync(string tenantId, string dialogId, bool force, CancellationToken cancellationToken)
public async Task<int> ExecuteAsync(
string tenantId,
string dialogId,
bool force,
CancellationToken cancellationToken)
{
var key = (tenantId, dialogId);
if (!_running.TryAdd(key, 0))
@@ -168,7 +172,10 @@ public sealed class BackfillService
// dialogId: Подписанный id диалога.
// cancellationToken: Отмена операции.
// Возвращает: Сколько сообщений отправлено в ядро.
private async Task<int> BackfillDialogAsync(string tenantId, string dialogId, CancellationToken cancellationToken)
private async Task<int> BackfillDialogAsync(
string tenantId,
string dialogId,
CancellationToken cancellationToken)
{
IReadOnlyList<TelegramMessage> messages = await _sessionFarm
.GetMessagesAsync(tenantId, dialogId, MessagesLimit, cancellationToken)
@@ -43,7 +43,10 @@ public sealed class DialogCatalog
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Id диалога.</param>
/// <param name="enabled">Мониторить (true) или выключить (false).</param>
public void SetMonitored(string tenantId, string dialogId, bool enabled)
public void SetMonitored(
string tenantId,
string dialogId,
bool enabled)
{
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
ArgumentException.ThrowIfNullOrWhiteSpace(dialogId);
@@ -17,5 +17,8 @@ public interface IBackfillPacer
/// <param name="maxSeconds">Верхняя граница паузы, секунды (≥ minSeconds).</param>
/// <param name="cancellationToken">Отмена ожидания.</param>
/// <returns>Задача ожидания.</returns>
public Task WaitAsync(double minSeconds, double maxSeconds, CancellationToken cancellationToken);
public Task WaitAsync(
double minSeconds,
double maxSeconds,
CancellationToken cancellationToken);
}
@@ -7,7 +7,10 @@ namespace Deal.Telegram.Dialogs;
public sealed class RandomBackfillPacer : IBackfillPacer
{
/// <inheritdoc />
public async Task WaitAsync(double minSeconds, double maxSeconds, CancellationToken cancellationToken)
public async Task WaitAsync(
double minSeconds,
double maxSeconds,
CancellationToken cancellationToken)
{
if (maxSeconds < 0 || minSeconds < 0 || minSeconds > maxSeconds)
{
@@ -47,7 +47,11 @@ public sealed class RealtimeSweep
/// <param name="catalog">Зеркало каталога/мониторинга (актуализируется ответом SyncDialogs).</param>
/// <param name="ingress">Канал в ядро (PushMessage/SyncDialogs).</param>
/// <param name="logger">Логгер.</param>
public RealtimeSweep(SessionFarm sessionFarm, DialogCatalog catalog, ICoreIngressClient ingress, ILogger<RealtimeSweep> logger)
public RealtimeSweep(
SessionFarm sessionFarm,
DialogCatalog catalog,
ICoreIngressClient ingress,
ILogger<RealtimeSweep> logger)
{
_sessionFarm = sessionFarm;
_catalog = catalog;
@@ -136,7 +140,10 @@ public sealed class RealtimeSweep
// tenantId: Id тенанта.
// dialog: Диалог с unread_count &gt; 0.
// cancellationToken: Отмена операции.
private async Task SweepDialogAsync(string tenantId, TelegramDialog dialog, CancellationToken cancellationToken)
private async Task SweepDialogAsync(
string tenantId,
TelegramDialog dialog,
CancellationToken cancellationToken)
{
try
{
@@ -48,7 +48,10 @@ public sealed class DiscoveryOps
/// <param name="sessionFarm">Пул сессий тенантов (операции только на сессии своего тенанта).</param>
/// <param name="pacer">Анти-бан-паузы (реальный — случайные 2–4 с, тесты — фейк).</param>
/// <param name="logger">Логгер.</param>
public DiscoveryOps(SessionFarm sessionFarm, IBackfillPacer pacer, ILogger<DiscoveryOps> logger)
public DiscoveryOps(
SessionFarm sessionFarm,
IBackfillPacer pacer,
ILogger<DiscoveryOps> logger)
{
_sessionFarm = sessionFarm;
_pacer = pacer;
@@ -65,7 +68,11 @@ public sealed class DiscoveryOps
/// <param name="limit">Верхняя граница результата (≤ 0 — прототип-дефолт 30).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Найденные источники (каналы/группы/личные) без дублей, не длиннее limit.</returns>
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(string tenantId, string query, int limit, CancellationToken cancellationToken)
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(
string tenantId,
string query,
int limit,
CancellationToken cancellationToken)
{
int effectiveLimit = limit > 0 ? limit : SearchDefaultLimit;
IReadOnlyList<TelegramDialog> found = await _sessionFarm
@@ -84,7 +91,10 @@ public sealed class DiscoveryOps
/// <param name="dialogId">Подписанный id источника («-100…»/«-…»/«+…»).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Инфо об источнике (сбои определения не бросаются — см. ISessionClient.GetInfoAsync).</returns>
public Task<TelegramSourceInfo> GetInfoAsync(string tenantId, string dialogId, CancellationToken cancellationToken)
public Task<TelegramSourceInfo> GetInfoAsync(
string tenantId,
string dialogId,
CancellationToken cancellationToken)
=> _sessionFarm.GetInfoAsync(tenantId, dialogId, cancellationToken);
/// <summary>
@@ -95,7 +105,11 @@ public sealed class DiscoveryOps
/// <param name="limit">Размер выборки (≤ 0 — дефолт; форумы читаются по активным темам).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>ok/сообщения либо ok=false/error="no_history" (не ошибка RPC).</returns>
public Task<DiscoveryReadResult> ReadForEvalAsync(string tenantId, string dialogId, int limit, CancellationToken cancellationToken)
public Task<DiscoveryReadResult> ReadForEvalAsync(
string tenantId,
string dialogId,
int limit,
CancellationToken cancellationToken)
=> _sessionFarm.ReadForEvalAsync(tenantId, dialogId, limit > 0 ? limit : ReadForEvalDefaultLimit, cancellationToken);
/// <summary>
@@ -106,7 +120,10 @@ public sealed class DiscoveryOps
/// <param name="tenantId">Id тенанта.</param>
/// <param name="username">Username источника («@»/пробелы нормализуются).</param>
/// <param name="cancellationToken">Отмена операции.</param>
public async Task JoinAsync(string tenantId, string username, CancellationToken cancellationToken)
public async Task JoinAsync(
string tenantId,
string username,
CancellationToken cancellationToken)
{
string normalized = NormalizeUsername(username);
if (normalized.Length == 0)
@@ -124,7 +141,10 @@ public sealed class DiscoveryOps
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Подписанный id диалога.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task LeaveAsync(string tenantId, string dialogId, CancellationToken cancellationToken)
public Task LeaveAsync(
string tenantId,
string dialogId,
CancellationToken cancellationToken)
=> _sessionFarm.LeaveAsync(tenantId, dialogId, cancellationToken);
/// <summary>
@@ -18,7 +18,10 @@ public sealed class SessionException : Exception
/// <param name="code">gRPC-статус ошибки (контракт telegram.proto, шапка файла).</param>
/// <param name="message">Текст причины — detail RPC (1:1 с текстами прототипа).</param>
/// <param name="innerException">Внутренняя причина (исключение Telegram/адаптера), если есть.</param>
public SessionException(StatusCode code, string message, Exception? innerException = null)
public SessionException(
StatusCode code,
string message,
Exception? innerException = null)
: base(message, innerException)
{
Code = code;
@@ -56,7 +56,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="limit">Верхняя граница числа диалогов.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<IReadOnlyList<TelegramDialog>> ListDialogsAsync(string tenantId, int limit, CancellationToken cancellationToken)
public Task<IReadOnlyList<TelegramDialog>> ListDialogsAsync(
string tenantId,
int limit,
CancellationToken cancellationToken)
=> RequireSession(tenantId).ListDialogsAsync(limit, cancellationToken);
/// <summary>
@@ -66,7 +69,11 @@ public sealed class SessionFarm
/// <param name="dialogId">Подписанный id диалога.</param>
/// <param name="limit">Сколько последних сообщений запросить.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(string tenantId, string dialogId, int limit, CancellationToken cancellationToken)
public Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(
string tenantId,
string dialogId,
int limit,
CancellationToken cancellationToken)
=> RequireSession(tenantId).GetMessagesAsync(dialogId, limit, cancellationToken);
/// <summary>
@@ -75,7 +82,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Подписанный id диалога.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task MarkReadAsync(string tenantId, string dialogId, CancellationToken cancellationToken)
public Task MarkReadAsync(
string tenantId,
string dialogId,
CancellationToken cancellationToken)
=> RequireSession(tenantId).MarkReadAsync(dialogId, cancellationToken);
/// <summary>
@@ -85,7 +95,11 @@ public sealed class SessionFarm
/// <param name="query">Поисковый запрос (ключ задачи discovery).</param>
/// <param name="limit">Верхняя граница результата.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<IReadOnlyList<TelegramDialog>> SearchAsync(string tenantId, string query, int limit, CancellationToken cancellationToken)
public Task<IReadOnlyList<TelegramDialog>> SearchAsync(
string tenantId,
string query,
int limit,
CancellationToken cancellationToken)
=> RequireSession(tenantId).SearchAsync(query, limit, cancellationToken);
/// <summary>
@@ -94,7 +108,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Подписанный id источника.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<TelegramSourceInfo> GetInfoAsync(string tenantId, string dialogId, CancellationToken cancellationToken)
public Task<TelegramSourceInfo> GetInfoAsync(
string tenantId,
string dialogId,
CancellationToken cancellationToken)
=> RequireSession(tenantId).GetInfoAsync(dialogId, cancellationToken);
/// <summary>
@@ -104,7 +121,11 @@ public sealed class SessionFarm
/// <param name="dialogId">Подписанный id источника.</param>
/// <param name="limit">Размер выборки.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<DiscoveryReadResult> ReadForEvalAsync(string tenantId, string dialogId, int limit, CancellationToken cancellationToken)
public Task<DiscoveryReadResult> ReadForEvalAsync(
string tenantId,
string dialogId,
int limit,
CancellationToken cancellationToken)
=> RequireSession(tenantId).ReadForEvalAsync(dialogId, limit, cancellationToken);
/// <summary>
@@ -113,7 +134,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="username">Username (без «@»; нормализует DiscoveryOps).</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task JoinAsync(string tenantId, string username, CancellationToken cancellationToken)
public Task JoinAsync(
string tenantId,
string username,
CancellationToken cancellationToken)
=> RequireSession(tenantId).JoinAsync(username, cancellationToken);
/// <summary>
@@ -122,7 +146,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="dialogId">Подписанный id диалога.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task LeaveAsync(string tenantId, string dialogId, CancellationToken cancellationToken)
public Task LeaveAsync(
string tenantId,
string dialogId,
CancellationToken cancellationToken)
=> RequireSession(tenantId).LeaveAsync(dialogId, cancellationToken);
/// <summary>
@@ -133,7 +160,12 @@ public sealed class SessionFarm
/// <param name="apiHash">api_hash приложения.</param>
/// <param name="phone">Номер телефона.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<TenantSessionSnapshot> StartPhoneAsync(string tenantId, int apiId, string apiHash, string phone, CancellationToken cancellationToken)
public Task<TenantSessionSnapshot> StartPhoneAsync(
string tenantId,
int apiId,
string apiHash,
string phone,
CancellationToken cancellationToken)
=> GetOrCreate(tenantId).StartPhoneAsync(apiId, apiHash, phone, cancellationToken);
/// <summary>
@@ -143,7 +175,11 @@ public sealed class SessionFarm
/// <param name="apiId">api_id приложения.</param>
/// <param name="apiHash">api_hash приложения.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<TenantSessionSnapshot> StartQrAsync(string tenantId, int apiId, string apiHash, CancellationToken cancellationToken)
public Task<TenantSessionSnapshot> StartQrAsync(
string tenantId,
int apiId,
string apiHash,
CancellationToken cancellationToken)
=> GetOrCreate(tenantId).StartQrAsync(apiId, apiHash, cancellationToken);
/// <summary>
@@ -152,7 +188,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="code">Код.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<TenantSessionSnapshot> SendCodeAsync(string tenantId, string code, CancellationToken cancellationToken)
public Task<TenantSessionSnapshot> SendCodeAsync(
string tenantId,
string code,
CancellationToken cancellationToken)
=> RequireSession(tenantId).SendCodeAsync(code, cancellationToken);
/// <summary>
@@ -161,7 +200,10 @@ public sealed class SessionFarm
/// <param name="tenantId">Id тенанта.</param>
/// <param name="password">Пароль.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public Task<TenantSessionSnapshot> SendPasswordAsync(string tenantId, string password, CancellationToken cancellationToken)
public Task<TenantSessionSnapshot> SendPasswordAsync(
string tenantId,
string password,
CancellationToken cancellationToken)
=> RequireSession(tenantId).SendPasswordAsync(password, cancellationToken);
/// <summary>
@@ -32,7 +32,10 @@ public sealed class SessionStore
/// <param name="options">Опции хранения сессий (каталог, ключ шифрования).</param>
/// <param name="cipher">AES-GCM-обёртка файлов сессий.</param>
/// <param name="logger">Логгер.</param>
public SessionStore(TgOptions options, SessionFileCipher cipher, ILogger<SessionStore> logger)
public SessionStore(
TgOptions options,
SessionFileCipher cipher,
ILogger<SessionStore> logger)
{
_sessionsDirectory = options.SessionsDirectory;
_cipher = cipher;
@@ -101,7 +104,10 @@ public sealed class SessionStore
/// <param name="tenantId">Id тенанта.</param>
/// <param name="session">Открытое содержимое сессии для шифрования at-rest.</param>
/// <param name="cancellationToken">Отмена операции.</param>
public async Task SaveAsync(string tenantId, StoredSession session, CancellationToken cancellationToken = default)
public async Task SaveAsync(
string tenantId,
StoredSession session,
CancellationToken cancellationToken = default)
{
string filePath = PathFor(tenantId);
byte[] plainBytes = JsonSerializer.SerializeToUtf8Bytes(session);
@@ -90,7 +90,11 @@ public sealed class TenantSession : IAsyncDisposable
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Снимок состояния после операции (фаза "code").</returns>
/// <exception cref="SessionException">Нет ключей (INVALID_ARGUMENT) / ошибки Telegram.</exception>
public async Task<TenantSessionSnapshot> StartPhoneAsync(int apiId, string apiHash, string phone, CancellationToken cancellationToken)
public async Task<TenantSessionSnapshot> StartPhoneAsync(
int apiId,
string apiHash,
string phone,
CancellationToken cancellationToken)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -148,7 +152,10 @@ public sealed class TenantSession : IAsyncDisposable
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Снимок состояния: Qr с qrUrl или Ready (авторизация уже была).</returns>
/// <exception cref="SessionException">Нет ключей (INVALID_ARGUMENT) / ошибки Telegram до первого URL.</exception>
public async Task<TenantSessionSnapshot> StartQrAsync(int apiId, string apiHash, CancellationToken cancellationToken)
public async Task<TenantSessionSnapshot> StartQrAsync(
int apiId,
string apiHash,
CancellationToken cancellationToken)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -410,7 +417,10 @@ public sealed class TenantSession : IAsyncDisposable
/// <param name="limit">Сколько последних сообщений запросить.</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Сообщения диалога (от новых к старым, непустые тексты).</returns>
public async Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken)
public async Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(
string dialogId,
int limit,
CancellationToken cancellationToken)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -452,7 +462,10 @@ public sealed class TenantSession : IAsyncDisposable
/// <param name="limit">Верхняя граница результата.</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Найденные источники (нейтральный вид; личные чаты отсеивает ядро, Ruling 10).</returns>
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(string query, int limit, CancellationToken cancellationToken)
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(
string query,
int limit,
CancellationToken cancellationToken)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -493,7 +506,10 @@ public sealed class TenantSession : IAsyncDisposable
/// <param name="limit">Размер выборки (limit ≤ 0 — пусто без сети).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Результат чтения (ok/messages либо no_history).</returns>
public async Task<DiscoveryReadResult> ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken)
public async Task<DiscoveryReadResult> ReadForEvalAsync(
string dialogId,
int limit,
CancellationToken cancellationToken)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -812,7 +828,10 @@ public sealed class TenantSession : IAsyncDisposable
// apiId: api_id приложения.
// apiHash: api_hash приложения.
// cancellationToken: Отмена операции.
private async Task EnsureClientAsync(int apiId, string apiHash, CancellationToken cancellationToken)
private async Task EnsureClientAsync(
int apiId,
string apiHash,
CancellationToken cancellationToken)
{
if (_client is not null && _clientApiId == apiId && _clientApiHash == apiHash)
{
@@ -988,7 +1007,10 @@ public sealed class TenantSession : IAsyncDisposable
// firstUrlTcs: TCS первого URL.
// cancellationToken: Отмена операции.
// Возвращает: Первый URL либо null (задача завершилась без URL).
private static async Task<string?> WaitForFirstQrUrlAsync(Task qrTask, TaskCompletionSource<string?> firstUrlTcs, CancellationToken cancellationToken)
private static async Task<string?> WaitForFirstQrUrlAsync(
Task qrTask,
TaskCompletionSource<string?> firstUrlTcs,
CancellationToken cancellationToken)
{
Task<string?> urlTask = firstUrlTcs.Task;
Task completed = await Task.WhenAny(urlTask, qrTask).WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -12,6 +12,9 @@ namespace Deal.Telegram.Telegram;
public sealed class ClientFactory : ITelegramClientFactory
{
/// <inheritdoc />
public ISessionClient Create(int apiId, string apiHash, byte[]? storedSession)
public ISessionClient Create(
int apiId,
string apiHash,
byte[]? storedSession)
=> new WTelegramSessionClient(apiId, apiHash, storedSession);
}
@@ -18,7 +18,12 @@ public sealed record DiscoveryMessage
/// <param name="dateMs">Время сообщения, epoch-ms.</param>
/// <param name="topicId">Id темы форума (для обычных источников пуст).</param>
/// <param name="topicTitle">Название темы форума (для обычных источников пусто).</param>
public DiscoveryMessage(long id, string text, long dateMs, long? topicId, string? topicTitle)
public DiscoveryMessage(
long id,
string text,
long dateMs,
long? topicId,
string? topicTitle)
{
Id = id;
Text = text;
@@ -25,7 +25,10 @@ public sealed record DiscoveryReadResult
/// <param name="ok">True — выборка получена; false — история недоступна.</param>
/// <param name="error">Код причины при ok=false ("no_history"); иначе null.</param>
/// <param name="messages">Сообщения выборки (форумы — по темам, topic_id/topic_title заполнены).</param>
public DiscoveryReadResult(bool ok, string? error, IReadOnlyList<DiscoveryMessage> messages)
public DiscoveryReadResult(
bool ok,
string? error,
IReadOnlyList<DiscoveryMessage> messages)
{
Ok = ok;
Error = error;
@@ -114,7 +114,10 @@ public interface ISessionClient : IAsyncDisposable
/// <param name="limit">Сколько последних сообщений запросить.</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Сообщения диалога (с канальными полями для PushMessage).</returns>
public Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken);
public Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(
string dialogId,
int limit,
CancellationToken cancellationToken);
/// <summary>
/// Помечает весь диалог прочитанным (send_read_acknowledge прототипа L277/L383/L454/L609).
@@ -145,7 +148,10 @@ public interface ISessionClient : IAsyncDisposable
/// <param name="limit">Верхняя граница результата (прототип: default 30).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Найденные источники в нейтральном виде (каналы/группы/личные).</returns>
public Task<IReadOnlyList<TelegramDialog>> SearchAsync(string query, int limit, CancellationToken cancellationToken);
public Task<IReadOnlyList<TelegramDialog>> SearchAsync(
string query,
int limit,
CancellationToken cancellationToken);
/// <summary>
/// Инфо об источнике для оценки кандидата (discovery_info L666–716): имя/username/kind + участники
@@ -169,7 +175,10 @@ public interface ISessionClient : IAsyncDisposable
/// <param name="limit">Размер выборки (прототип: limit сообщений/тем; limit ≤ 0 — пусто без сети).</param>
/// <param name="cancellationToken">Отмена операции.</param>
/// <returns>Результат чтения выборки (ok + сообщения либо no_history).</returns>
public Task<DiscoveryReadResult> ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken);
public Task<DiscoveryReadResult> ReadForEvalAsync(
string dialogId,
int limit,
CancellationToken cancellationToken);
/// <summary>
/// Вступить в канал/группу по username (discovery_join L818–839; ручной join вне квот — паузу перед
@@ -19,5 +19,8 @@ public interface ITelegramClientFactory
/// либо null — новая сессия (нет файла или ключи приложения сменились).
/// </param>
/// <returns>Клиент, готовый к ConnectAsync/логину.</returns>
public ISessionClient Create(int apiId, string apiHash, byte[]? storedSession);
public ISessionClient Create(
int apiId,
string apiHash,
byte[]? storedSession);
}
@@ -20,7 +20,13 @@ public sealed record TelegramDialog
/// <param name="kind">Тип канона: channel|group|forum|chat (шапка telegram.proto).</param>
/// <param name="unreadCount">Число непрочитанных сообщений (для realtime_sweep).</param>
/// <param name="topMessageId">Id самого свежего сообщения диалога (для read-ack).</param>
public TelegramDialog(string id, string name, string username, string kind, int unreadCount, int topMessageId)
public TelegramDialog(
string id,
string name,
string username,
string kind,
int unreadCount,
int topMessageId)
{
Id = id;
Name = name;
@@ -19,7 +19,13 @@ public sealed record TelegramMessage
/// <param name="dateMs">Время сообщения, epoch-ms.</param>
/// <param name="dialogName">Имя диалога (title/first_name) для PushMessage.channel_name.</param>
/// <param name="dialogHandle">Username диалога для PushMessage.channel_handle.</param>
public TelegramMessage(string dialogId, int id, string text, long dateMs, string dialogName, string dialogHandle)
public TelegramMessage(
string dialogId,
int id,
string text,
long dateMs,
string dialogName,
string dialogHandle)
{
DialogId = dialogId;
Id = id;
@@ -19,7 +19,13 @@ public sealed record TelegramSourceInfo
/// <param name="kind">Тип канона контракта: channel|group|forum|chat (пусто — не определён).</param>
/// <param name="participants">Число участников (full_chat); null — определить не удалось.</param>
/// <param name="isForum">True — мегагруппа с темами (форум; core трактует kind как forum).</param>
public TelegramSourceInfo(string id, string name, string username, string kind, int? participants, bool isForum)
public TelegramSourceInfo(
string id,
string name,
string username,
string kind,
int? participants,
bool isForum)
{
Id = id;
Name = name;
@@ -32,7 +32,10 @@ public static class TlMessageMapper
/// <param name="dialog">Диалог из ответа getDialogs.</param>
/// <param name="chats">Сущности чатов контейнера (по raw id).</param>
/// <param name="users">Сущности пользователей контейнера (по raw id).</param>
public static TelegramDialog? ToDialog(DialogBase dialog, IReadOnlyDictionary<long, ChatBase> chats, IReadOnlyDictionary<long, User> users)
public static TelegramDialog? ToDialog(
DialogBase dialog,
IReadOnlyDictionary<long, ChatBase> chats,
IReadOnlyDictionary<long, User> users)
{
if (dialog.Peer is null)
{
@@ -53,7 +56,10 @@ public static class TlMessageMapper
/// <param name="message">Сообщение (MessageBase).</param>
/// <param name="chats">Сущности чатов контейнера.</param>
/// <param name="users">Сущности пользователей контейнера.</param>
public static TelegramMessage? ToMessage(MessageBase message, IReadOnlyDictionary<long, ChatBase> chats, IReadOnlyDictionary<long, User> users)
public static TelegramMessage? ToMessage(
MessageBase message,
IReadOnlyDictionary<long, ChatBase> chats,
IReadOnlyDictionary<long, User> users)
{
if (message is not Message textMessage
|| string.IsNullOrWhiteSpace(textMessage.message)
@@ -103,7 +109,11 @@ public static class TlMessageMapper
/// <param name="topicId">Id темы форума (для обычных источников null).</param>
/// <param name="topicTitle">Название темы форума (для обычных источников null).</param>
/// <param name="now">Текущее время (фолбэк даты сообщения без времени, как python `now`).</param>
public static DiscoveryMessage? ToEvalMessage(MessageBase message, long? topicId, string? topicTitle, DateTimeOffset now)
public static DiscoveryMessage? ToEvalMessage(
MessageBase message,
long? topicId,
string? topicTitle,
DateTimeOffset now)
{
if (message is not Message textMessage
|| string.IsNullOrWhiteSpace(textMessage.message))
@@ -136,7 +146,10 @@ public static class TlMessageMapper
// signedId: Подписанный id диалога (фолбэк имени).
// chat: Сущность чата/канала (или null).
// user: Сущность пользователя (или null).
private static (string Name, string Handle, string Kind) DescribePeer(string signedId, ChatBase? chat, UserBase? user)
private static (string Name, string Handle, string Kind) DescribePeer(
string signedId,
ChatBase? chat,
UserBase? user)
{
if (chat is not null)
{
@@ -73,7 +73,10 @@ public sealed class WTelegramSessionClient : ISessionClient
/// <param name="apiId">api_id приложения Telegram.</param>
/// <param name="apiHash">api_hash приложения Telegram (же ключ внутреннего шифрования сессии).</param>
/// <param name="storedSession">Сохранённые байты сессии (null — новая сессия).</param>
public WTelegramSessionClient(int apiId, string apiHash, byte[]? storedSession)
public WTelegramSessionClient(
int apiId,
string apiHash,
byte[]? storedSession)
{
_apiId = apiId;
_apiHash = apiHash;
@@ -257,7 +260,10 @@ public sealed class WTelegramSessionClient : ISessionClient
}
/// <inheritdoc />
public async Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(string dialogId, int limit, CancellationToken cancellationToken)
public async Task<IReadOnlyList<TelegramMessage>> GetMessagesAsync(
string dialogId,
int limit,
CancellationToken cancellationToken)
{
InputPeer peer = await ResolvePeerAsync(dialogId, cancellationToken).ConfigureAwait(false);
Messages_MessagesBase result = await RunTlCallAsync(() => _client.Messages_GetHistory(peer, limit: limit), cancellationToken).ConfigureAwait(false);
@@ -289,7 +295,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// --- Discovery (план Task 11; discovery_search/info/read/join/leave L622–873; Ruling 3/7) ---
/// <inheritdoc />
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(string query, int limit, CancellationToken cancellationToken)
public async Task<IReadOnlyList<TelegramDialog>> SearchAsync(
string query,
int limit,
CancellationToken cancellationToken)
{
// contacts.SearchRequest(q, limit): результат — сущности chats/users (поиск каналов/групп/людей).
Contacts_Found found = await RunTlCallAsync(() => _client.Contacts_Search(query, limit), cancellationToken).ConfigureAwait(false);
@@ -347,7 +356,10 @@ public sealed class WTelegramSessionClient : ISessionClient
}
/// <inheritdoc />
public async Task<DiscoveryReadResult> ReadForEvalAsync(string dialogId, int limit, CancellationToken cancellationToken)
public async Task<DiscoveryReadResult> ReadForEvalAsync(
string dialogId,
int limit,
CancellationToken cancellationToken)
{
// discovery_read L718–760: limit ≤ 0 — пустой ok без сетевых вызовов (L730–732).
if (limit <= 0)
@@ -431,7 +443,11 @@ public sealed class WTelegramSessionClient : ISessionClient
// rawId: Raw id канала.
// unknown: Инфо по умолчанию (сущность недоступна).
// cancellationToken: Отмена операции.
private async Task<TelegramSourceInfo> GetChannelInfoAsync(string dialogId, long rawId, TelegramSourceInfo unknown, CancellationToken cancellationToken)
private async Task<TelegramSourceInfo> GetChannelInfoAsync(
string dialogId,
long rawId,
TelegramSourceInfo unknown,
CancellationToken cancellationToken)
{
Channel? cached;
lock (_entityCacheGate)
@@ -465,7 +481,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// dialogId: Подписанный id.
// rawId: Raw id канала.
// cancellationToken: Отмена операции.
private async Task<int?> TryFetchChannelParticipantsAsync(string dialogId, long rawId, CancellationToken cancellationToken)
private async Task<int?> TryFetchChannelParticipantsAsync(
string dialogId,
long rawId,
CancellationToken cancellationToken)
{
try
{
@@ -485,7 +504,11 @@ public sealed class WTelegramSessionClient : ISessionClient
// rawId: Raw id группы (положительный).
// unknown: Инфо по умолчанию.
// cancellationToken: Отмена операции.
private async Task<TelegramSourceInfo> GetChatInfoAsync(string dialogId, long rawId, TelegramSourceInfo unknown, CancellationToken cancellationToken)
private async Task<TelegramSourceInfo> GetChatInfoAsync(
string dialogId,
long rawId,
TelegramSourceInfo unknown,
CancellationToken cancellationToken)
{
Chat? cached;
lock (_entityCacheGate)
@@ -537,7 +560,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// dialogId: Подписанный id.
// group: Entity группы.
// participants: Число участников (null — определить не удалось).
private static TelegramSourceInfo DescribeGroup(string dialogId, Chat group, int? participants)
private static TelegramSourceInfo DescribeGroup(
string dialogId,
Chat group,
int? participants)
=> new(
dialogId,
DisplayName(dialogId, group),
@@ -550,7 +576,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// dialogId: Подписанный id.
// rawId: Raw id пользователя.
// unknown: Инфо по умолчанию (сущность неизвестна — как entity-not-found прототипа).
private TelegramSourceInfo GetUserInfo(string dialogId, long rawId, TelegramSourceInfo unknown)
private TelegramSourceInfo GetUserInfo(
string dialogId,
long rawId,
TelegramSourceInfo unknown)
{
User? user;
lock (_entityCacheGate)
@@ -578,7 +607,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// limit: Размер выборки (раскладывается по темам).
// cancellationToken: Отмена операции.
// Возвращает: Плоский список сообщений тем (пуст — темы не прочитались; фолбэк на ленту делает вызывающий).
private async Task<IReadOnlyList<DiscoveryMessage>> ReadForumTopicsAsync(InputPeer peer, int limit, CancellationToken cancellationToken)
private async Task<IReadOnlyList<DiscoveryMessage>> ReadForumTopicsAsync(
InputPeer peer,
int limit,
CancellationToken cancellationToken)
{
var outMessages = new List<DiscoveryMessage>();
try
@@ -659,7 +691,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// dialogId: Подписанный id.
// channel: Entity канала из ответа полного чата.
// participants: Число участников (full_chat) либо null.
private static TelegramSourceInfo DescribeChannel(string dialogId, Channel channel, int? participants)
private static TelegramSourceInfo DescribeChannel(
string dialogId,
Channel channel,
int? participants)
=> new(
dialogId,
DisplayName(dialogId, channel),
@@ -952,7 +987,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// dialogId: Подписанный id канала («-100…»).
// rawId: Raw id канала.
// cancellationToken: Отмена операции.
private async Task<InputChannel> ResolveInputChannelAsync(string dialogId, long rawId, CancellationToken cancellationToken)
private async Task<InputChannel> ResolveInputChannelAsync(
string dialogId,
long rawId,
CancellationToken cancellationToken)
{
lock (_entityCacheGate)
{
@@ -976,7 +1014,10 @@ public sealed class WTelegramSessionClient : ISessionClient
// isChannel: Признак канала («-100…»).
// isUser: Признак личного чата («+…»).
// rawId: Raw id (канала/пользователя).
private bool TryImportPeerFromCollector(bool isChannel, bool isUser, long rawId)
private bool TryImportPeerFromCollector(
bool isChannel,
bool isUser,
long rawId)
{
if (!isChannel && !isUser)
{
@@ -1022,7 +1063,11 @@ public sealed class WTelegramSessionClient : ISessionClient
// isChat: Признак базовой группы («-…»).
// isUser: Признак личного чата («+…»).
// rawId: Raw id (канала/группы/пользователя).
private InputPeer? BuildPeer(bool isChannel, bool isChat, bool isUser, long rawId)
private InputPeer? BuildPeer(
bool isChannel,
bool isChat,
bool isUser,
long rawId)
{
if (isChat)
{
@@ -1050,7 +1095,12 @@ public sealed class WTelegramSessionClient : ISessionClient
// isChat: True — базовая группа.
// isUser: True — личный чат.
// rawId: Raw id без префикса.
private static bool TryParseSignedId(string dialogId, out bool isChannel, out bool isChat, out bool isUser, out long rawId)
private static bool TryParseSignedId(
string dialogId,
out bool isChannel,
out bool isChat,
out bool isUser,
out long rawId)
{
isChannel = false;
isChat = false;
@@ -473,7 +473,10 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase
// tenantId: Id тенанта.
// entries: Актуальный каталог диалогов.
// cancellationToken: Отмена операции.
private async Task SyncCatalogSilentlyAsync(string tenantId, IReadOnlyList<DialogEntry> entries, CancellationToken cancellationToken)
private async Task SyncCatalogSilentlyAsync(
string tenantId,
IReadOnlyList<DialogEntry> entries,
CancellationToken cancellationToken)
{
try
{