Skip to content

Commit 18226d2

Browse files
committed
Add new bot commands and enhance file handling
- Added `/receipt` command in `CafeController.cs` with an empty handler. - Enhanced `Program.cs` to register bot commands using `RegisterCommands`. - Updated `EmptyResult.cs` for a detailed `ExecuteResultAsync` summary. - Changed `InlineResult` and `MarkdownResult` classes to public and added XML docs. - Introduced `FileResult` class for handling file responses. - Added `CommandRegistrationBuilder` for bot command registration. - Modified `BotApp.cs` to register commands and dispose of resources properly. - Added `File` and `Delete` methods in `BotControllerBase.cs`. - Removed redundant `Delete` method from `BotControllerBase.cs`. - Updated `BotBuilder.cs` to include `RegisterCommands`. - Adjusted DI in `BotApp.cs` to use `GetService` for `IHostApplicationLifetime`. - Added necessary `using` directives for new functionality.
1 parent 0db15db commit 18226d2

10 files changed

Lines changed: 266 additions & 27 deletions

File tree

Sources/TelegramBot.ConsoleTest/Controllers/CafeController.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,11 @@ public async Task<IActionResult> HandleBurgersDoneAsync()
6969
await telegramBotClient.SendTextMessageAsync(customerId, text);
7070
return Text(text);
7171
}
72+
73+
[TextCommand("/receipt")]
74+
public IActionResult HandleReceipt()
75+
{
76+
77+
}
7278
}
7379
}

