Skip to content

Commit 0d00fae

Browse files
committed
Merge branch 'develop'
2 parents ae1cfc9 + 5f87a0c commit 0d00fae

File tree

11 files changed

+444
-145
lines changed

11 files changed

+444
-145
lines changed

Sources/TelegramBot.ConsoleTest/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
namespace TelegramBot.ConsoleTest
77
{
8-
internal class Program
8+
public class Program
99
{
10-
static void Main(string[] args)
10+
public static void Main(string[] args)
1111
{
1212
BotBuilder builder = new BotBuilder(args)
1313
.UseApiKey(x => x.FromConfiguration());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace TelegramBot.Abstractions
4+
{
5+
/// <summary>
6+
/// Key-value provider.
7+
/// </summary>
8+
public interface IKeyValueProvider
9+
{
10+
/// <summary>
11+
/// Gets the value of the key.
12+
/// </summary>
13+
/// <param name="key">Key to get the value for.</param>
14+
/// <returns>Value of the key.</returns>
15+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is null or empty.</exception>
16+
string GetValue(string key);
17+
18+
/// <summary>
19+
/// Sets the value of the key.
20+
/// </summary>
21+
/// <param name="key">Key to set the value for.</param>
22+
/// <param name="value">Value to set.</param>
23+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is null or empty.</exception>
24+
void SetValue(string key, string? value);
25+
}
26+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Reflection;
2+
using Telegram.Bot.Types;
3+
4+
namespace TelegramBot.Handlers
5+
{
6+
internal interface ITelegramUpdateHandler
7+
{
8+
MethodInfo GetMethodInfo();
9+
object[]? GetArguments();
10+
}
11+
}

Sources/TelegramBot/BotApp.cs

Lines changed: 74 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
using System;
2+
using System.Linq;
23
using Telegram.Bot;
34
using System.Threading;
45
using System.Reflection;
56
using Telegram.Bot.Types;
6-
using TelegramBot.Extensions;
7-
using TelegramBot.Attributes;
7+
using TelegramBot.Handlers;
88
using System.Threading.Tasks;
9+
using TelegramBot.Extensions;
910
using TelegramBot.Controllers;
1011
using TelegramBot.Abstractions;
1112
using System.Collections.Generic;
1213
using Microsoft.Extensions.Logging;
1314
using Microsoft.Extensions.DependencyInjection;
14-
using System.Linq;
15+
using Microsoft.Extensions.Hosting;
1516

1617
namespace TelegramBot
1718
{
@@ -22,8 +23,8 @@ public class BotApp : IBot
2223
{
2324
private readonly ILogger<BotApp> _logger;
2425
private readonly TelegramBotClient _client;
25-
private IReadOnlyCollection<Type> _controllers;
2626
private readonly ServiceProvider _serviceProvider;
27+
private IReadOnlyCollection<MethodInfo> _controllerMethods;
2728

2829
/// <summary>
2930
/// Creates a new instance of <see cref="BotApp"/>.
@@ -33,8 +34,8 @@ public class BotApp : IBot
3334
public BotApp(TelegramBotClient client, ServiceProvider serviceProvider)
3435
{
3536
_client = client;
36-
_controllers = new List<Type>();
3737
_serviceProvider = serviceProvider;
38+
_controllerMethods = new List<MethodInfo>();
3839
_logger = serviceProvider.GetRequiredService<ILogger<BotApp>>();
3940
}
4041

@@ -52,7 +53,9 @@ public IBot MapControllers()
5253
result.Add(type);
5354
}
5455
}
55-
_controllers = result;
56+
_controllerMethods = result
57+
.SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
58+
.ToList();
5659
return this;
5760
}
5861

@@ -62,11 +65,39 @@ public IBot MapControllers()
6265
/// <param name="token">Cancellation token (optional).</param>
6366
public void Run(CancellationToken token = default)
6467
{
65-
var botUser = _client.GetMeAsync().Result;
66-
_logger.BeginScope("Bot user: {BotUser}", botUser.Username);
67-
_client.StartReceiving(UpdateHandler, ErrorHandler, cancellationToken: token);
68-
_logger.LogInformation("Bot started - receiving updates.");
69-
Task.Delay(-1, token).Wait(token);
68+
try
69+
{
70+
var botUser = _client.GetMeAsync().Result;
71+
_client.StartReceiving(UpdateHandler, ErrorHandler, cancellationToken: token);
72+
_logger.LogInformation("Bot '{botUser}' started - receiving updates.", botUser.Username);
73+
}
74+
catch (Exception ex)
75+
{
76+
if (ex is AggregateException aggregateException)
77+
{
78+
ex = aggregateException.InnerException;
79+
}
80+
_logger.LogError(ex, "Error occurred while starting the bot. Probably the bot token is invalid or the network is not available.");
81+
throw ex;
82+
}
83+
var hostedServices = _serviceProvider.GetServices<IHostedService>();
84+
foreach (var hostedService in hostedServices)
85+
{
86+
hostedService.StartAsync(token).Wait(token);
87+
}
88+
try
89+
{
90+
Task.Delay(-1, token).Wait(token);
91+
}
92+
catch (OperationCanceledException)
93+
{
94+
_logger.LogInformation("Bot stopped - no longer receiving updates.");
95+
}
96+
_logger.LogInformation("Stopping hosted services...");
97+
foreach (var hostedService in hostedServices)
98+
{
99+
hostedService.StopAsync(token).Wait(token);
100+
}
70101
}
71102

72103
private Task ErrorHandler(ITelegramBotClient client, Exception exception, CancellationToken token)
@@ -75,141 +106,58 @@ private Task ErrorHandler(ITelegramBotClient client, Exception exception, Cancel
75106
return Task.CompletedTask;
76107
}
77108

78-
[Obsolete("Not implemented yet.")]
79109
private async Task UpdateHandler(ITelegramBotClient client, Update update, CancellationToken token)
80110
{
81111
if (update.Message != null && !string.IsNullOrWhiteSpace(update.Message.Text) && update.Message.Text.StartsWith('/'))
82112
{
83113
_logger.LogInformation("Received text message: {Text}.", update.Message.Text);
84-
await HandleTextMessageAsync(update, update.Message);
114+
var handler = new TextMessageHandler(_controllerMethods, update);
115+
await HandleRequestAsync(handler, update);
85116
}
86117
else if (update.CallbackQuery != null && update.CallbackQuery.Data != null)
87118
{
88119
_logger.LogInformation("Received inline query: {Data}.", update.CallbackQuery.Data);
89-
await HandleInlineQueryAsync(update, update.CallbackQuery);
120+
var handler = new InlineQueryHandler(_controllerMethods, update);
121+
await HandleRequestAsync(handler, update);
90122
}
91123
else
92124
{
93125
_logger.LogWarning("Unsupported update type: {UpdateType}.", update.Type);
94126
}
95127
}
96128

97-
private async Task HandleInlineQueryAsync(Update update, CallbackQuery inlineQuery)
129+
private async Task HandleRequestAsync(ITelegramUpdateHandler handler, Update update)
98130
{
99-
// /language/{language}
100-
// /language/{language}/framework/{framework}
101-
102-
string command = inlineQuery.Data!;
103-
foreach (var controllerType in _controllers)
131+
bool hasUser = update.TryGetUser(out User user);
132+
if (!hasUser)
104133
{
105-
var controller = (BotControllerBase)ActivatorUtilities.CreateInstance(_serviceProvider, controllerType);
106-
var methods = controllerType.GetMethods();
107-
foreach (var method in methods)
108-
{
109-
var attributes = method.GetCustomAttributes(typeof(InlineCommandAttribute), false);
110-
foreach (var attribute in attributes)
111-
{
112-
if (attribute is InlineCommandAttribute botCommandAttribute)
113-
{
114-
// controller command: /language/{language}
115-
// incoming command: /language/en
116-
117-
List<object> args = new List<object>();
118-
string[] controllerCommandParts = botCommandAttribute.Command.Split('/');
119-
string[] incomingCommandParts = command.Split('/');
120-
if (controllerCommandParts.Length != incomingCommandParts.Length)
121-
{
122-
continue;
123-
}
124-
bool match = true;
125-
for (int i = 0; i < controllerCommandParts.Length; i++)
126-
{
127-
if (controllerCommandParts[i] != incomingCommandParts[i] && !controllerCommandParts[i].StartsWith("{") && !controllerCommandParts[i].EndsWith("}"))
128-
{
129-
match = false;
130-
break;
131-
}
132-
if (controllerCommandParts[i].StartsWith("{") && controllerCommandParts[i].EndsWith("}"))
133-
{
134-
args.Add(incomingCommandParts[i]);
135-
}
136-
}
137-
if (match)
138-
{
139-
controller.Update = update;
140-
bool hasUser = update.TryGetUser(out User user);
141-
if (!hasUser)
142-
{
143-
_logger.LogWarning("User not found in the update {id}.", update.Id);
144-
return;
145-
}
146-
controller.User = user;
147-
var result = method.Invoke(controller, args.ToArray());
148-
if (result is Task<IActionResult> taskResult)
149-
{
150-
await (await taskResult).ExecuteResultAsync(new ActionContext(_client, user.Id));
151-
return;
152-
}
153-
else if (result is IActionResult actionResult)
154-
{
155-
await actionResult.ExecuteResultAsync(new ActionContext(_client, user.Id));
156-
return;
157-
}
158-
else
159-
{
160-
throw new InvalidOperationException("Invalid result type: " + result.GetType().Name);
161-
}
162-
}
163-
}
164-
}
165-
}
134+
return;
166135
}
167-
168-
}
169-
170-
private async Task HandleTextMessageAsync(Update update, Message message)
171-
{
172-
string command = message.Text!.Split(' ')[0];
173-
foreach (var controllerType in _controllers)
136+
var args = handler.GetArguments();
137+
MethodInfo method = handler.GetMethodInfo();
138+
if (method.ReturnType != typeof(Task<IActionResult>) && method.ReturnType != typeof(IActionResult))
174139
{
175-
var controller = (BotControllerBase)ActivatorUtilities.CreateInstance(_serviceProvider, controllerType);
176-
var methods = controllerType.GetMethods();
177-
foreach (var method in methods)
178-
{
179-
var attributes = method.GetCustomAttributes(typeof(TextCommandAttribute), false);
180-
foreach (var attribute in attributes)
181-
{
182-
if (attribute is TextCommandAttribute botCommandAttribute)
183-
{
184-
if (botCommandAttribute.Command == command)
185-
{
186-
controller.Update = update;
187-
bool hasUser = update.TryGetUser(out User user);
188-
if (!hasUser)
189-
{
190-
_logger.LogWarning("User not found in the update {id}.", update.Id);
191-
return;
192-
}
193-
controller.User = user;
194-
var result = method.Invoke(controller, new object[] { });
195-
if (result is Task<IActionResult> taskResult)
196-
{
197-
await (await taskResult).ExecuteResultAsync(new ActionContext(_client, user.Id));
198-
return;
199-
}
200-
else if (result is IActionResult actionResult)
201-
{
202-
await actionResult.ExecuteResultAsync(new ActionContext(_client, user.Id));
203-
return;
204-
}
205-
else
206-
{
207-
throw new InvalidOperationException("Invalid result type: " + result.GetType().Name);
208-
}
209-
}
210-
}
211-
}
212-
}
140+
throw new InvalidOperationException("Invalid return type: " + method.ReturnType.Name);
141+
}
142+
BotControllerBase controller = (BotControllerBase)ActivatorUtilities.CreateInstance(_serviceProvider, method.DeclaringType);
143+
controller.Update = update;
144+
controller.User = user;
145+
if (_serviceProvider.GetService<IKeyValueProvider>() is IKeyValueProvider keyValueProvider)
146+
{
147+
controller.KeyValueProvider = keyValueProvider;
148+
}
149+
var result = method.Invoke(controller, args);
150+
if (result is Task<IActionResult> taskResult)
151+
{
152+
await (await taskResult).ExecuteResultAsync(new ActionContext(_client, user.Id));
153+
}
154+
else if (result is IActionResult actionResult)
155+
{
156+
await actionResult.ExecuteResultAsync(new ActionContext(_client, user.Id));
157+
}
158+
else
159+
{
160+
throw new InvalidOperationException("Invalid result type: " + result.GetType().Name);
213161
}
214162
}
215163
}

Sources/TelegramBot/Builders/BotBuilder.cs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
2+
using Telegram.Bot;
23
using Microsoft.Extensions.Logging;
34
using Microsoft.Extensions.Configuration;
45
using Microsoft.Extensions.DependencyInjection;
5-
using Telegram.Bot;
6-
using System.Diagnostics;
6+
using System.Linq;
7+
using TelegramBot.Abstractions;
8+
using TelegramBot.Providers;
79

810
namespace TelegramBot.Builders
911
{
@@ -41,7 +43,11 @@ public BotBuilder(params string[] args)
4143
configuration.AddJsonFile("appsettings.json", optional: true);
4244
configuration.AddEnvironmentVariables();
4345
Configuration = configuration;
44-
Services.AddLogging(builder => builder.AddConsole());
46+
Services.AddLogging(builder =>
47+
{
48+
builder.AddConsole();
49+
builder.AddConfiguration(Configuration.GetSection("Logging"));
50+
});
4551
}
4652

4753
private string _baseApiUrl = Constants.DefaultBaseApiUrl;
@@ -96,6 +102,20 @@ public BotBuilder UseApiKey(Action<TelegramApiKeyBuilder> value)
96102
return this;
97103
}
98104

105+
/// <summary>
106+
/// Use custom services for the bot additionally to the built-in services.
107+
/// </summary>
108+
/// <param name="services">The services to use.</param>
109+
/// <returns>This instance of <see cref="IBotBuilder"/>.</returns>
110+
public BotBuilder UseServices(IServiceCollection services)
111+
{
112+
foreach (var service in services)
113+
{
114+
Services.Add(service);
115+
}
116+
return this;
117+
}
118+
99119
/// <summary>
100120
/// Build the bot.
101121
/// </summary>
@@ -108,21 +128,13 @@ public IBot Build()
108128
}
109129
TelegramBotClientOptions options = new TelegramBotClientOptions(_token, _baseApiUrl);
110130
TelegramBotClient client = new TelegramBotClient(options);
111-
return new BotApp(client, Services.BuildServiceProvider());
112-
}
113-
114-
/// <summary>
115-
/// Use custom services for the bot additionally to the built-in services.
116-
/// </summary>
117-
/// <param name="services">The services to use.</param>
118-
/// <returns>This instance of <see cref="IBotBuilder"/>.</returns>
119-
public BotBuilder UseServices(IServiceCollection services)
120-
{
121-
foreach (var service in services)
131+
Services.AddSingleton<ITelegramBotClient>(client);
132+
bool hasKeyValueProvider = Services.Any(service => service.ServiceType == typeof(IKeyValueProvider));
133+
if (!hasKeyValueProvider)
122134
{
123-
Services.Add(service);
135+
Services.AddSingleton<IKeyValueProvider, InMemoryKeyValueProvider>();
124136
}
125-
return this;
137+
return new BotApp(client, Services.BuildServiceProvider());
126138
}
127139
}
128140
}

0 commit comments

Comments
 (0)