From 833d8c80d97c8e4b0a0f911d25bb8acb063cb023 Mon Sep 17 00:00:00 2001 From: FrigaT Date: Wed, 24 Dec 2025 05:55:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/IMessengerAdapter.cs | 2 +- .../Abstractions/IMessengerAdapterFactory.cs | 8 +- .../Abstractions/MultiAdapterFactory.cs | 6 +- BotPages.Core/BotPagesApp.cs | 2 +- README.md | 164 +++++++++--------- docfx.json | 30 ++++ docs/API_REFERENCE.md | 57 ++++++ docs/CONTRIBUTING.md | 19 ++ docs/GETTING_STARTED.md | 47 +++++ docs/PROJECT_DOCUMENTATION.md | 114 ++++++++++++ docs/docfx_project/docfx.css | 2 + docs/docfx_project/docfx.js | 7 + scripts/generate-docs.ps1 | 22 +++ scripts/generate-docs.sh | 14 ++ 14 files changed, 405 insertions(+), 89 deletions(-) create mode 100644 docfx.json create mode 100644 docs/API_REFERENCE.md create mode 100644 docs/CONTRIBUTING.md create mode 100644 docs/GETTING_STARTED.md create mode 100644 docs/PROJECT_DOCUMENTATION.md create mode 100644 docs/docfx_project/docfx.css create mode 100644 docs/docfx_project/docfx.js create mode 100644 scripts/generate-docs.ps1 create mode 100644 scripts/generate-docs.sh diff --git a/BotPages.Core/Abstractions/IMessengerAdapter.cs b/BotPages.Core/Abstractions/IMessengerAdapter.cs index bc1abbb..6d4da71 100644 --- a/BotPages.Core/Abstractions/IMessengerAdapter.cs +++ b/BotPages.Core/Abstractions/IMessengerAdapter.cs @@ -51,7 +51,7 @@ public interface IMessengerAdapter /// /// Контракт конфигурации адаптера. /// -public interface IMessangerAdapterSetup : IMessengerAdapter +public interface IMessengerAdapterSetup : IMessengerAdapter { /// /// Запуск работы адаптера diff --git a/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs b/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs index 0a1e1da..2235438 100644 --- a/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs +++ b/BotPages.Core/Abstractions/IMessengerAdapterFactory.cs @@ -4,14 +4,14 @@ namespace BotPages.Core.Abstractions; /// /// Фабрика адаптеров мессенджеров. -/// Используется для разрешения конкретного по типу мессенджера. +/// Используется для разрешения конкретного по типу мессенджера. /// public interface IMessengerAdapterFactory { /// /// Список зарегистрированных адаптеров. /// - Dictionary Adapters { get; } + Dictionary Adapters { get; } /// /// Зарегистрировать адаптер для указанного типа мессенджера. @@ -23,9 +23,9 @@ public interface IMessengerAdapterFactory /// Экземпляр адаптера, реализующий . /// /// - /// Текущий экземпляр для цепочки вызовов. + /// Текущий экземпляр для цепочки вызовов. /// - IMessengerAdapterFactory Register(string messengerType, IMessangerAdapterSetup adapter); + IMessengerAdapterFactory Register(string messengerType, IMessengerAdapterSetup adapter); /// /// Получить адаптер для указанного мессенджера. diff --git a/BotPages.Core/Abstractions/MultiAdapterFactory.cs b/BotPages.Core/Abstractions/MultiAdapterFactory.cs index 4be6bfc..ddf47fd 100644 --- a/BotPages.Core/Abstractions/MultiAdapterFactory.cs +++ b/BotPages.Core/Abstractions/MultiAdapterFactory.cs @@ -6,12 +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; + public Dictionary Adapters => _adapters; /// /// Зарегистрировать адаптер для указанного типа мессенджера. @@ -25,7 +25,7 @@ public sealed class MultiAdapterFactory : IMessengerAdapterFactory /// /// Текущий экземпляр для цепочки вызовов. /// - public IMessengerAdapterFactory Register(string messengerType, IMessangerAdapterSetup adapter) + public IMessengerAdapterFactory Register(string messengerType, IMessengerAdapterSetup adapter) { _adapters[messengerType] = adapter; return this; diff --git a/BotPages.Core/BotPagesApp.cs b/BotPages.Core/BotPagesApp.cs index c060ae4..79174e7 100644 --- a/BotPages.Core/BotPagesApp.cs +++ b/BotPages.Core/BotPagesApp.cs @@ -40,7 +40,7 @@ public sealed class BotPagesApp /// /// Добавить адаптер. /// - public BotPagesApp AddAdapter(string messengerType, IMessangerAdapterSetup adapter) + public BotPagesApp AddAdapter(string messengerType, IMessengerAdapterSetup adapter) { _adapterFactory.Register(messengerType, adapter); return this; diff --git a/README.md b/README.md index 1f67574..c1bb019 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,96 @@ -# BotPages Framework +# BotPages -BotPages — это архитектурный фреймворк для построения Telegram/MAX‑ботов с чистыми слоями, расширяемыми API и удобным developer experience. +BotPages — кроссплатформенный фреймворк для создания диалоговых ботов с системой страниц (page‑based conversational framework). -## ✨ Возможности -- **Навигация через страницы**: каждая страница — отдельный обработчик логики. -- **Единый метод GoToAsync**: автоматически поддерживает как `SingletonPage`, так и `StatefulPage`. -- **StatefulPage**: свойства автоматически сохраняются в `StateStorage`. -- **SingletonPage**: один экземпляр на всё приложение, без состояния. -- **Middleware**: подключение промежуточных обработчиков (логирование, обработка ошибок). -- **Автоматическая регистрация страниц**: через рефлексию. -- **Роутинг страниц**: возможность устанавливать пути вызова для страниц. -- **Команды**: возможность установки команд, которые работают приоритетнее обработчиков страниц. -- **Минимум boilerplate**: декларативные интерфейсы и fluent API. +Цели проекта: +- Простая модель страниц и навигации. +- Портируемость между мессенджерами через адаптеры. +- Поддержка middleware и декларативной системы команд. +- Минимум boilerplate и удобный developer experience. + +Структура репозитория + +- `BotPages.Core` — ядро фреймворка: навигация, страницы, маршрутизация, реестр команд, middleware, абстракции адаптеров и хранилища состояния. +- `BotPages.Telegram` — адаптер для Telegram Bot API (реализация `IMessangerAdapterSetup`/`IMessengerAdapter`). +- `Demo` — пример приложения с несколькими страницами и конфигурацией адаптера. +- `BotPages` — мета‑проект для упаковки библиотек. +- `docs/` — внутренняя документация проекта (Quickstart, API reference и т.д.). + +Требования + +- .NET 8 SDK +- C# 12 + +Быстрый старт (локально) + +1. Клонируйте репозиторий: + +```bash +git clone https://git.frigat.duckdns.org/FrigaT/BotPages.git +cd BotPages +``` + +2. Соберите решения: + +```bash +dotnet build +``` + +3. Запуск демо (Telegram): + +- Установите переменную окружения `TELEGRAM_TOKEN` с токеном бота. + +```bash +setx TELEGRAM_TOKEN "" # Windows +export TELEGRAM_TOKEN="" # Linux/macOS +``` + +- Запустите demo: + +```bash +dotnet run --project Demo +``` + +Основные концепции + +- `BotPagesApp` — точка конфигурации приложения: регистрация адаптеров, middleware, маршрутов и команд, запуск. +- `Page`, `StatefulPage`, `SingletonPage` — модели страниц с жизненным циклом (`OnEnter`, `OnUpdate`, `OnText`, `OnButton`, `OnFile`, `OnError`). +- `NavigationService` — управление переходами между страницами и определение текущей страницы по сессии. +- `CommandsRegistry` — шаблоны команд вида `/cmd {arg} {opt?}` с поддержкой именованных и опциональных аргументов. +- `IPageMiddleware` — middleware-конвейер, выполняющийся для каждого апдейта. +- `IMessangerAdapterSetup` / `IMessengerAdapter` — интерфейсы, позволяющие подключать новые мессенджеры. + +Документация + +Полная внутренняя документация находится в `docs/`: +- `docs/GETTING_STARTED.md` — быстрый старт и примеры конфигурации. +- `docs/API_REFERENCE.md` — краткий reference публичных API. +- `docs/PROJECT_DOCUMENTATION.md` — обзор архитектуры и компонентов. + +XML‑документация генерируется при сборке (опция `GenerateDocumentationFile` в `.csproj`), её можно использовать для генерации HTML‑референса (docfx, MkDocs и т.п.). + +Примеры использования -## 🚀 Быстрый старт ```csharp -var app = new BotPagesApp(factory, state, logger) - .UseDefaultPage() +var app = new BotPagesApp(stateStorage, logger) + .AddAdapter("telegram", new TelegramAdapterSetup(token)) + .AddDefaultPage() .MapCommand("/start") - .AddMiddleware(new LoggingMiddleware(logger)) - .AddMiddleware(new ErrorHandlingMiddleware(logger)); + .AddMiddleware(new LoggingMiddleware(logger)); + +await app.Build(CancellationToken.None); ``` -## 📄 Пример страниц +Вклад и тестирование -### StatefulPage -```csharp -public sealed class WelcomePage : StatefulPage -{ - [Statefull("visitCount")] - private int VisitCount; +- Принимам пулл‑реквесты. Описание PR должно содержать цель и краткое описание изменения. +- Следуйте единому стилю кода и включённым nullable-аннотациям. - private PageContext? Context { get; set; } +Лицензия - public override async Task OnEnter(PageContext ctx, CancellationToken ct) - { - Context = ctx; +Проект распространяется под лицензией MIT. Смотрите файл `LICENSE`. - LoadState(); - VisitCount++; - SaveState(); +Контакты - await ctx.SendTextAsync($"Добро пожаловать 🚀. Вы заходили сюда {VisitCount} раз(а).", ct: ct); - } -} -``` - -### SingletonPage -```csharp -public sealed class HelpPage : SingletonPage -{ - public override Task OnEnter(PageContext ctx, CancellationToken ct) - => ctx.SendTextAsync("Это справка 📖", ct: ct); -} -``` - -## 🧩 Навигация -```csharp -await ctx.Navigation.GoToAsync(ctx, ct); // Stateful -await ctx.Navigation.GoToAsync(ctx, agrs, ct); // Stateful with arguments -await ctx.Navigation.GoToAsync(ctx, ct); // Singleton -await ctx.Navigation.GoToHome(ctx, ct); // Stateful -``` - -## ⚙️ Middleware -```csharp -public sealed class LoggingMiddleware : IPageMiddleware -{ - private readonly ILogger _logger; - public LoggingMiddleware(ILogger logger) => _logger = logger; - - public async Task InvokeAsync(PageContext ctx, Func next, CancellationToken ct) - { - _logger.Log(LogLevel.Info, $"Update: {ctx.Update.Kind}"); - await next(); - } -} -``` - -## 🏗 Архитектурные принципы -- **Separation of concerns**: страницы не знают о транспорте, всё через адаптеры. -- **Расширяемость**: новые страницы и middleware подключаются декларативно. -- **Прозрачность**: минимум скрытой логики, всё явно через контекст. -- **Developer Experience**: удобные API, автоматическая регистрация, документация через `///summary`. - -## 📌 Итог -BotPages позволяет писать чистые, расширяемые и удобные для команды боты: -- минимум boilerplate, -- декларативные интерфейсы, -- прозрачная навигация, -- автоматическое управление состоянием. \ No newline at end of file +Автор: FrigaT +Репозиторий: https://git.frigat.duckdns.org/FrigaT/BotPages \ No newline at end of file diff --git a/docfx.json b/docfx.json new file mode 100644 index 0000000..719b8ff --- /dev/null +++ b/docfx.json @@ -0,0 +1,30 @@ +{ + "metadata": [ + { + "src": [ + { "files": ["BotPages.Core/BotPages.Core.csproj"] }, + { "files": ["BotPages.Telegram/BotPages.Telegram.csproj"] }, + { "files": ["BotPages/BotPages.csproj"] } + ], + "dest": "api", + "properties": { + "TargetFramework": "net8.0" + } + } + ], + "build": { + "content": [ + { "files": ["docs/**.md"] }, + { "files": ["README.md"] }, + { "files": ["api/**.yml"], "dest": "api" } + ], + "resource": [ + { "files": ["docs/**.png", "docs/**.jpg", "docs/**.svg"] } + ], + "dest": "_site", + "globalMetadata": { + "_enableSearch": true + }, + "template": [ "default" ] + } +} diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..a24eb23 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,57 @@ +# API Reference () + + API . + +## BotPages.Core + +### `BotPagesApp` +- `AddAdapter(string messengerType, IMessangerAdapterSetup adapter)` . +- `AddDefaultPage() where TPage : SingletonPage` "" . +- `AddMiddleware(TMiddleware instance) where TMiddleware : IPageMiddleware` middleware . +- `MapCommand(string commandTemplate) where TPage : Page` , . +- `MapCommand(string template, CommandHandler handler)` . +- `MapRoute(string template) where TPage : Page` . +- `AutoMapRoute()` Page- . +- `HandleUpdateAsync(UpdateContext update, CancellationToken ct)` ( ). +- `Build(CancellationToken ct)` . + +### +- `Page` . +- `StatefulPage` . +- `SingletonPage` . + +### +- `NavigationService` . + - `GoToAsync(PageContext ctx, CancellationToken ct)` + - `GoToHomeAsync(PageContext ctx, CancellationToken ct)` + +### +- `CommandsRegistry` (). . + +### Middleware +- `IPageMiddleware` middleware. + - `InvokeAsync(PageContext ctx, Func next, CancellationToken ct)` + +### +- `IMessangerAdapterSetup` . +- `IMessengerAdapter` , `StartAdapterAsync(Func onUpdate, List commands, CancellationToken ct)`. +- `IMessengerAdapterFactory` / (`MultiAdapterFactory` ). + +### +- `IStateStorage` . + +## BotPages.Telegram +- Telegram `Telegram.Bot`. +- `TelegramUpdateMapper` Telegram `UpdateContext`. +- `TelegramAlbumBuilder` - . +- `TelegramAdapter` , `StartAdapterAsync`. + + +# + + `docfx` `Doxygen` HTML- XML-, (. `GenerateDocumentationFile` `.csproj`). + + +# + + API. reference XML- . \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..c9aa433 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,19 @@ +# Contributing to BotPages + + ! , PR , : + +- : `dotnet build`. +- / , . +- nullable-. + + `docfx`. . `docfx.json` `scripts/`. + + : + +```bash +# Unix +./scripts/generate-docs.sh + +# Windows PowerShell +./scripts/generate-docs.ps1 +``` diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..fc48e5a --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,47 @@ +# BotPages + + . + + +- .NET 8 SDK +- Telegram ( Telegram-) + + +```bash +dotnet build +``` + + +1. `Demo`. +2. Telegram ( ) Demo. +3. : +```bash +dotnet run --project Demo +``` + + +```csharp +var app = new BotPagesApp(stateStorage, logger) + .AddAdapter("telegram", new TelegramAdapterSetup("")) + .AddDefaultPage() + .MapCommand("/start") + .AddMiddleware(new LoggingMiddleware(logger)); + +await app.Build(CancellationToken.None); +``` + + +- `StatefulPage` - . +- `SingletonPage` . +- `OnEnter`, `OnUpdate`, `OnText`, `OnButton`, `OnFile`, `OnError` . +- `AutoMapRoute()`. + + +- `BotPagesApp.MapCommand`. +- : `/cmd {a} {b?}`. + + +- Middleware , `BotPagesApp`. +- ( `/`). + + API `docs/API_REFERENCE.md`. \ No newline at end of file diff --git a/docs/PROJECT_DOCUMENTATION.md b/docs/PROJECT_DOCUMENTATION.md new file mode 100644 index 0000000..de68c16 --- /dev/null +++ b/docs/PROJECT_DOCUMENTATION.md @@ -0,0 +1,114 @@ +# BotPages + + . , , . + +## + +- `BotPages.Core` . + - : `NavigationService`, : `Page`, `StatefulPage`, `SingletonPage`. + - : `RoutesRegistry` ( `AutoMapRoute`). + - : `CommandsRegistry` ( , `TryDispatch`). + - Middleware: `IPageMiddleware` `LoggingMiddleware`. + - /-: `IMessangerAdapterSetup`, `IMessengerAdapterFactory` ( `MultiAdapterFactory`). + - : `IStateStorage`. + +- `BotPages.Telegram` Telegram ( `Telegram.Bot`). + +- `Demo` . + +- `BotPages` -/ . + +## + +- `BotPagesApp` . + - : `AddAdapter(string messengerType, IMessangerAdapterSetup adapter)`. + - middleware: `AddMiddleware(T instance)`. + - : `MapCommand(string template)` `MapCommand(string template, CommandHandler handler)`. + - : `MapRoute(string template)` `AutoMapRoute()` . + - : `Build(CancellationToken)` . + +- : + - `Page` () (`OnEnter`, `OnUpdate`, `OnText`, `OnButton`, `OnFile`, `OnError`). + - `StatefulPage` `IStateStorage` ( , stateful-). + - `SingletonPage` , per-session . + +- : `NavigationService` ( `PageContext.SessionKey`) `GoToAsync`, `GoToHomeAsync`. + +- : `/cmd {arg} {opt?}`. `CommandsRegistry.ToRegex` . `CommandHandler(PageContext, IDictionary, CancellationToken)`. + +- Middleware: `IPageMiddleware.InvokeAsync(PageContext, Func next, CancellationToken ct)`; (). + +## + +- `UpdateContext` : `MessengerType`, `Chat`, `User`, `Kind` ( `Text`, `Button`, `File`), `Text`, `Files`. +- `BotPagesApp.HandleUpdateAsync` `PageContext` : + 1. ( `/`) . + 2. pipeline middleware. + 3. `DispatchToPageAsync`. + +## + +- , `IMessangerAdapterSetup`/`IMessengerAdapter` `BotPagesApp.AddAdapter`. +- `MultiAdapterFactory` `messengerType`. + +## + +: +- .NET 8 SDK + + : + +```bash +dotnet build +``` + + : + +```bash +dotnet run --project Demo +``` + + Telegram- `Demo` ( ). + +## API + +XML- `GenerateDocumentationFile` `.csproj` . (, `docfx`, `doxygen` `MkDocs` XML). + +## + +- : + +```csharp +var app = new BotPagesApp(stateStorage, logger) + .AddAdapter("telegram", new TelegramAdapterSetup(token)) + .AddMiddleware(new LoggingMiddleware(logger)) + .MapCommand("/start") + .AddDefaultPage(); + +await app.Build(CancellationToken.None); +``` + +- : + +```csharp +public sealed class WelcomePage : StatefulPage +{ + public override async Task OnEnter(PageContext ctx, CancellationToken ct) + { + await ctx.SendTextAsync(" ", ct: ct); + } +} +``` + +## + +- PR . PR. +- null-safe (`Nullable` ). + +## + + MIT (. `.csproj` `LICENSE`). + +--- + + . Reference API ( ) . \ No newline at end of file diff --git a/docs/docfx_project/docfx.css b/docs/docfx_project/docfx.css new file mode 100644 index 0000000..1e62930 --- /dev/null +++ b/docs/docfx_project/docfx.css @@ -0,0 +1,2 @@ +/* Minimal styling overrides for docfx output */ +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif; } diff --git a/docs/docfx_project/docfx.js b/docs/docfx_project/docfx.js new file mode 100644 index 0000000..5fcf1aa --- /dev/null +++ b/docs/docfx_project/docfx.js @@ -0,0 +1,7 @@ +module.exports = { + "templates": { + "global": { + "favicon": "favicon.ico" + } + } +}; \ No newline at end of file diff --git a/scripts/generate-docs.ps1 b/scripts/generate-docs.ps1 new file mode 100644 index 0000000..7b724f1 --- /dev/null +++ b/scripts/generate-docs.ps1 @@ -0,0 +1,22 @@ +Param( + [string]$DocfxPath = "docfx", + [string]$WorkingDir = "$(Resolve-Path .)" +) + +$ErrorActionPreference = 'Stop' + +Write-Host "Generating docfx documentation..." + +if (-not (Get-Command $DocfxPath -ErrorAction SilentlyContinue)) { + Write-Host "Docfx not found on PATH. Install docfx or provide full path to docfx.exe" -ForegroundColor Yellow +} + +Push-Location $WorkingDir +try { + & $DocfxPath metadata docfx.json + & $DocfxPath build docfx.json + Write-Host "Docfx build complete. Output in _site folder" -ForegroundColor Green +} +finally { + Pop-Location +} diff --git a/scripts/generate-docs.sh b/scripts/generate-docs.sh new file mode 100644 index 0000000..8183d79 --- /dev/null +++ b/scripts/generate-docs.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +DOCFX_PATH=${DOCFX_PATH:-docfx} +WORKING_DIR=$(pwd) + +if ! command -v "$DOCFX_PATH" >/dev/null 2>&1; then + echo "docfx not found. Install it or set DOCFX_PATH to the executable path." +fi + +pushd "$WORKING_DIR" > /dev/null +$DOCFX_PATH metadata docfx.json +$DOCFX_PATH build docfx.json +popd > /dev/null + +echo "Docfx build complete. Output in _site folder"