diff --git a/BotPages.Core/Abstractions/IMessengerAdapter.cs b/BotPages.Core/Abstractions/IMessengerAdapter.cs index 08e74b3..b1a1b99 100644 --- a/BotPages.Core/Abstractions/IMessengerAdapter.cs +++ b/BotPages.Core/Abstractions/IMessengerAdapter.cs @@ -1,4 +1,5 @@ -using BotPages.Core.Messaging; +using BotPages.Core.Context; +using BotPages.Core.Messaging; namespace BotPages.Core.Abstractions; @@ -40,3 +41,17 @@ public interface IMessengerAdapter /// Task OnLeaveAsync(PageContext ctx, CancellationToken ct); } + +/// +/// Контракт конфигурации адаптера. +/// +public interface IMessangerAdapterSetup : IMessengerAdapter +{ + /// + /// Запуск работы адаптера + /// + /// + /// + /// + Task StartAdapterAsync(Func onUpdate, CancellationToken ct); +} \ No newline at end of file diff --git a/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs b/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs index 055a46e..0a1e1da 100644 --- a/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs +++ b/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs @@ -4,16 +4,35 @@ namespace BotPages.Core.Abstractions; /// /// Фабрика адаптеров мессенджеров. -/// Используется для разрешения конкретного по типу мессенджера. +/// Используется для разрешения конкретного по типу мессенджера. /// public interface IMessengerAdapterFactory { + /// + /// Список зарегистрированных адаптеров. + /// + Dictionary Adapters { get; } + + /// + /// Зарегистрировать адаптер для указанного типа мессенджера. + /// + /// + /// Тип мессенджера (например, "Telegram", "Slack", "VK"). + /// + /// + /// Экземпляр адаптера, реализующий . + /// + /// + /// Текущий экземпляр для цепочки вызовов. + /// + IMessengerAdapterFactory Register(string messengerType, IMessangerAdapterSetup adapter); + /// /// Получить адаптер для указанного мессенджера. /// /// /// Тип мессенджера (например, "Telegram", "Slack", "VK"). - /// Значение должно совпадать с .. + /// Значение должно совпадать с . /// /// /// Экземпляр , зарегистрированный для данного типа мессенджера. diff --git a/BotPages.Core/Abstractions/MultiAdapterFactory.cs b/BotPages.Core/Abstractions/MultiAdapterFactory.cs index 7ad8be3..4be6bfc 100644 --- a/BotPages.Core/Abstractions/MultiAdapterFactory.cs +++ b/BotPages.Core/Abstractions/MultiAdapterFactory.cs @@ -6,7 +6,12 @@ /// public sealed class MultiAdapterFactory : IMessengerAdapterFactory { - private readonly Dictionary _adapters = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _adapters = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Список зарегистрированных адаптеров. + /// + public Dictionary Adapters => _adapters; /// /// Зарегистрировать адаптер для указанного типа мессенджера. @@ -20,7 +25,7 @@ public sealed class MultiAdapterFactory : IMessengerAdapterFactory /// /// Текущий экземпляр для цепочки вызовов. /// - public MultiAdapterFactory Register(string messengerType, IMessengerAdapter adapter) + public IMessengerAdapterFactory Register(string messengerType, IMessangerAdapterSetup adapter) { _adapters[messengerType] = adapter; return this; diff --git a/BotPages.Core/BotPagesApp.cs b/BotPages.Core/BotPagesApp.cs index 8618886..4e24099 100644 --- a/BotPages.Core/BotPagesApp.cs +++ b/BotPages.Core/BotPagesApp.cs @@ -19,16 +19,30 @@ public sealed class BotPagesApp private readonly ILogger _logger; private readonly NavigationService _navigation; + /// + /// Серсвис логирования. + /// + public ILogger Logger => _logger; + /// /// Создать приложение BotPages. /// - public BotPagesApp(IMessengerAdapterFactory adapterFactory, IStateStorage state, ILogger logger) + public BotPagesApp(IStateStorage state, ILogger logger) { - _adapterFactory = adapterFactory; _state = state; _logger = logger; _navigation = new NavigationService(_routes); + _adapterFactory = new MultiAdapterFactory(); + } + + /// + /// Добавить адаптер. + /// + public BotPagesApp AddAdapter(string messengerType, IMessangerAdapterSetup adapter) + { + _adapterFactory.Register(messengerType, adapter); + return this; } /// @@ -173,4 +187,17 @@ public sealed class BotPagesApp /// private Page? ResolveCurrentPage(PageContext ctx) => _navigation.ResolveCurrentPage(ctx); + + /// + /// Сборка и запуск приложения. + /// + /// + /// + public async Task Build(CancellationToken cancellationToken) + { + foreach (var adapter in _adapterFactory.Adapters) + { + await adapter.Value.StartAdapterAsync(update => HandleUpdateAsync(update, cancellationToken), cancellationToken); + } + } } \ No newline at end of file diff --git a/BotPages.Core/Messaging/ButtonAttribute.cs b/BotPages.Core/Messaging/ButtonAttribute.cs index b287c41..c2e70d3 100644 --- a/BotPages.Core/Messaging/ButtonAttribute.cs +++ b/BotPages.Core/Messaging/ButtonAttribute.cs @@ -25,6 +25,9 @@ public class ButtonAttribute : Attribute public string? Value { get; } } +/// +/// Расширение для работы с кнопками. +/// public static class ButtonExtensions { private static readonly Dictionary> _cacheName = new(); diff --git a/BotPages.Core/Storage/InMemoryStateStorage.cs b/BotPages.Core/Storage/InMemoryStateStorage.cs index dc78040..8874c59 100644 --- a/BotPages.Core/Storage/InMemoryStateStorage.cs +++ b/BotPages.Core/Storage/InMemoryStateStorage.cs @@ -31,6 +31,7 @@ public sealed class InMemoryStateStorage : IStateStorage public Task RemoveAsync(CompositeSessionKey session, string key, CancellationToken ct) => Task.FromResult(_store.TryGetValue(session, out var dict) ? dict.TryRemove(key, out _) : true); + /// public Task ClearAsync(CompositeSessionKey session, CancellationToken ct) => Task.FromResult(_store.TryRemove(session, out _)); } \ No newline at end of file diff --git a/BotPages.Telegram/BotPagesAppExtension.cs b/BotPages.Telegram/BotPagesAppExtension.cs new file mode 100644 index 0000000..ba54118 --- /dev/null +++ b/BotPages.Telegram/BotPagesAppExtension.cs @@ -0,0 +1,22 @@ +using BotPages.Core; + +namespace BotPages.Telegram; + +/// +/// Расширения для . +/// +public static class BotPagesAppExtension +{ + /// + /// Добавление адаптера для телеграмм в + /// + /// + /// + /// + public static BotPagesApp AddTelegramAdapter(this BotPagesApp app, string token) + { + var telegram = new TelegramAdapter(app.Logger, token); + app.AddAdapter(telegram.MessagerType, telegram); + return app; + } +} diff --git a/BotPages.Telegram/TelegramAdapter.cs b/BotPages.Telegram/TelegramAdapter.cs index 3e02733..3c3b94e 100644 --- a/BotPages.Telegram/TelegramAdapter.cs +++ b/BotPages.Telegram/TelegramAdapter.cs @@ -21,32 +21,44 @@ namespace BotPages.Telegram; /// Адаптер для Telegram на базе Telegram.Bot. /// Реализует отправку текста, кнопок, файлов, альбомов и прогресса. /// -public sealed class TelegramAdapter : IMessengerAdapter +public sealed class TelegramAdapter : IMessangerAdapterSetup { private readonly ILogger _logger; private TelegramBotClient? _client; + private string _token; + private string _messagerType; /// Создать адаптер Telegram. - public TelegramAdapter(ILogger logger) => _logger = logger; + public TelegramAdapter(ILogger logger, string token) + { + _logger = logger; + _token = token; + _messagerType = "Telegram: " + Guid.NewGuid().ToString(); + } + + /// + ///Идентификатор мессенджера / адаптера + /// + public string MessagerType => _messagerType; /// /// Запустить polling для приема обновлений от Telegram. /// - public async Task StartPollingAsync(string token, Func onUpdate, CancellationToken ct) + public async Task StartAdapterAsync(Func onUpdate, CancellationToken ct) { - _client = new TelegramBotClient(token); + _client = new TelegramBotClient(_token); _client.StartReceiving( updateHandler: async (_, update, ct2) => { - var mapped = TelegramUpdateMapper.Map(update, _client); + var mapped = TelegramUpdateMapper.Map(_messagerType, update, _client); if (mapped is not null) await onUpdate(mapped); }, errorHandler: async (_, ex, ct2) => { - _logger.Log(LogLevel.Warn, "Telegram error.", ex); + _logger.Log(LogLevel.Warn, $"{_messagerType} error.", ex); await Task.CompletedTask; }, @@ -55,7 +67,7 @@ public sealed class TelegramAdapter : IMessengerAdapter var me = await _client.GetMe(); - _logger.Log(LogLevel.Info, $"Telegram started: @{me.Username}"); + _logger.Log(LogLevel.Info, $"{_messagerType} started: @{me.Username}"); return; } @@ -67,7 +79,7 @@ public sealed class TelegramAdapter : IMessengerAdapter { if (_client is null) { - _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); + _logger.Log(LogLevel.Critical, $"{_messagerType} client is not initialized."); return; } @@ -137,7 +149,7 @@ public sealed class TelegramAdapter : IMessengerAdapter { if (_client is null) { - _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); + _logger.Log(LogLevel.Critical, $"{_messagerType} client is not initialized."); return; } @@ -192,7 +204,7 @@ public sealed class TelegramAdapter : IMessengerAdapter { if (_client is null) { - _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); + _logger.Log(LogLevel.Critical, $"{_messagerType} client is not initialized."); return null; } @@ -216,7 +228,7 @@ public sealed class TelegramAdapter : IMessengerAdapter { if (_client is null) { - _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); + _logger.Log(LogLevel.Critical, $"{_messagerType} client is not initialized."); return; } diff --git a/BotPages.Telegram/TelegramUpdateMapper.cs b/BotPages.Telegram/TelegramUpdateMapper.cs index 0e73fff..f1dfabb 100644 --- a/BotPages.Telegram/TelegramUpdateMapper.cs +++ b/BotPages.Telegram/TelegramUpdateMapper.cs @@ -19,7 +19,7 @@ public static class TelegramUpdateMapper /// /// Маппинг Telegram Update в UpdateContext BotPages. /// - public static UpdateContext Map(Update update, TelegramBotClient client) + public static UpdateContext Map(string MessagerType, Update update, TelegramBotClient client) { var chat = update.Message?.Chat ?? update.CallbackQuery?.Message?.Chat; var user = update.Message?.From ?? update.CallbackQuery?.From; @@ -131,7 +131,7 @@ public static class TelegramUpdateMapper return new UpdateContext { - MessengerType = "Telegram", + MessengerType = MessagerType, User = userContext, Chat = chatContext, Text = text, diff --git a/Demo/Models/Request.cs b/Demo/Models/Request.cs index 7046050..85db492 100644 --- a/Demo/Models/Request.cs +++ b/Demo/Models/Request.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Demo.Models +namespace Demo.Models { internal class Request { diff --git a/Demo/Pages/DetailsPage.cs b/Demo/Pages/DetailsPage.cs index 97d6b78..ca7307c 100644 --- a/Demo/Pages/DetailsPage.cs +++ b/Demo/Pages/DetailsPage.cs @@ -1,6 +1,5 @@ using BotPages.Core; using BotPages.Core.Messaging; -using System.Xml; namespace Demo.Pages; @@ -38,12 +37,13 @@ public sealed class DetailsPage : StatefullPage break; } - case "back": + case "back": { await ctx.Navigation.GoToAsync(ctx, ct); break; } - }; + } + ; } public override async Task OnText(PageContext ctx, string text, CancellationToken ct) diff --git a/Demo/Program.cs b/Demo/Program.cs index f414227..6863801 100644 --- a/Demo/Program.cs +++ b/Demo/Program.cs @@ -1,5 +1,4 @@ using BotPages.Core; -using BotPages.Core.Abstractions; using BotPages.Core.Logging; using BotPages.Core.Middleware; using BotPages.Core.Storage; @@ -17,22 +16,15 @@ namespace Demo var logger = new ConsoleLogger(); var state = new InMemoryStateStorage(); + using var cts = new CancellationTokenSource(); - var telegram = new TelegramAdapter(logger); - var factory = new MultiAdapterFactory() - .Register("Telegram", telegram); - - var app = new BotPagesApp(factory, state, logger) + var app = new BotPagesApp(state, logger) .AddDefaultPage() .MapCommand("/start") .AddMiddleware(new ErrorHandlingMiddleware(logger)) - .AddMiddleware(new LoggingMiddleware(logger)); - - using var cts = new CancellationTokenSource(); - - await telegram.StartPollingAsync(token, - update => app.HandleUpdateAsync(update, CancellationToken.None), - cts.Token); + .AddMiddleware(new LoggingMiddleware(logger)) + .AddTelegramAdapter(token) + .Build(cts.Token); Console.ReadKey(); cts.Cancel();