11using System ;
2+ using System . Linq ;
23using Telegram . Bot ;
34using System . Threading ;
45using System . Reflection ;
56using Telegram . Bot . Types ;
6- using TelegramBot . Extensions ;
7- using TelegramBot . Attributes ;
7+ using TelegramBot . Handlers ;
88using System . Threading . Tasks ;
9+ using TelegramBot . Extensions ;
910using TelegramBot . Controllers ;
1011using TelegramBot . Abstractions ;
1112using System . Collections . Generic ;
1213using Microsoft . Extensions . Logging ;
1314using Microsoft . Extensions . DependencyInjection ;
14- using System . Linq ;
15+ using Microsoft . Extensions . Hosting ;
1516
1617namespace 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 }
0 commit comments