diff --git a/lib/src/televerse/bot.dart b/lib/src/televerse/bot.dart index b1385db0..0ef1dd32 100644 --- a/lib/src/televerse/bot.dart +++ b/lib/src/televerse/bot.dart @@ -256,7 +256,8 @@ class Bot { if (_pendingCalls.isNotEmpty) { for (final fn in _pendingCalls) { - fn.call(); + final mirror = reflect(fn); + mirror.invoke(#fn, fn.params, fn.namedParams); } _pendingCalls.clear(); } @@ -270,59 +271,83 @@ class Bot { /// Whether the function is async or not. /// Thanks to StackOverflow answer: https://stackoverflow.com/a/63109983/10006183 - bool _checkSync(Function fn) { + bool _isAsync(Function fn) { if (fn is Future Function(Never)) return true; return false; } + /// Preprocessing the Context. + /// + /// This method sets additional info if any has to be set. For example, this method currently sets `Context.matches` + /// parameter if the [scope] is a RegExp handler. + void _preProcess(HandlerScope scope, Context context) async { + if (scope.isRegExp) { + final text = context.msg?.text ?? ""; + if (scope.pattern != null) { + context._matches = scope.pattern!.allMatches(text).toList(); + } + } + } + + Future _processUpdate(HandlerScope scope, Context context) async { + if (_isAsync(scope.handler!)) { + try { + await ((scope.handler!(context)) as Future); + } catch (err, stack) { + if (_onError != null) { + final botErr = BotError(err, stack, ctx: context); + await _onError!(botErr); + } else { + rethrow; + } + } + } else { + try { + scope.handler!(context); + } catch (err, stack) { + if (_onError != null) { + final botErr = BotError(err, stack, ctx: context); + await _onError!(botErr); + } else { + rethrow; + } + } + } + } + /// Emit new update into the stream. void _onUpdate(Update update) async { final sub = _handlerScopes.reversed.where((scope) { return scope.types.contains(update.type); }).toList(); int len = sub.length; - for (int i = 0; i < len; i++) { - final context = Context(this, update: update); + final context = Context(this, update: update); + + final forks = + sub.indexed.where((s) => s.$2.forked).map((e) => e.$1).toList(); + + for (int i = 0; i < forks.length; i++) { + final passing = sub[forks[i]].predicate(context); + if (!passing) continue; + _preProcess(sub[forks[i]], context); + _processUpdate(sub[forks[i]], context); + sub[forks[i]].executed(); + } + + for (int i = 0; i < len; i++) { if (sub[i].isConversation && sub[i].predicate(context)) { break; } if (sub[i].handler == null) continue; + if (sub[i].isExecuted) continue; - if (sub[i].special) { - if (sub[i].isRegExp) { - final text = context.msg?.text ?? ""; - if (sub[i].pattern != null) { - context._matches = sub[i].pattern!.allMatches(text).toList(); - } - } - } + _preProcess(sub[i], context); if (sub[i].predicate(context)) { - if (_checkSync(sub[i].handler!)) { - try { - await ((sub[i].handler!(context)) as Future); - } catch (err, stack) { - if (_onError != null) { - final botErr = BotError(err, stack, ctx: context); - await _onError!(botErr); - } else { - rethrow; - } - } - } else { - try { - sub[i].handler!(context); - } catch (err, stack) { - if (_onError != null) { - final botErr = BotError(err, stack, ctx: context); - await _onError!(botErr); - } else { - rethrow; - } - } - } + await _processUpdate(sub[i], context); + sub[i].executed(); break; } } @@ -402,19 +427,21 @@ class Bot { /// /// Example: /// ```dart - /// bot.command('start', (ctx) { - /// ctx.reply('Hello!'); + /// bot.command('start', (ctx) async { + /// await ctx.reply('Hello!'); /// }); /// ``` /// /// This will reply "Hello!" to any message that starts with `/start`. void command( Pattern command, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { if (initialized) { final scope = HandlerScope( isCommand: true, + options: options, handler: callback, types: UpdateType.messages(), predicate: (ctx) { @@ -438,6 +465,9 @@ class Bot { _PendingCall( fn: this.command, params: [command, callback], + namedParams: { + #options: options, + }, ), ); } @@ -447,10 +477,10 @@ class Bot { void _internalCallbackQueryRegister( Pattern data, Handler callback, { - String? name, + ScopeOptions? options, }) { final scope = HandlerScope( - name: name, + options: options, handler: callback, types: [UpdateType.callbackQuery], predicate: (ctx) { @@ -471,10 +501,10 @@ class Bot { void _acceptAll( Handler callback, List types, { - String? name, + ScopeOptions? options, }) { final scope = HandlerScope( - name: name, + options: options, handler: callback, types: types, predicate: (_) => true, @@ -502,8 +532,13 @@ class Bot { Pattern data, Handler callback, { @Deprecated("Use the 'data' parameter instead.") RegExp? regex, + ScopeOptions? options, }) { - return _internalCallbackQueryRegister(data, callback); + return _internalCallbackQueryRegister( + data, + callback, + options: options, + ); } /// Registers a callback for particular chat types. @@ -526,9 +561,11 @@ class Bot { /// the [chatTypes] method. void chatType( ChatType type, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.message, @@ -559,9 +596,11 @@ class Bot { /// from a private chat or a group. void chatTypes( List types, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.message, @@ -594,10 +633,10 @@ class Bot { void filter( bool Function(Context ctx) predicate, Handler callback, { - String? name, + ScopeOptions? options, }) { final scope = HandlerScope( - name: name, + options: options, handler: callback, types: UpdateType.values, predicate: predicate, @@ -618,9 +657,11 @@ class Bot { /// ``` void text( String text, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: UpdateType.messages(), predicate: (ctx) { @@ -649,9 +690,11 @@ class Bot { /// matches the regular expression `Hello, (.*)!`. void hears( RegExp exp, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, pattern: exp, isRegExp: true, handler: callback, @@ -669,9 +712,11 @@ class Bot { /// The callback will be called when an inline query with the specified query is received. void inlineQuery( Pattern query, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.inlineQuery, @@ -692,13 +737,27 @@ class Bot { } /// Registers a callback for the `/settings` command. - void settings(Handler handler) { - return command("settings", handler); + void settings( + Handler handler, { + ScopeOptions? options, + }) { + return command( + "settings", + handler, + options: options, + ); } /// Registers a callback for the `/help` command. - void help(Handler handler) { - return command("help", handler); + void help( + Handler handler, { + ScopeOptions? options, + }) { + return command( + "help", + handler, + options: options, + ); } /// Registers a callback for any unexpected error. @@ -787,8 +846,10 @@ class Bot { MessageEntityType type, Handler callback, { bool shouldMatchCaptionEntities = false, + ScopeOptions? options, }) { final scope = HandlerScope( + options: options, handler: callback, types: UpdateType.messages(), predicate: (ctx) { @@ -815,8 +876,10 @@ class Bot { List types, Handler callback, { bool shouldMatchCaptionEntities = false, + ScopeOptions? options, }) { final scope = HandlerScope( + options: options, handler: callback, types: UpdateType.messages(), predicate: (ctx) { @@ -843,9 +906,11 @@ class Bot { required Handler callback, ChatMemberStatus? oldStatus, ChatMemberStatus? newStatus, + ScopeOptions? options, }) { final scope = HandlerScope( handler: callback, + options: options, types: [ UpdateType.chatMember, ], @@ -884,8 +949,10 @@ class Bot { Handler callback, { ChatMemberStatus? oldStatus, ChatMemberStatus? newStatus, + ScopeOptions? options, }) { return _internalChatMemberUpdatedHandling( + options: options, callback: callback, oldStatus: oldStatus, newStatus: newStatus, @@ -900,20 +967,26 @@ class Bot { required Handler callback, ChatMemberStatus? oldStatus, ChatMemberStatus? newStatus, + ScopeOptions? options, }) { return _internalChatMemberUpdatedHandling( callback: callback, oldStatus: oldStatus, newStatus: newStatus, + options: options, ); } /// Registers a callback for the [Update.poll] events. void poll( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.poll], name: name); + return _acceptAll( + callback, + [UpdateType.poll], + options: options, + ); } /// Registers a callback for the [Update.pollAnswer] events. @@ -922,8 +995,10 @@ class Bot { void pollAnswer( Handler callback, { String? pollId, + ScopeOptions? options, }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.pollAnswer, @@ -941,16 +1016,25 @@ class Bot { /// The callback will be called when a chosen inline result is received. void chosenInlineResult( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.chosenInlineResult], name: name); + return _acceptAll( + callback, + [UpdateType.chosenInlineResult], + options: options, + ); } /// Registers a callback for the [Update.chatJoinRequest] events of the particular chat mentioned by the chatId. /// /// The callback will be called when a chat join request is received. - void chatJoinRequest(ID chatId, Handler callback) { + void chatJoinRequest( + ID chatId, + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.chatJoinRequest, @@ -964,9 +1048,11 @@ class Bot { /// Registers a callback for Shipping Query events for the specified User. void shippingQuery( ID chatId, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.shippingQuery, @@ -982,9 +1068,11 @@ class Bot { /// Registers a callback for Pre Checkout Query events. void preCheckoutQuery( ChatID chatId, - Handler callback, - ) { + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.preCheckoutQuery, @@ -1001,11 +1089,13 @@ class Bot { void onURL( Handler callback, { bool shouldMatchCaptionEntities = false, + ScopeOptions? options, }) { return entity( MessageEntityType.url, callback, shouldMatchCaptionEntities: shouldMatchCaptionEntities, + options: options, ); } @@ -1013,11 +1103,13 @@ class Bot { void onEmail( Handler callback, { bool shouldMatchCaptionEntities = false, + ScopeOptions? options, }) { return entity( MessageEntityType.email, callback, shouldMatchCaptionEntities: shouldMatchCaptionEntities, + options: options, ); } @@ -1025,11 +1117,13 @@ class Bot { void onPhoneNumber( Handler callback, { bool shouldMatchCaptionEntities = false, + ScopeOptions? options, }) { return entity( MessageEntityType.phoneNumber, callback, shouldMatchCaptionEntities: shouldMatchCaptionEntities, + options: options, ); } @@ -1038,16 +1132,19 @@ class Bot { Handler callback, { bool shouldMatchCaptionEntities = false, String? hashtag, + ScopeOptions? options, }) { if (hashtag == null) { return entity( MessageEntityType.hashtag, callback, shouldMatchCaptionEntities: shouldMatchCaptionEntities, + options: options, ); } final scope = HandlerScope( + options: options, handler: callback, types: UpdateType.messages(), predicate: (ctx) { @@ -1083,15 +1180,18 @@ class Bot { Handler callback, { String? username, int? userId, + ScopeOptions? options, }) { if (username == null && userId == null) { return entity( MessageEntityType.mention, callback, + options: options, ); } if (username != null) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.message, @@ -1108,6 +1208,7 @@ class Bot { } final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.message, @@ -1131,12 +1232,14 @@ class Bot { /// This method sets up a callback to be fired when the bot is mentioned. void whenMentioned( - Handler callback, - ) async { + Handler callback, { + ScopeOptions? options, + }) async { if (initialized) { return onMention( callback, username: me.username, + options: options, ); } if (_getMeStatus == _GetMeStatus.pending) { @@ -1144,88 +1247,192 @@ class Bot { _PendingCall( fn: whenMentioned, params: [callback], + namedParams: { + #options: options, + }, ), ); } } /// Registers a callback for all Message updates - void onMessage(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.message], name: name); + void onMessage( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.message], + options: options, + ); } /// Registers a callback for all Edited Message updates - void onEditedMessage(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.editedMessage], name: name); + void onEditedMessage( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.editedMessage], + options: options, + ); } /// Registers a callback for all Channel Post updates - void onChannelPost(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.channelPost], name: name); + void onChannelPost( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.channelPost], + options: options, + ); } /// Registers a callback for all Edited Channel Post updates - void onEditedChannelPost(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.editedChannelPost], name: name); + void onEditedChannelPost( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.editedChannelPost], + options: options, + ); } /// Registers a callback for all Inline Query updates - void onInlineQuery(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.inlineQuery], name: name); + void onInlineQuery( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.inlineQuery], + options: options, + ); } /// Registers a callback for all Chosen Inline Result updates - void onChosenInlineResult(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.chosenInlineResult], name: name); + void onChosenInlineResult( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.chosenInlineResult], + options: options, + ); } /// Registers a callback for all Callback Query updates - void onCallbackQuery(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.callbackQuery], name: name); + void onCallbackQuery( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.callbackQuery], + options: options, + ); } /// Registers a callback for all Shipping Query updates - void onShippingQuery(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.shippingQuery], name: name); + void onShippingQuery( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.shippingQuery], + options: options, + ); } /// Registers a callback for all Pre Checkout Query updates - void onPreCheckoutQuery(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.preCheckoutQuery], name: name); + void onPreCheckoutQuery( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.preCheckoutQuery], + options: options, + ); } /// Registers a callback to be fired for all successful payments - void onSuccessfulPayment(Handler callback, {String? name}) { + void onSuccessfulPayment( + Handler callback, { + ScopeOptions? options, + }) { return _internalSubMessageHandler( callback, (ctx) => ctx.message?.successfulPayment != null, - name: name, + options: options, ); } /// Registers a callback for all Poll updates - void onPoll(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.poll], name: name); + void onPoll( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.poll], + options: options, + ); } /// Registers a callback for all Poll Answer updates - void onPollAnswer(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.pollAnswer], name: name); + void onPollAnswer( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.pollAnswer], + options: options, + ); } /// Registers a callback for all My Chat Member updates - void onMyChatMember(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.myChatMember], name: name); + void onMyChatMember( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.myChatMember], + options: options, + ); } /// Registers a callback for all Chat Member updates - void onChatMember(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.chatMember], name: name); + void onChatMember( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.chatMember], + options: options, + ); } /// Registers a callback for all Chat Join Request updates - void onChatJoinRequest(Handler callback, {String? name}) { - return _acceptAll(callback, [UpdateType.chatJoinRequest], name: name); + void onChatJoinRequest( + Handler callback, { + ScopeOptions? options, + }) { + return _acceptAll( + callback, + [UpdateType.chatJoinRequest], + options: options, + ); } /// On Stop Handler @@ -1244,11 +1451,11 @@ class Bot { bool Function(Context) predicate, { bool includeChannelPosts = false, bool onlyChannelPosts = false, - String? name, ID? chatId, + ScopeOptions? options, }) { final scope = HandlerScope( - name: name, + options: options, handler: callback, types: [ if (includeChannelPosts || onlyChannelPosts) UpdateType.channelPost, @@ -1276,6 +1483,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1283,6 +1491,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1297,6 +1506,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1304,6 +1514,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1318,6 +1529,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1325,6 +1537,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1339,6 +1552,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1346,6 +1560,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1360,6 +1575,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1367,6 +1583,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1381,6 +1598,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1388,6 +1606,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1402,6 +1621,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1409,6 +1629,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1416,11 +1637,13 @@ class Bot { void onContact( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.contact != null, chatId: chatId, + options: options, ); } @@ -1435,6 +1658,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1442,6 +1666,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1456,6 +1681,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1463,6 +1689,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1477,6 +1704,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1484,6 +1712,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1498,6 +1727,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1505,6 +1735,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1519,6 +1750,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1526,6 +1758,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1533,12 +1766,14 @@ class Bot { void onLiveLocation( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.location != null && ctx.msg?.location!.livePeriod != null, chatId: chatId, + options: options, ); } @@ -1553,6 +1788,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1560,6 +1796,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1574,6 +1811,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1581,6 +1819,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1588,11 +1827,13 @@ class Bot { void onDeleteChatPhoto( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.deleteChatPhoto != null && ctx.msg!.deleteChatPhoto!, chatId: chatId, + options: options, ); } @@ -1607,6 +1848,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1616,6 +1858,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1623,11 +1866,13 @@ class Bot { void onUsrShared( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.usersShared != null, chatId: chatId, + options: options, ); } @@ -1635,11 +1880,13 @@ class Bot { void onChatShared( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.chatShared != null, chatId: chatId, + options: options, ); } @@ -1654,6 +1901,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1663,6 +1911,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1677,6 +1926,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1686,6 +1936,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1700,6 +1951,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1709,6 +1961,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1716,11 +1969,13 @@ class Bot { void whenVideoChatParticipantsInvited( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.videoChatParticipantsInvited != null, chatId: chatId, + options: options, ); } @@ -1728,11 +1983,13 @@ class Bot { void onForumTopicCreated( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.forumTopicCreated != null, chatId: chatId, + options: options, ); } @@ -1740,11 +1997,13 @@ class Bot { void onForumTopicEdited( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.forumTopicEdited != null, chatId: chatId, + options: options, ); } @@ -1752,11 +2011,13 @@ class Bot { void onForumTopicClosed( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.forumTopicClosed != null, chatId: chatId, + options: options, ); } @@ -1764,11 +2025,13 @@ class Bot { void onForumTopicReopened( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.forumTopicReopened != null, chatId: chatId, + options: options, ); } @@ -1776,11 +2039,13 @@ class Bot { void onWebAppData( Handler callback, { ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, (ctx) => ctx.msg?.webAppData != null, chatId: chatId, + options: options, ); } @@ -1795,6 +2060,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1804,6 +2070,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1818,6 +2085,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1827,6 +2095,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1841,6 +2110,7 @@ class Bot { bool includeChannelPosts = false, bool onlyChannelPosts = false, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1850,6 +2120,7 @@ class Bot { includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, chatId: chatId, + options: options, ); } @@ -1868,7 +2139,7 @@ class Bot { _internalCallbackQueryRegister( data, menu._buttons[i][j].handler as Handler, - name: "${menu.name}-$data", + options: ScopeOptions(name: "${menu.name}-$data"), ); } } @@ -1884,32 +2155,32 @@ class Bot { final action = menu._buttons[i][j].handler as Handler; switch (menu._buttons[i][j].runtimeType) { - case _KeyboardMenuTextButton: + case _KeyboardMenuTextButton _: _internalSubMessageHandler( action, (ctx) => ctx.message?.text == text, - name: name, + options: ScopeOptions(name: name), ); break; - case _KeyboardMenuRequestContactButton: + case _KeyboardMenuRequestContactButton _: _internalSubMessageHandler( action, (ctx) => ctx.message?.contact != null, - name: name, + options: ScopeOptions(name: name), ); break; - case _KeyboardMenuRequestLocationButton: + case _KeyboardMenuRequestLocationButton _: _internalSubMessageHandler( action, (ctx) => ctx.message?.location != null, - name: name, + options: ScopeOptions(name: name), ); break; - case _KeyboardMenuRequestUsersButton: + case _KeyboardMenuRequestUsersButton _: _internalSubMessageHandler( action, (ctx) => ctx.message?.usersShared != null, - name: name, + options: ScopeOptions(name: name), ); } } @@ -1925,7 +2196,11 @@ class Bot { } /// Next step handler - void setNextStep(Message msg, Handler callback) { + void setNextStep( + Message msg, + Handler callback, + ScopeOptions? options, + ) { final scopeName = "next-step-${msg.messageId}"; bool isNextMessage(int? messageId) { return messageId == msg.messageId + 1 || messageId == msg.messageId + 2; @@ -1940,7 +2215,7 @@ class Bot { await callback(ctx); _handlerScopes.removeWhere((scope) => scope.name == scopeName); }, - name: scopeName, + options: options, ); } @@ -1951,8 +2226,8 @@ class Bot { Handler callback, { bool includeChannelPosts = false, bool onlyChannelPosts = false, - String? name, ID? chatId, + ScopeOptions? options, }) { return _internalSubMessageHandler( callback, @@ -1965,25 +2240,33 @@ class Bot { }, includeChannelPosts: includeChannelPosts, onlyChannelPosts: onlyChannelPosts, - name: name, chatId: chatId, + options: options, ); } /// Register a callback when a reaction to a message was changed by a user. void onMessageReaction( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.messageReaction], name: name); + return _acceptAll( + callback, + [UpdateType.messageReaction], + options: options, + ); } /// Reactions to a message with anonymous reactions were changed. void onMessageReactionCount( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.messageReactionCount], name: name); + return _acceptAll( + callback, + [UpdateType.messageReactionCount], + options: options, + ); } /// Registers a callback for when the chat was boosted. @@ -1991,9 +2274,13 @@ class Bot { /// The bot must be an administrator in the chat for this to work. void onChatBoosted( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.chatBoost], name: name); + return _acceptAll( + callback, + [UpdateType.chatBoost], + options: options, + ); } /// Registers a callback to be fired when the chat boost was removed. @@ -2001,14 +2288,23 @@ class Bot { /// The bot must be an administrator in the chat for this to work. void onChatBoostRemoved( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.chatBoostRemoved], name: name); + return _acceptAll( + callback, + [UpdateType.chatBoostRemoved], + options: options, + ); } /// Registers a callback to be fired when a user reacts given emoji to a message. - void whenReacted(String emoji, Handler callback) { + void whenReacted( + String emoji, + Handler callback, { + ScopeOptions? options, + }) { final scope = HandlerScope( + options: options, handler: callback, types: [ UpdateType.messageReaction, @@ -2034,36 +2330,56 @@ class Bot { /// Registers a callback to be fired when a connection of the bot with a business account is made. void onBusinessConnection( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.businessConnection], name: name); + return _acceptAll( + callback, + [UpdateType.businessConnection], + options: options, + ); } /// Registers callback to be fired when a new message is received in a business account connected to the bot. void onBusinessMessage( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.businessMessage], name: name); + return _acceptAll( + callback, + [UpdateType.businessMessage], + options: options, + ); } /// Registers a callback to be fired when a business message is edited. void onBusinessMessageEdited( Handler callback, { - String? name, + ScopeOptions? options, }) { - return _acceptAll(callback, [UpdateType.channelPost], name: name); + return _acceptAll( + callback, + [UpdateType.channelPost], + options: options, + ); } /// Registers a callback to be fired when a business message is deleted. void onBusinessMessageDeleted( Handler callback, { - String? name, + ScopeOptions? options, }) { return _acceptAll( callback, [UpdateType.deletedBusinessMessages], - name: name, + options: options, ); } + + /// Removes an already added listener by name + bool removeScope(String name) { + final ix = _handlerScopes.indexWhere((s) => s.name == name); + if (ix == -1) return false; + _handlerScopes.removeAt(ix); + return true; + } } diff --git a/lib/src/televerse/conversation/conversation.dart b/lib/src/televerse/conversation/conversation.dart index 50a6b924..857e5895 100644 --- a/lib/src/televerse/conversation/conversation.dart +++ b/lib/src/televerse/conversation/conversation.dart @@ -383,7 +383,7 @@ class Conversation { _bot._handlerScopes.add( HandlerScope( isConversation: true, - name: scopeName, + options: ScopeOptions(name: scopeName), predicate: (ctx) => _isSameChat(ctx.update, chatId) && filter(ctx.update), types: UpdateType.values, diff --git a/lib/src/televerse/models/chat_id.dart b/lib/src/televerse/models/chat_id.dart index 9757610c..46fb6ee1 100644 --- a/lib/src/televerse/models/chat_id.dart +++ b/lib/src/televerse/models/chat_id.dart @@ -19,18 +19,19 @@ abstract class ID { /// /// If the passed [value] is neither an integer nor a string, this method will throw a [TeleverseException]. static ID create(dynamic value) { - switch (value.runtimeType) { - case int: - return ChatID.create(value); - case String: - return SupergroupID.create(value); - default: - throw TeleverseException( - "The passed value is not a valid chat id. The value must be an integer or a string.", - description: "The passed value is of type ${value.runtimeType}.", - type: TeleverseExceptionType.invalidParameter, - ); + if (value is int) { + return ChatID.create(value); } + + if (value is String) { + return ChannelID.create(value); + } + + throw TeleverseException( + "The passed value is not a valid chat id. The value must be an integer or a string.", + description: "The passed value is of type ${value.runtimeType}.", + type: TeleverseExceptionType.invalidParameter, + ); } /// Returns the id as a string or an integer. diff --git a/lib/src/televerse/models/handler_scope.dart b/lib/src/televerse/models/handler_scope.dart index e5103687..097623b6 100644 --- a/lib/src/televerse/models/handler_scope.dart +++ b/lib/src/televerse/models/handler_scope.dart @@ -2,17 +2,6 @@ part of 'models.dart'; /// A Handler Scope is used to define the scope and related information of a handler method. class HandlerScope { - /// Optional. The name of the handler. (For debugging purposes) - final String? name; - - /// Whether the handler is a special handler. - /// - /// True if it's a command handler or a RegExp handler. - /// - /// - If it's a command handler, we set `args` to the parameter of the command. - /// - If it's a RegExp handler, we'll set the `MessageContext.matches` to the matches of the RegExp. - final bool special; - /// If it's a command handler, we set `args` to the parameter of the command. final bool isCommand; @@ -23,6 +12,8 @@ class HandlerScope { final RegExp? pattern; /// Handler + /// + /// Required unless [isConversation] is `true`. final Handler? handler; /// The update type @@ -37,9 +28,14 @@ class HandlerScope { /// Chat ID of the conversation. final ID? chatId; + /// Scope Options - Additional parameters for the scope. + final ScopeOptions? options; + + /// Flag whether already executed + bool isExecuted; + /// Creates a new [HandlerScope]. - const HandlerScope({ - this.name, + HandlerScope({ this.handler, required this.predicate, required this.types, @@ -48,6 +44,18 @@ class HandlerScope { this.pattern, this.isConversation = false, this.chatId, - }) : special = isCommand || isRegExp, - assert(handler != null || isConversation); + this.options, + this.isExecuted = false, + }) : assert(handler != null || isConversation); + + /// Whether the scope is forked or not. + bool get forked => options?.forked ?? false; + + /// Name of the scope + String? get name => options?.name; + + /// Sets executiion status to true. + void executed() { + isExecuted = true; + } } diff --git a/lib/src/televerse/models/scope_options.dart b/lib/src/televerse/models/scope_options.dart new file mode 100644 index 00000000..bfdc0775 --- /dev/null +++ b/lib/src/televerse/models/scope_options.dart @@ -0,0 +1,33 @@ +part of '../../../televerse.dart'; + +/// Represents additional options that can be passed to create Handler Scope +class ScopeOptions { + /// Name of the Handler Scope. + /// + /// This can be used to remove a Handler later on your code. + final String? name; + + /// Whether the scope is forked. + /// + /// By default fork is set to `false` that is once a matching scope is found + /// the processor only executes that particular scope and skips all the rest. + /// + /// When fork is set to `true` the successive Handler Scope is also considered + /// and executed if it satisfies the predicate. + final bool forked; + + /// Constructs a `ScopeOption`. + /// + /// - [name] - Name of the Handler Scope. This can be used to remove the scope later. + /// + /// - [forked] - A flag used to determine whether the succeeding handler should also be executed + const ScopeOptions({ + this.forked = false, + this.name, + }); + + /// Constructs a `ScopeOption` that is forked. This scope will allow the processing of subsequent handler scope as well. + /// + /// - [name] - Name of the Handler Scope. This can be used to remove the scope later. + const ScopeOptions.forked({this.name}) : forked = true; +} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index ea000dc0..25fb8ab6 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -110,28 +110,13 @@ enum _GetMeStatus { class _PendingCall { final Function fn; final List params; + final Map namedParams; const _PendingCall({ required this.fn, required this.params, + this.namedParams = const {}, }); - - void call() { - switch (params.length) { - case 0: - fn.call(); - break; - case 1: - fn.call(params[0]); - break; - case 2: - fn.call(params[0], params[1]); - break; - case 3: - fn.call(params[0], params[1], params[2]); - break; - } - } } /// Extension on `Chat` to create ID of the chat diff --git a/lib/televerse.dart b/lib/televerse.dart index 7b1e0b83..7bfdb553 100644 --- a/lib/televerse.dart +++ b/lib/televerse.dart @@ -24,6 +24,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io' as io; import 'dart:math'; +import 'dart:mirrors'; import 'package:dio/dio.dart'; import 'package:televerse/telegram.dart'; import 'package:televerse/televerse.dart'; @@ -60,6 +61,7 @@ part 'src/televerse/builders/message_content_generator.dart'; /// Extras part 'src/televerse/extras/tg_exceptions.dart'; +part 'src/televerse/models/scope_options.dart'; /// The main class of the library. /// diff --git a/pubspec.yaml b/pubspec.yaml index fa3c669a..1fa09cce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ topics: - bot-framework environment: - sdk: ">=2.17.7 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: dio: ^5.4.0