This commit is contained in:
25
Demo/Pages/ConfirmPage.cs
Normal file
25
Demo/Pages/ConfirmPage.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Страница подтверждения заявки.
|
||||
/// </summary>
|
||||
public sealed class ConfirmPage : SingletonPage
|
||||
{
|
||||
public override Task OnEnter(PageContext ctx, CancellationToken ct)
|
||||
=> new MessageBuilder(ctx)
|
||||
.Text("Подтвердите заявку ✅")
|
||||
.Inline("Отправить", "submit")
|
||||
.Inline("Отмена", "cancel")
|
||||
.SendAsync(ct);
|
||||
|
||||
public override Task OnButton(PageContext ctx, string payload, CancellationToken ct)
|
||||
=> payload switch
|
||||
{
|
||||
"submit" => ctx.Navigation.GoToAsync<SubmitPage>(ctx, ct),
|
||||
"cancel" => ctx.Navigation.GoToAsync<WelcomePage>(ctx, ct),
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
33
Demo/Pages/DetailsPage.cs
Normal file
33
Demo/Pages/DetailsPage.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Страница ввода деталей заявки.
|
||||
/// </summary>
|
||||
public sealed class DetailsPage : StatefullPage<DetailsArgs>
|
||||
{
|
||||
public override Task OnEnter(PageContext ctx, DetailsArgs args, CancellationToken ct)
|
||||
=> new MessageBuilder(ctx)
|
||||
.Text($"Заголовок: {args.Title}\nДобавьте детали или нажмите Далее.")
|
||||
.Inline(new InlineButton("Далее", "next"), new InlineButton("Назад", "back"))
|
||||
.Reply("Отмена")
|
||||
.SendAsync(ct);
|
||||
|
||||
public override Task OnButton(PageContext ctx, string payload, CancellationToken ct)
|
||||
=> payload switch
|
||||
{
|
||||
"next" => ctx.Navigation.GoToAsync<FilesPage>(ctx, ct),
|
||||
"back" => ctx.Navigation.GoToAsync<TitlePage>(ctx, ct),
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Аргументы для страницы DetailsPage.
|
||||
/// </summary>
|
||||
public sealed class DetailsArgs
|
||||
{
|
||||
public string Title { get; set; } = "";
|
||||
}
|
||||
29
Demo/Pages/FileSendPage.cs
Normal file
29
Demo/Pages/FileSendPage.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages;
|
||||
|
||||
public sealed class FileSendPage : SingletonPage
|
||||
{
|
||||
public override Task OnEnter(PageContext ctx, CancellationToken ct)
|
||||
{
|
||||
var content = "Hello from BotPages! This file is generated on the fly.";
|
||||
var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var demoFile = new FileDescriptor
|
||||
{
|
||||
Id = "", // не используется при отправке нового файла
|
||||
Name = "demo.txt",
|
||||
Extension = "txt",
|
||||
Size = stream.Length,
|
||||
Kind = FileKind.Document,
|
||||
GetStreamAsync = _ => Task.FromResult<Stream>(stream)
|
||||
};
|
||||
|
||||
return new MessageBuilder(ctx)
|
||||
.Text("Вот пример отправки нового файла 📎", MessageFormat.Markdown)
|
||||
.File(demoFile, "Демонстрационный файл")
|
||||
.SendAsync(ct);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,36 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages
|
||||
namespace Demo.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Страница загрузки файлов.
|
||||
/// </summary>
|
||||
public sealed class FilesPage : Page
|
||||
{
|
||||
public sealed class FilesPage : Page
|
||||
public override Task OnEnter(PageContext ctx, CancellationToken ct)
|
||||
=> new MessageBuilder(ctx)
|
||||
.Text("Пришлите файлы для заявки 📎", MessageFormat.Markdown)
|
||||
.Reply("Пропустить")
|
||||
.SendAsync(ct);
|
||||
|
||||
public override async Task OnFile(PageContext ctx, List<FileDescriptor> files, CancellationToken ct)
|
||||
{
|
||||
public static string Id => nameof(FilesPage);
|
||||
|
||||
public override Task<PageResult> EnterAsync(UpdateContext ctx, CancellationToken ct)
|
||||
foreach (var file in files)
|
||||
{
|
||||
var actions = new[]
|
||||
{
|
||||
new PageAction { Label = "⬅️ Назад", Value = "back", Placement = ActionPlacement.Reply, Row = 0 }
|
||||
};
|
||||
|
||||
return Task.FromResult(
|
||||
PageResultBuilder.Empty()
|
||||
.WithText("📂 Здесь можно загрузить или отправить файл.")
|
||||
.WithKeyboard(actions)
|
||||
.Build()
|
||||
);
|
||||
await ctx.SendFileAsync(file, $"Файл '{file.Name}' получен и отправлен обратно.", ct);
|
||||
}
|
||||
|
||||
public override async Task<PageResult> HandleAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
if (ctx.Text == "⬅️ Назад")
|
||||
return PageResultBuilder.Empty().WithNavigate(nameof(MainPage)).Build();
|
||||
|
||||
if (ctx.IncomingFiles?.Count > 0)
|
||||
{
|
||||
await ctx.Client.SendFilesAsync(ctx.Chat.Id, ctx.IncomingFiles, ct);
|
||||
return PageResultBuilder.Empty().WithText("Файл получен и отправлен обратно.").Build();
|
||||
}
|
||||
|
||||
return PageResultBuilder.Empty().WithText("Пришлите файл или нажмите 'Назад'.").Build();
|
||||
}
|
||||
await new MessageBuilder(ctx)
|
||||
.Text($"Получено файлов: {files.Count}", MessageFormat.Plain)
|
||||
.Inline("Далее", "next")
|
||||
.SendAsync(ct);
|
||||
}
|
||||
|
||||
public override Task OnButton(PageContext ctx, string payload, CancellationToken ct)
|
||||
=> ctx.Navigation.GoToAsync<ConfirmPage>(ctx, ct);
|
||||
|
||||
public override Task OnText(PageContext ctx, string text, CancellationToken ct)
|
||||
=> ctx.Navigation.GoToAsync<ConfirmPage>(ctx, ct);
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using BotPages.Core;
|
||||
|
||||
namespace Demo.Pages
|
||||
{
|
||||
public sealed class InlinePage : Page
|
||||
{
|
||||
public override string Id => nameof(InlinePage);
|
||||
|
||||
public override Task<PageResult> EnterAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
var actions = new[]
|
||||
{
|
||||
new PageAction { Label = "⬅️ Назад", Value = "back", Placement = ActionPlacement.Inline, Row = 0 }
|
||||
};
|
||||
|
||||
return Task.FromResult(
|
||||
PageResultBuilder.Empty()
|
||||
.WithText("Это страница с Inline‑кнопками.")
|
||||
.WithKeyboard(actions)
|
||||
.Build()
|
||||
);
|
||||
}
|
||||
|
||||
public override Task<PageResult> HandleAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
if (ctx.Text == "back")
|
||||
return Task.FromResult(PageResultBuilder.Empty().WithNavigate(nameof(MainPage)).Build());
|
||||
|
||||
return Task.FromResult(PageResultBuilder.Empty().WithText("Нажмите кнопку 'Назад'.").Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using BotPages.Core;
|
||||
|
||||
namespace Demo.Pages
|
||||
{
|
||||
public sealed class MainPage : Page
|
||||
{
|
||||
public override string Id => nameof(MainPage);
|
||||
|
||||
public override Task<PageResult> EnterAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
var actions = new[]
|
||||
{
|
||||
new PageAction(MainPageButtons.Inline) { Placement = ActionPlacement.Reply, Row = 0 },
|
||||
new PageAction(MainPageButtons.Reply) { Placement = ActionPlacement.Reply, Row = 1 },
|
||||
new PageAction(MainPageButtons.Files) { Placement = ActionPlacement.Reply, Row = 2 },
|
||||
};
|
||||
|
||||
return Task.FromResult(
|
||||
PageResultBuilder.Empty()
|
||||
.WithText("🏠 Главная страница.\nВыберите куда перейти:")
|
||||
.WithKeyboard(actions)
|
||||
.Build()
|
||||
);
|
||||
}
|
||||
|
||||
public override Task<PageResult> HandleAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
var button = ActionExtensions.FromActionLabel<MainPageButtons>(ctx.Text);
|
||||
|
||||
return button switch
|
||||
{
|
||||
MainPageButtons.Inline => Task.FromResult(PageResultBuilder.Empty().WithNavigate(nameof(InlinePage)).Build()),
|
||||
MainPageButtons.Reply => Task.FromResult(PageResultBuilder.Empty().WithNavigate(nameof(ReplyPage)).Build()),
|
||||
MainPageButtons.Files => Task.FromResult(PageResultBuilder.Empty().WithNavigate(nameof(FilesPage)).Build()),
|
||||
_ => Task.FromResult(PageResultBuilder.Empty().WithText("Выберите действие с кнопок.").Build())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum MainPageButtons
|
||||
{
|
||||
[Action("📌 Inline")]
|
||||
Inline,
|
||||
|
||||
[Action("⌨️ Reply")]
|
||||
Reply,
|
||||
|
||||
[Action("📂 Файлы")]
|
||||
Files,
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BotPages.Core;
|
||||
|
||||
namespace Demo.Pages
|
||||
{
|
||||
public sealed class ReplyPage : Page
|
||||
{
|
||||
public override string Id => nameof(ReplyPage);
|
||||
|
||||
public override Task<PageResult> EnterAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
var actions = new[]
|
||||
{
|
||||
new PageAction { Label = "⬅️ Назад", Value = "back", Placement = ActionPlacement.Reply, Row = 0 }
|
||||
};
|
||||
|
||||
return Task.FromResult(
|
||||
PageResultBuilder.Empty()
|
||||
.WithText("Это страница с Reply‑клавиатурой.")
|
||||
.WithKeyboard(actions)
|
||||
.Build()
|
||||
);
|
||||
}
|
||||
|
||||
public override Task<PageResult> HandleAsync(UpdateContext ctx, CancellationToken ct)
|
||||
{
|
||||
if (ctx.Text == "⬅️ Назад")
|
||||
return Task.FromResult(PageResultBuilder.Empty().WithNavigate(nameof(MainPage)).Build());
|
||||
|
||||
return Task.FromResult(PageResultBuilder.Empty().WithText("Нажмите кнопку 'Назад'.").Build());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
37
Demo/Pages/SubmitPage.cs
Normal file
37
Demo/Pages/SubmitPage.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Финальная страница отправки заявки.
|
||||
/// </summary>
|
||||
public sealed class SubmitPage : SingletonPage
|
||||
{
|
||||
public override async Task OnEnter(PageContext ctx, CancellationToken ct)
|
||||
{
|
||||
var progress = new MessageBuilder(ctx);
|
||||
|
||||
await progress
|
||||
.Progress("Отправка заявки", 7)
|
||||
.SendAsync(ct);
|
||||
|
||||
int i = 7;
|
||||
do
|
||||
{
|
||||
i += 25;
|
||||
Thread.Sleep(TimeSpan.FromMilliseconds(200));
|
||||
await progress
|
||||
.Progress("Отправка заявки", i)
|
||||
.SendAsync(ct);
|
||||
}
|
||||
while (i < 100);
|
||||
|
||||
await ctx.Navigation.GoToHome(ctx, ct);
|
||||
}
|
||||
|
||||
public override Task OnLeave(PageContext ctx, CancellationToken ct)
|
||||
{
|
||||
return new MessageBuilder(ctx).Text("Заявка отправлена").SendAsync(ct);
|
||||
}
|
||||
}
|
||||
18
Demo/Pages/TitlePage.cs
Normal file
18
Demo/Pages/TitlePage.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages;
|
||||
/// <summary>
|
||||
/// Страница ввода заголовка заявки.
|
||||
/// </summary>
|
||||
public sealed class TitlePage : SingletonPage
|
||||
{
|
||||
public override Task OnEnter(PageContext ctx, CancellationToken ct)
|
||||
=> new MessageBuilder(ctx)
|
||||
.Text("Введите заголовок заявки:", MessageFormat.Markdown)
|
||||
.SendAsync(ct);
|
||||
|
||||
public override Task OnText(PageContext ctx, string text, CancellationToken ct)
|
||||
=> ctx.Navigation.GoToAsync<DetailsPage, DetailsArgs>(ctx, new DetailsArgs { Title = text }, ct);
|
||||
}
|
||||
56
Demo/Pages/WelcomePage.cs
Normal file
56
Demo/Pages/WelcomePage.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Messaging;
|
||||
|
||||
namespace Demo.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// Стартовая страница демо‑бота.
|
||||
/// </summary>
|
||||
public sealed class WelcomePage : SingletonPage
|
||||
{
|
||||
public override Task OnEnter(PageContext ctx, CancellationToken ct)
|
||||
=> new MessageBuilder(ctx)
|
||||
.Text("Добро пожаловать! 🚀")
|
||||
.Reply(WelcomePageButtons.CreateRequest)
|
||||
.Reply(WelcomePageButtons.Help)
|
||||
.Reply(WelcomePageButtons.SendFile)
|
||||
.SendAsync(ct);
|
||||
|
||||
public override Task OnText(PageContext ctx, string text, CancellationToken ct)
|
||||
{
|
||||
var button = ButtonExtensions.FromButtonLabel<WelcomePageButtons>(text);
|
||||
|
||||
switch (button)
|
||||
{
|
||||
case WelcomePageButtons.CreateRequest:
|
||||
{
|
||||
return ctx.Navigation.GoToAsync<TitlePage>(ctx, ct);
|
||||
}
|
||||
|
||||
case WelcomePageButtons.Help:
|
||||
{
|
||||
return new MessageBuilder(ctx).Text("Здесь будет справка.", MessageFormat.Plain).SendAsync(ct);
|
||||
}
|
||||
|
||||
case WelcomePageButtons.SendFile:
|
||||
{
|
||||
return ctx.Navigation.GoToAsync<FileSendPage>(ctx, ct);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnText(ctx, text, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public enum WelcomePageButtons
|
||||
{
|
||||
[Button("Создать заявку")]
|
||||
CreateRequest,
|
||||
|
||||
[Button("Помощь")]
|
||||
Help,
|
||||
|
||||
[Button("Отправка файла")]
|
||||
SendFile,
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Logging;
|
||||
using BotPages.Core.Middleware;
|
||||
using BotPages.Core.Storage;
|
||||
using BotPages.Telegram;
|
||||
using Demo.Pages;
|
||||
using Telegram.Bot;
|
||||
|
||||
namespace Demo
|
||||
{
|
||||
@@ -9,57 +12,30 @@ namespace Demo
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
// Токен Telegram бота
|
||||
var token = Environment.GetEnvironmentVariable("TELEGRAM_TOKEN") ?? throw new InvalidOperationException("TELEGRAM_TOKEN not set");
|
||||
var token = Environment.GetEnvironmentVariable("TELEGRAM_TOKEN")
|
||||
?? throw new InvalidOperationException("TELEGRAM_TOKEN not set");
|
||||
|
||||
// Инициализация Telegram клиента
|
||||
var botClient = new TelegramBotClient(token);
|
||||
var chatClient = new TelegramClientAdapter(botClient);
|
||||
var logger = new ConsoleLogger();
|
||||
var state = new InMemoryStateStorage();
|
||||
|
||||
var telegram = new TelegramAdapter(logger);
|
||||
var factory = new MultiAdapterFactory()
|
||||
.Register("Telegram", telegram);
|
||||
|
||||
// Регистрируем страницы
|
||||
var pages = new IPage[]
|
||||
{
|
||||
new MainPage(),
|
||||
new InlinePage(),
|
||||
new ReplyPage(),
|
||||
new FilesPage()
|
||||
};
|
||||
var registry = new PageRegistry(pages, pages[0]);
|
||||
var app = new BotPagesApp(factory, state, logger)
|
||||
.AddDefaultPage<WelcomePage>()
|
||||
.MapCommand<WelcomePage>("/start")
|
||||
.AddMiddleware(new ErrorHandlingMiddleware(logger))
|
||||
.AddMiddleware(new LoggingMiddleware(logger));
|
||||
|
||||
// Навигация и состояние
|
||||
IStateStore store = new InMemoryStateStore();
|
||||
INavigationService nav = new NavigationService(registry, store);
|
||||
var router = new Router(registry);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
var middleware = new IUpdateMiddleware[]
|
||||
{
|
||||
new LoggingMiddleware(), //логирование вызова в консоль
|
||||
new ErrorHandlingMiddleware(), //обработчик ошибок
|
||||
//new ThrottleMiddleware(TimeSpan.FromMilliseconds(150)), //задержка в 150мс перед ответом
|
||||
};
|
||||
await telegram.StartPollingAsync(token,
|
||||
update => app.HandleUpdateAsync(update, CancellationToken.None),
|
||||
cts.Token);
|
||||
|
||||
var pipeline = new Pipeline(middleware, router);
|
||||
|
||||
botClient.StartReceiving(
|
||||
async (bot, update, ct) =>
|
||||
{
|
||||
var ctx = TelegramUpdateMapper.Map(bot, nav, store, update);
|
||||
|
||||
await pipeline.ExecuteAsync(ctx, ct);
|
||||
},
|
||||
(bot, error, ct) =>
|
||||
{
|
||||
Console.WriteLine($"⚠️ Ошибка Telegram: {error}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
);
|
||||
|
||||
var me = await botClient.GetMe();
|
||||
|
||||
Console.WriteLine($"BotPages Demo (@{me.Username}) запущен. Нажмите Enter для выхода.");
|
||||
Console.ReadLine();
|
||||
Console.ReadKey();
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user