using BotPages.Core.Abstractions; namespace BotPages.Core.Messaging; /// /// Fluent‑билдер для отправки сообщений (текст, кнопки, файлы, альбомы, прогресс). /// Поддерживает указание адаптер-специфичных опций через . /// public sealed class MessageBuilder { private readonly PageContext _ctx; private string? _text = null; private MessageFormat _format = MessageFormat.Plain; private readonly List> _inline = new(); private readonly List> _reply = new(); private readonly List<(FileDescriptor file, string? caption, MessageFormat? captionFormat)> _files = new(); private readonly List<(FileDescriptor file, string? caption, MessageFormat? captionFormat)> _album = new(); private bool _disableReplyKeyboard; private AdapterOptionsBag? _adapterOptions = null; private string? _replyToMessageId = null; private bool _quoteReply = true; private string? _quoteTitle = null; private bool _disableWebPagePreview = false; private bool _disableNotification = false; private bool _protectContent = false; private MessageStyle? _style = null; private DateTime? _scheduleDate = null; private string? _topic = null; private string? _messageId = null; private IMessengerAdapter? _targetAdapter = null; private string? _targetChatId = null; /// Создать билдер сообщений. public MessageBuilder(PageContext ctx) => _ctx = ctx; /// /// Отправить сообщение через указанный адаптер. /// public MessageBuilder WithAdapter(IMessengerAdapter adapter) { _targetAdapter = adapter; return this; } /// /// Отправить сообщение через адаптер по ID. /// public MessageBuilder WithAdapter(string adapterId) { _targetAdapter = _ctx.AdapterFactory.Resolve(adapterId); return this; } /// /// Использовать текущий адаптер (по умолчанию). /// public MessageBuilder UseCurrentAdapter() { _targetAdapter = null; return this; } /// /// Отправить в указанный чат (с указанием адаптера). /// public MessageBuilder ToChat(string chatId, IMessengerAdapter? adapter) { _targetChatId = chatId; _targetAdapter = adapter; return this; } /// /// Отправить в указанный чат через адаптер по ID. /// public MessageBuilder ToChat(string chatId, string adapterId) { _targetChatId = chatId; _targetAdapter = _ctx.AdapterFactory.Resolve(adapterId); return this; } /// /// Отправить в указанный чат. /// public MessageBuilder ToChat(string chatId) { _targetChatId = chatId; return this; } /// /// Установить опции для конкретного адаптера. Ключ адаптера определяется адаптером (напр., "telegram"). /// public MessageBuilder WithAdapterOption(string adapterType, T options) { if (_adapterOptions is null) _adapterOptions = new AdapterOptionsBag(); _adapterOptions.Set(adapterType, options); return this; } /// Текст сообщения. public MessageBuilder Text(string text, MessageFormat format = MessageFormat.Plain) { _text = text; _format = format; return this; } /// Стиль сообщения. public MessageBuilder Style(MessageStyle style) { _style = style; return this; } /// Тема сообщения (для форумов). public MessageBuilder Topic(string topic) { _topic = topic; return this; } /// Запланировать отправку на определенную дату. public MessageBuilder Schedule(DateTime scheduleDate) { _scheduleDate = scheduleDate; return this; } /// Отключить предпросмотр ссылок. public MessageBuilder DisableWebPagePreview() { _disableWebPagePreview = true; return this; } /// Отключить уведомление. public MessageBuilder DisableNotification() { _disableNotification = true; return this; } /// Защитить содержимое от пересылки. public MessageBuilder ProtectContent() { _protectContent = true; return this; } /// Ответить на сообщение. public MessageBuilder ReplyTo(string messageId, bool quote = true, string? quoteTitle = null) { _replyToMessageId = messageId; _quoteReply = quote; _quoteTitle = quoteTitle; return this; } /// Ответить на текущее сообщение. public MessageBuilder ReplyToCurrent(bool quote = true, string? quoteTitle = null) { if (_ctx.Update is not null) { // Предполагаем, что Update содержит ID текущего сообщения // Это может потребовать расширения UpdateContext _replyToMessageId = _ctx.Update.MessageId; _quoteReply = quote; _quoteTitle = quoteTitle; } return this; } /// Добавить inline‑кнопку. public MessageBuilder Inline(string label, string value) { _inline.Add(new() { new(label, value) }); return this; } /// Добавить inline‑кнопку. public MessageBuilder Inline(InlineButton button) { _inline.Add(new() { button }); return this; } /// Добавить inline‑кнопку. public MessageBuilder Inline(params InlineButton[] buttons) { _inline.Add(buttons.ToList()); return this; } /// Добавить строку inline‑кнопок. public MessageBuilder Inline(IEnumerable row) { _inline.Add(row.ToList()); return this; } /// Добавить строки inline‑кнопок. public MessageBuilder Inline(IEnumerable> row) { _inline.AddRange(row.Select(t => t.ToList()).ToList()); return this; } /// /// Отключение Reply клавиатуры. /// public MessageBuilder DisableReply() { _disableReplyKeyboard = true; return this; } /// Добавить reply‑кнопку. public MessageBuilder Reply(params ReplyButton[] label) { _disableReplyKeyboard = false; _reply.Add(label.ToList()); return this; } /// Добавить строку reply‑кнопок. public MessageBuilder Reply(IEnumerable row) { _disableReplyKeyboard = false; _reply.Add(row.ToList()); return this; } /// Добавить строки reply‑кнопок. public MessageBuilder Reply(IEnumerable> row) { _disableReplyKeyboard = false; _reply.AddRange(row.Select(t => t.ToList()).ToList()); return this; } /// Добавить файл для отправки. public MessageBuilder File(FileDescriptor file, string? caption = null, MessageFormat? captionFormat = null) { _files.Add((file, caption, captionFormat)); return this; } /// Добавить файл в альбом. public MessageBuilder Album(FileDescriptor file, string? caption = null, MessageFormat? captionFormat = null) { _album.Add((file, caption, captionFormat)); return this; } /// Удалить сообщение. public async Task DeleteAsync(string messageId, CancellationToken ct = default) { await _ctx.Adapter.DeleteAsync(GetEffectiveChatId(), messageId, ct); } /// Удалить несколько сообщений. public async Task DeleteMultipleAsync(IEnumerable messageIds, CancellationToken ct = default) { return await _ctx.Adapter.DeleteMultipleAsync(GetEffectiveChatId(), messageIds, ct); } /// Редактировать только текст сообщения. public async Task EditTextAsync(string messageId, string newText, MessageFormat? format = null, CancellationToken ct = default) { return await _ctx.Adapter.EditTextAsync( GetEffectiveChatId(), messageId, newText, format ?? _format, ct); } /// Редактировать только кнопки сообщения. public async Task EditButtonsAsync(string messageId, IEnumerable>? inlineButtons = null, CancellationToken ct = default) { return await _ctx.Adapter.EditButtonsAsync( GetEffectiveChatId(), messageId, inlineButtons ?? _inline, ct); } /// Закрепить сообщение. public async Task PinAsync(string messageId, bool disableNotification = false, CancellationToken ct = default) { return await _ctx.Adapter.PinMessageAsync( GetEffectiveChatId(), messageId, disableNotification, ct); } /// Открепить сообщение. public async Task UnpinAsync(string messageId, CancellationToken ct = default) { return await _ctx.Adapter.UnpinMessageAsync( GetEffectiveChatId(), messageId, ct); } /// Получить информацию о сообщении. public async Task GetInfoAsync(string messageId, CancellationToken ct = default) { return await _ctx.Adapter.GetMessageInfoAsync( GetEffectiveChatId(), messageId, ct); } /// Переслать сообщение. public async Task ForwardAsync(string fromChatId, string messageId, string toChatId, bool disableNotification = false, CancellationToken ct = default) { return await _ctx.Adapter.ForwardMessageAsync( fromChatId, messageId, toChatId ?? GetEffectiveChatId(), disableNotification, ct); } /// Копировать сообщение. public async Task CopyAsync(string fromChatId, string messageId, string toChatId, string? caption = null, MessageFormat? captionFormat = null, bool disableNotification = false, CancellationToken ct = default) { return await _ctx.Adapter.CopyMessageAsync( fromChatId, messageId, toChatId ?? GetEffectiveChatId(), caption, captionFormat, disableNotification, ct); } private string GetEffectiveChatId() { return _targetChatId ?? _ctx.Update.Chat.Id; } private IMessengerAdapter GetEffectiveAdapter() { return _targetAdapter ?? _ctx.Adapter ?? throw new InvalidOperationException("No adapter specified for sending message"); } /// Отправить собранное сообщение. public async Task SendAsync(CancellationToken ct = default) => await SendAsync(string.Empty, ct); /// Редактировать сообщение. public async Task SendAsync(string messageId, CancellationToken ct = default) { _messageId = string.IsNullOrWhiteSpace(messageId) ? null : messageId; string? outMessageId = null; List>? reply = null; if (_disableReplyKeyboard) reply = new(); else if (_reply.Any()) reply = _reply; // Определяем целевой адаптер var adapter = GetEffectiveAdapter(); // Определяем целевой чат var targetChatId = GetEffectiveChatId(); // Текст if (!string.IsNullOrWhiteSpace(_text)) { var req = new SendRequest { ChatId = targetChatId, Text = _text, TextFormat = _format, Inline = _inline, Reply = reply, MessageId = _messageId, ReplyToMessageId = _replyToMessageId, QuoteReply = _quoteReply, QuoteTitle = _quoteTitle, DisableWebPagePreview = _disableWebPagePreview, DisableNotification = _disableNotification, ProtectContent = _protectContent, Style = _style, ScheduleDate = _scheduleDate, Topic = _topic, AdapterOptions = _adapterOptions }; outMessageId = await adapter.SendAsync(req, ct); } // Файлы foreach (var (file, caption, captionFormat) in _files) { var req = new SendRequest { ChatId = targetChatId, File = file, Caption = caption, CaptionFormat = captionFormat, Inline = _inline, Reply = reply, ReplyToMessageId = _replyToMessageId, QuoteReply = _quoteReply, QuoteTitle = _quoteTitle, DisableWebPagePreview = _disableWebPagePreview, DisableNotification = _disableNotification, ProtectContent = _protectContent, Style = _style, ScheduleDate = _scheduleDate, Topic = _topic, AdapterOptions = _adapterOptions }; var res = await adapter.SendAsync(req, ct); // сохранить первый возвращённый id сообщения if (outMessageId is null && res is not null) outMessageId = res; } // Альбом if (_album.Count > 0) { // Для альбома нужен PageContext с правильным адаптером var albumCtx = CreateAlbumContext(_ctx, adapter); var builder = adapter.CreateAlbumBuilder(albumCtx); foreach (var (file, caption, captionFormat) in _album) builder.Add(file, caption, captionFormat); await builder.SendAsync(ct); } Reset(); return outMessageId; } private PageContext CreateAlbumContext(PageContext originalCtx, IMessengerAdapter adapter) { // Создаем новый контекст для альбома с указанным адаптером return new PageContext { SessionKey = originalCtx.SessionKey, Update = originalCtx.Update, StateStorage = originalCtx.StateStorage, Navigation = originalCtx.Navigation, AdapterFactory = originalCtx.AdapterFactory, Adapter = adapter, }; } /// /// Сбросить состояние билдера для повторного использования. /// public MessageBuilder Reset() { _text = null; _files.Clear(); _album.Clear(); _adapterOptions = null; _targetAdapter = null; _targetChatId = null; _replyToMessageId = null; _quoteReply = true; _quoteTitle = null; _disableWebPagePreview = false; _disableNotification = false; _protectContent = false; _style = null; _scheduleDate = null; _topic = null; _messageId = null; _reply.Clear(); _inline.Clear(); _disableReplyKeyboard = false; return this; } }