refactor: add platform messenger contracts
PR Checks / test-and-build (pull_request) Successful in 12m35s
PR Checks / test-and-build (pull_request) Successful in 12m35s
Introduce platform-neutral PlatformKind, PlatformUser, PlatformGroup, and IPlatformMessenger contracts in GmRelay.Shared. Route Telegram session schedule updates, direct notifications, interaction replies, and calendar export through TelegramPlatformMessenger while preserving existing Telegram behavior. Bump version -> 2.0.1
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
using System.Globalization;
|
||||
using GmRelay.Shared.Platform;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegram.Bot.Types.ReplyMarkups;
|
||||
|
||||
namespace GmRelay.Bot.Infrastructure.Telegram;
|
||||
|
||||
public sealed class TelegramPlatformMessenger(
|
||||
ITelegramBotClient bot,
|
||||
ILogger<TelegramPlatformMessenger> logger) : IPlatformMessenger
|
||||
{
|
||||
public async Task<PlatformMessageRef> SendScheduleAsync(PlatformScheduleMessage message, CancellationToken ct)
|
||||
{
|
||||
EnsureTelegram(message.Group.Platform);
|
||||
|
||||
var chatId = ParseLong(message.Group.ExternalGroupId);
|
||||
var threadId = ParseNullableInt(message.Group.ExternalThreadId);
|
||||
var renderResult = TelegramSessionBatchRenderer.Render(message.View);
|
||||
Message sentMessage;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(message.ImageReference) && renderResult.Text.Length <= 1024)
|
||||
{
|
||||
try
|
||||
{
|
||||
sentMessage = await bot.SendPhoto(
|
||||
chatId: chatId,
|
||||
messageThreadId: threadId,
|
||||
photo: InputFile.FromString(message.ImageReference),
|
||||
caption: renderResult.Text,
|
||||
parseMode: ParseMode.Html,
|
||||
replyMarkup: renderResult.Markup,
|
||||
cancellationToken: ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to send Telegram schedule image for group {ExternalGroupId}", message.Group.ExternalGroupId);
|
||||
sentMessage = await SendScheduleTextMessage(chatId, threadId, renderResult.Text, renderResult.Markup, ct);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(message.ImageReference))
|
||||
{
|
||||
await TrySendScheduleImageOnly(chatId, threadId, message.View.Title, message.ImageReference, ct);
|
||||
}
|
||||
|
||||
sentMessage = await SendScheduleTextMessage(chatId, threadId, renderResult.Text, renderResult.Markup, ct);
|
||||
}
|
||||
|
||||
return new PlatformMessageRef(
|
||||
PlatformKind.Telegram,
|
||||
message.Group.ExternalGroupId,
|
||||
message.Group.ExternalThreadId,
|
||||
sentMessage.MessageId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public async Task UpdateScheduleAsync(PlatformScheduleMessage message, CancellationToken ct)
|
||||
{
|
||||
EnsureTelegram(message.Group.Platform);
|
||||
if (message.ExistingMessage is null)
|
||||
{
|
||||
throw new ArgumentException("Existing schedule message reference is required.", nameof(message));
|
||||
}
|
||||
|
||||
var renderResult = TelegramSessionBatchRenderer.Render(message.View);
|
||||
await BatchMessageEditor.EditBatchMessageAsync(
|
||||
bot,
|
||||
chatId: ParseLong(message.Group.ExternalGroupId),
|
||||
messageId: ParseInt(message.ExistingMessage.ExternalMessageId),
|
||||
text: renderResult.Text,
|
||||
replyMarkup: renderResult.Markup,
|
||||
ct);
|
||||
}
|
||||
|
||||
public Task SendGroupMessageAsync(PlatformGroup group, string htmlText, CancellationToken ct)
|
||||
{
|
||||
EnsureTelegram(group.Platform);
|
||||
return bot.SendMessage(
|
||||
chatId: ParseLong(group.ExternalGroupId),
|
||||
messageThreadId: ParseNullableInt(group.ExternalThreadId),
|
||||
text: htmlText,
|
||||
parseMode: ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
public Task SendPrivateMessageAsync(PlatformPrivateMessage message, CancellationToken ct)
|
||||
{
|
||||
EnsureTelegram(message.Recipient.Platform);
|
||||
return bot.SendMessage(
|
||||
chatId: ParseLong(message.Recipient.ExternalUserId),
|
||||
text: message.HtmlText,
|
||||
parseMode: ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
public Task AnswerInteractionAsync(PlatformInteractionReply reply, CancellationToken ct) =>
|
||||
bot.AnswerCallbackQuery(
|
||||
callbackQueryId: reply.InteractionId,
|
||||
text: reply.Text,
|
||||
showAlert: reply.ShowAlert,
|
||||
cancellationToken: ct);
|
||||
|
||||
public async Task SendCalendarFileAsync(PlatformCalendarFile file, CancellationToken ct)
|
||||
{
|
||||
EnsureTelegram(file.Group.Platform);
|
||||
|
||||
using var stream = new MemoryStream(file.Content);
|
||||
await bot.SendDocument(
|
||||
chatId: ParseLong(file.Group.ExternalGroupId),
|
||||
messageThreadId: ParseNullableInt(file.Group.ExternalThreadId),
|
||||
document: InputFile.FromStream(stream, file.FileName),
|
||||
caption: file.CaptionHtml,
|
||||
parseMode: ParseMode.Html,
|
||||
replyMarkup: BuildActionsMarkup(file.Actions),
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
private async Task<Message> SendScheduleTextMessage(
|
||||
long chatId,
|
||||
int? threadId,
|
||||
string text,
|
||||
InlineKeyboardMarkup markup,
|
||||
CancellationToken ct) =>
|
||||
await bot.SendMessage(
|
||||
chatId: chatId,
|
||||
messageThreadId: threadId,
|
||||
text: text,
|
||||
parseMode: ParseMode.Html,
|
||||
replyMarkup: markup,
|
||||
cancellationToken: ct);
|
||||
|
||||
private async Task TrySendScheduleImageOnly(
|
||||
long chatId,
|
||||
int? threadId,
|
||||
string title,
|
||||
string imageReference,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await bot.SendPhoto(
|
||||
chatId: chatId,
|
||||
messageThreadId: threadId,
|
||||
photo: InputFile.FromString(imageReference),
|
||||
caption: $"🎲 {System.Net.WebUtility.HtmlEncode(title)}",
|
||||
parseMode: ParseMode.Html,
|
||||
cancellationToken: ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to send Telegram schedule image for chat {ChatId}", chatId);
|
||||
}
|
||||
}
|
||||
|
||||
private static InlineKeyboardMarkup? BuildActionsMarkup(IReadOnlyList<PlatformMessageAction> actions)
|
||||
{
|
||||
if (actions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new InlineKeyboardMarkup(
|
||||
actions.Select(action => new[]
|
||||
{
|
||||
Uri.TryCreate(action.Payload, UriKind.Absolute, out var uri) &&
|
||||
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
||||
? InlineKeyboardButton.WithUrl(action.Label, action.Payload)
|
||||
: InlineKeyboardButton.WithCallbackData(action.Label, action.Payload)
|
||||
}));
|
||||
}
|
||||
|
||||
private static void EnsureTelegram(PlatformKind platform)
|
||||
{
|
||||
if (platform != PlatformKind.Telegram)
|
||||
{
|
||||
throw new NotSupportedException($"Telegram messenger cannot send messages for platform {platform}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static long ParseLong(string value) => long.Parse(value, CultureInfo.InvariantCulture);
|
||||
|
||||
private static int ParseInt(string value) => int.Parse(value, CultureInfo.InvariantCulture);
|
||||
|
||||
private static int? ParseNullableInt(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : int.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
Reference in New Issue
Block a user