Добавить remote-просмотр исходника карточки
RPC TelegramService.ReadSource + ISessionClient.GetMessageAsync, порт ITelegramGateway.ReadSourceAsync и провайдер TelegramSourceContentProvider в ядре; GET /api/cards/{id}/source догружает исходник у сервиса-владельца, в UI — кнопка «Обновить из источника». Медиа-посты пропускаются.
This commit is contained in:
@@ -92,6 +92,11 @@ public static class SessionErrorMessages
|
||||
/// </summary>
|
||||
public const string InvalidDialogId = "Некорректный id источника";
|
||||
|
||||
/// <summary>
|
||||
/// Неположительный id сообщения в запросе
|
||||
/// </summary>
|
||||
public const string InvalidMessageId = "Некорректный id сообщения";
|
||||
|
||||
/// <summary>
|
||||
/// Телефон не зарегистрирован в Telegram
|
||||
/// </summary>
|
||||
|
||||
@@ -73,6 +73,20 @@ public sealed class SessionFarm
|
||||
CancellationToken cancellationToken)
|
||||
=> RequireSession(tenantId).GetMessagesAsync(dialogId, limit, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Исходное сообщение диалога тенанта по id.
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <param name="dialogId">Подписанный id диалога.</param>
|
||||
/// <param name="msgId">Id сообщения в Telegram.</param>
|
||||
/// <param name="cancellationToken">Отмена операции.</param>
|
||||
public Task<TelegramMessage?> GetMessageAsync(
|
||||
string tenantId,
|
||||
string dialogId,
|
||||
long msgId,
|
||||
CancellationToken cancellationToken)
|
||||
=> RequireSession(tenantId).GetMessageAsync(dialogId, msgId, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Помечает диалог тенанта прочитанным.
|
||||
/// </summary>
|
||||
|
||||
@@ -416,6 +416,30 @@ public sealed class TenantSession : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исходное сообщение диалога по id (remote-просмотр).
|
||||
/// </summary>
|
||||
/// <param name="dialogId">Подписанный id диалога.</param>
|
||||
/// <param name="msgId">Id сообщения в Telegram.</param>
|
||||
/// <param name="cancellationToken">Отмена операции.</param>
|
||||
/// <returns>Сообщение с текстом либо null (медиа без текста/не найдено).</returns>
|
||||
public async Task<TelegramMessage?> GetMessageAsync(
|
||||
string dialogId,
|
||||
long msgId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
ISessionClient client = await EnsureReadyConnectedAsync(cancellationToken).ConfigureAwait(false);
|
||||
return await client.GetMessageAsync(dialogId, msgId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Помечает диалог прочитанным
|
||||
/// </summary>
|
||||
|
||||
@@ -98,6 +98,18 @@ public interface ISessionClient : IAsyncDisposable
|
||||
int limit,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает исходное сообщение диалога по id (remote-просмотр карточки).
|
||||
/// </summary>
|
||||
/// <param name="dialogId">Подписанный id диалога (каналы "-100…", группы "-…", личные "+…").</param>
|
||||
/// <param name="msgId">Id сообщения в Telegram.</param>
|
||||
/// <param name="cancellationToken">Отмена операции.</param>
|
||||
/// <returns>Сообщение с текстом либо null (медиа без текста/не найдено).</returns>
|
||||
public Task<TelegramMessage?> GetMessageAsync(
|
||||
string dialogId,
|
||||
long msgId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Помечает весь диалог прочитанным.
|
||||
/// </summary>
|
||||
|
||||
@@ -53,6 +53,7 @@ public static class TlMessageMapper
|
||||
{
|
||||
if (message is not Message textMessage
|
||||
|| string.IsNullOrWhiteSpace(textMessage.message)
|
||||
|| textMessage.media is not null
|
||||
|| (textMessage.flags & Message.Flags.out_) != 0
|
||||
|| textMessage.Peer is null)
|
||||
{
|
||||
|
||||
@@ -264,6 +264,36 @@ public sealed class WTelegramSessionClient : ISessionClient
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TelegramMessage?> GetMessageAsync(
|
||||
string dialogId,
|
||||
long msgId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Диалог обязан быть доступен аккаунту (иначе INVALID_ARGUMENT, как в истории).
|
||||
await ResolvePeerAsync(dialogId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// messages.getMessages по голому id (InputMessageID) — без привязки к peer.
|
||||
InputMessage[] ids = [new InputMessageID { id = (int)msgId }];
|
||||
Messages_MessagesBase result = await RunTlCallAsync(
|
||||
() => _client.Messages_GetMessages(ids),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
(MessageBase[] messages, Dictionary<long, ChatBase> chats, Dictionary<long, User> users) = UnpackMessages(result);
|
||||
CacheEntities(chats.Values, users.Values);
|
||||
|
||||
foreach (MessageBase message in messages)
|
||||
{
|
||||
TelegramMessage? mapped = TlMessageMapper.ToMessage(message, chats, users);
|
||||
if (mapped is not null && mapped.Id == msgId && mapped.DialogId == dialogId)
|
||||
{
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
|
||||
// Медиа без текста или удалённое сообщение — не найдено (found=false).
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MarkReadAsync(string dialogId, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -291,6 +291,44 @@ public sealed class TelegramServiceImpl : TelegramService.TelegramServiceBase
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ReadSource — исходное сообщение источника по id (remote-просмотр).
|
||||
/// </summary>
|
||||
public override async Task<ReadSourceReply> ReadSource(ReadSourceRequest request, ServerCallContext context)
|
||||
{
|
||||
string tenantId = RequireTenantId(context);
|
||||
RequireDialogId(request.DialogId);
|
||||
if (request.MsgId <= 0)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, SessionErrorMessages.InvalidMessageId));
|
||||
}
|
||||
|
||||
ReadSourceReply reply = await ExecuteAsync(
|
||||
tenantId,
|
||||
async ct =>
|
||||
{
|
||||
Telegram.TelegramMessage? message = await _sessionFarm
|
||||
.GetMessageAsync(tenantId, request.DialogId, request.MsgId, ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (message is null)
|
||||
{
|
||||
return new ReadSourceReply { Found = false };
|
||||
}
|
||||
|
||||
return new ReadSourceReply
|
||||
{
|
||||
Found = true,
|
||||
Text = message.Text,
|
||||
Time = message.DateMs,
|
||||
};
|
||||
},
|
||||
"read_source",
|
||||
context).ConfigureAwait(false);
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Search — глобальный поиск каналов/групп по ключу.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user