Sources/TelegramBot.ConsoleTest/Program.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ public class Program
1111
public static void Main(string[] args)
1212
{
1313
BotBuilder builder = new BotBuilder(args)
14-
.UseApiKey(x => x.FromConfiguration());
14+
.UseApiKey(x => x.FromConfiguration())
15+
.RegisterCommands(x =>
16+
{
17+
x.RegisterCommand("/start", "initiates the bot")
18+
.RegisterCommand("/help", "shows help message")
19+
.RegisterCommand("/burgers", "shows burgers menu")
20+
.RegisterCommand("/burgersdone", "notifies that the order is ready");
21+
}, "en");
1522

1623
builder.Services
1724
.AddDbContext<AppDbContext>(x => x.UseSqlite("Data Source=app.db"))

Sources/TelegramBot/ActionResults/EmptyResult.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ namespace TelegramBot.ActionResults
99
public class EmptyResult : IActionResult
1010
{
1111
/// <summary>
12-
/// Does nothing.
12+
/// Executes the result asynchronously.
1313
/// </summary>
14-
/// <param name="context">The context.</param>
15-
/// <returns>Task representing the result of the action.</returns>
14+
/// <param name="context">Action context.</param>
15+
/// <returns>The task representing the result of the action.</returns>
1616
public Task ExecuteResultAsync(ActionContext context)
1717
{
1818
return Task.CompletedTask;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Telegram.Bot;
5+
using Telegram.Bot.Types;
6+
using TelegramBot.Abstractions;
7+
8+
namespace TelegramBot.ActionResults
9+
{
10+
/// <summary>
11+
/// File result with disposing.
12+
/// </summary>
13+
public class FileResult : IActionResult, IDisposable
14+
{
15+
private bool _disposed;
16+
private readonly string _fileName;
17+
private readonly Stream _fileStream;
18+
private readonly bool _disposeStream = true;
19+
20+
/// <summary>
21+
/// Creates a new instance of <see cref="FileResult"/>.
22+
/// </summary>
23+
/// <param name="filePath">Full path to the file.</param>
24+
public FileResult(string filePath)
25+
{
26+
_fileName = Path.GetFileName(filePath);
27+
_fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
28+
}
29+
30+
/// <summary>
31+
/// Disposes the file stream if it is applicable.
32+
/// </summary>
33+
public void Dispose()
34+
{
35+
if (!_disposed)
36+
{
37+
_fileStream.Dispose();
38+
_disposed = true;
39+
}
40+
}
41+
42+
/// <summary>
43+
/// Executes the result asynchronously.
44+
/// </summary>
45+
/// <param name="context">Action context.</param>
46+
/// <returns>The task representing the result of the action.</returns>
47+
public Task ExecuteResultAsync(ActionContext context)
48+
{
49+
InputFile file = InputFile.FromStream(_fileStream, _fileName);
50+
return context.Bot.SendDocumentAsync(context.ChatId, document: file);
51+
}
52+
}
53+
}

Sources/TelegramBot/ActionResults/InlineResult.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,44 @@
66

77
namespace TelegramBot.ActionResults
88
{
9-
internal class InlineResult : IActionResult
9+
/// <summary>
10+
/// Inline query result.
11+
/// </summary>
12+
public class InlineResult : IActionResult
1013
{
14+
/// <summary>
15+
/// Inline message (caption).
16+
/// </summary>
1117
public string Text { get; }
18+
19+
/// <summary>
20+
/// Use markdown or just plain text.
21+
/// </summary>
1222
public bool UseMarkdown { get; }
23+
24+
/// <summary>
25+
/// Inline keyboard.
26+
/// </summary>
1327
public InlineKeyboardMarkup Keyboard { get; }
1428

29+
/// <summary>
30+
/// Creates a new instance of <see cref="InlineResult"/>.
31+
/// </summary>
32+
/// <param name="text">Inline message (caption).</param>
33+
/// <param name="keyboard">Inline keyboard.</param>
34+
/// <param name="useMarkdown">Use markdown or just plain text.</param>
1535
public InlineResult(string text, InlineKeyboardMarkup keyboard, bool useMarkdown)
1636
{
1737
Text = text;
1838
Keyboard = keyboard;
1939
UseMarkdown = useMarkdown;
2040
}
2141

42+
/// <summary>
43+
/// Executes the result asynchronously.
44+
/// </summary>
45+
/// <param name="context">Action context.</param>
46+
/// <returns>The task representing the result of the action.</returns>
2247
public async Task ExecuteResultAsync(ActionContext context)
2348
{
2449
ParseMode? parseMode = null;

Sources/TelegramBot/ActionResults/MarkdownResult.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,30 @@
55

66
namespace TelegramBot.ActionResults
77
{
8-
internal class MarkdownResult : IActionResult
8+
/// <summary>
9+
/// Markdown result.
10+
/// </summary>
11+
public class MarkdownResult : IActionResult
912
{
13+
/// <summary>
14+
/// Markdown message.
15+
/// </summary>
1016
public string Text { get; }
1117

18+
/// <summary>
19+
/// Creates a new instance of <see cref="MarkdownResult"/>.
20+
/// </summary>
21+
/// <param name="text">Markdown message.</param>
1222
public MarkdownResult(string text)
1323
{
1424
Text = text;
1525
}
1626

27+
/// <summary>
28+
/// Executes the result asynchronously.
29+
/// </summary>
30+
/// <param name="context">Action context.</param>
31+
/// <returns>The task representing the result of the action.</returns>
1732
public async Task ExecuteResultAsync(ActionContext context)
1833
{
1934
await context.Bot.SendTextMessageAsync(context.ChatId, Text, parseMode: ParseMode.MarkdownV2);

Sources/TelegramBot/BotApp.cs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Newtonsoft.Json.Linq;
1717
using TelegramBot.Attributes;
1818
using TelegramBot.Services;
19+
using TelegramBot.Builders;
1920

2021
namespace TelegramBot
2122
{
@@ -122,8 +123,21 @@ public async Task StartAsync(CancellationToken cancellationToken = default)
122123
await hostedService.StartAsync(mergedToken);
123124
_logger.LogInformation("Started '{hostedService}'.", hostedService.GetType().Name);
124125
}
125-
var hostApplicationLifetime = _serviceProvider.GetRequiredService<IHostApplicationLifetime>()
126+
var hostApplicationLifetime = _serviceProvider.GetService<IHostApplicationLifetime>()
126127
as HostApplicationLifetime ?? throw new InvalidOperationException("Host application lifetime is not registered.");
128+
var commandRegistrationBuilders = _serviceProvider.GetServices<CommandRegistrationBuilder>();
129+
if (commandRegistrationBuilders != null && commandRegistrationBuilders.Any())
130+
{
131+
foreach (var builder in commandRegistrationBuilders)
132+
{
133+
var commands = builder.Build();
134+
await _client.SetMyCommandsAsync(commands,
135+
languageCode: builder.Language,
136+
cancellationToken: mergedToken);
137+
_logger.LogInformation("Registered {count} commands for language '{language}'.",
138+
commands.Count(), builder.Language);
139+
}
140+
}
127141
hostApplicationLifetime.NotifyStarted();
128142
}
129143

@@ -225,7 +239,7 @@ private async Task HandleRequestAsync(ITelegramUpdateHandler handler, Update upd
225239
_logger.LogWarning("Method not found for message: {Text}.", update.Message?.Text);
226240
return;
227241
}
228-
if (method.GetCustomAttribute<AuthorizeAttribute>() != null
242+
if (method.GetCustomAttribute<AuthorizeAttribute>() != null
229243
|| method.DeclaringType?.GetCustomAttribute<AuthorizeAttribute>() != null)
230244
{
231245
if (_serviceProvider.GetService<IBotAuthorizationHandler>() is IBotAuthorizationHandler authorizationHandler)
@@ -268,6 +282,25 @@ await authorizationHandler
268282
{
269283
throw new InvalidOperationException("Invalid result type: " + result.GetType().Name);
270284
}
285+
286+
if (controller is IAsyncDisposable asyncDisposable)
287+
{
288+
await asyncDisposable.DisposeAsync();
289+
}
290+
else if (controller is IDisposable disposable)
291+
{
292+
disposable.Dispose();
293+
}
294+
295+
if (result is IAsyncDisposable asyncDisposableResult)
296+
{
297+
await asyncDisposableResult.DisposeAsync();
298+
}
299+
else if (result is IDisposable disposableResult)
300+
{
301+
disposableResult.Dispose();
302+
}
303+
271304
}
272305

273306
private void CheckDisposed()

Sources/TelegramBot/Builders/BotBuilder.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Extensions.Configuration;
1313
using Microsoft.Extensions.DependencyInjection;
1414
using TelegramBot.Controllers;
15+
using System.Collections.Generic;
1516

1617
namespace TelegramBot.Builders
1718
{
@@ -122,6 +123,20 @@ public BotBuilder UseServices(IServiceCollection services)
122123
return this;
123124
}
124125

126+
/// <summary>
127+
/// Register commands for the bot.
128+
/// </summary>
129+
/// <param name="setup">The setup for the command registration.</param>
130+
/// <param name="language">The language for the commands, default is English.</param>
131+
/// <returns>This instance of <see cref="BotBuilder"/>.</returns>
132+
public BotBuilder RegisterCommands(Action<CommandRegistrationBuilder> setup, string language = "en")
133+
{
134+
CommandRegistrationBuilder builder = new CommandRegistrationBuilder(language);
135+
setup(builder);
136+
Services.AddSingleton(builder);
137+
return this;
138+
}
139+
125140
/// <summary>
126141
/// Build the bot.
127142
/// </summary>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Linq;
3+
using Telegram.Bot.Types;
4+
using System.Collections.Generic;
5+
6+
namespace TelegramBot.Builders
7+
{
8+
/// <summary>
9+
/// Command registration builder.
10+
/// </summary>
11+
public class CommandRegistrationBuilder
12+
{
13+
internal string Language { get; set; } = string.Empty;
14+
private readonly List<BotCommand> botCommands = new List<BotCommand>();
15+
16+
/// <summary>
17+
/// Creates a new instance of the <see cref="CommandRegistrationBuilder"/> class.
18+
/// </summary>
19+
/// <param name="language">Language code of the commands.</param>
20+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="language"/> is null or empty.</exception>
21+
public CommandRegistrationBuilder(string language)
22+
{
23+
if (string.IsNullOrWhiteSpace(language))
24+
{
25+
throw new ArgumentNullException(nameof(language));
26+
}
27+
Language = language;
28+
}
29+
30+
/// <summary>
31+
/// Registers a command with a description.
32+
/// </summary>
33+
/// <param name="command">Text of the command, 1-32 characters. Can contain only lowercase English letters, digits and underscores.</param>
34+
/// <param name="description">Description of the command, 3-256 characters.</param>
35+
/// <returns>The <see cref="CommandRegistrationBuilder"/>.</returns>
36+
public CommandRegistrationBuilder RegisterCommand(string command, string description)
37+
{
38+
if (string.IsNullOrWhiteSpace(command))
39+
{
40+
throw new ArgumentNullException(nameof(command));
41+
}
42+
if (string.IsNullOrWhiteSpace(description))
43+
{
44+
throw new ArgumentNullException(nameof(description));
45+
}
46+
while (command.StartsWith('/'))
47+
{
48+
command = command[1..];
49+
}
50+
if (command.Length < 1 || command.Length > 32)
51+
{
52+
throw new ArgumentOutOfRangeException(nameof(command), "Command must be 1-32 characters long.");
53+
}
54+
if (!command.All(char.IsLower) && !command.All(char.IsDigit) && !command.All(char.IsLetter))
55+
{
56+
throw new ArgumentException("Command can contain only lowercase English letters, digits and underscores.", nameof(command));
57+
}
58+
var botCommand = new BotCommand()
59+
{
60+
Command = command,
61+
Description = description
62+
};
63+
botCommands.Add(botCommand);
64+
return this;
65+
}
66+
67+
/// <summary>
68+
/// Builds the collection of bot commands.
69+
/// </summary>
70+
/// <returns>The collection of bot commands.</returns>
71+
public IEnumerable<BotCommand> Build()
72+
{
73+
return botCommands;
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)