diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0b2893..dcc78384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.15.9 + +- Improved efficiency by removing redundant `getMe` calls while registering `command` handlers and `whenMentioned` handlers. +- Made `LongPolling.allowedUpdates` nullable, and removed the constant empty list initialization. +- Updated the README. + + # 1.15.8 - Introducing `TGException` enum which is a collection of different possible Telegram exceptions binded with their reasons and possible solutions. diff --git a/README.md b/README.md index 485c7fbc..8fac8511 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ -# Televerse +
+

Televerse

-[![Pub Version](https://img.shields.io/pub/v/televerse?color=blue&logo=blue)](https://pub.dev/packages/televerse) -![GitHub](https://img.shields.io/github/license/HeySreelal/televerse?color=green) -![](https://shields.io/badge/Latest-Bot%20API%207.2-blue) + [![Pub Version](https://img.shields.io/pub/v/televerse?color=blue&logo=blue)](https://pub.dev/packages/televerse) + ![GitHub](https://img.shields.io/github/license/HeySreelal/televerse?color=green) + ![](https://shields.io/badge/Latest-Bot%20API%207.2-blue) + + + + + +
+ +--- 🤖 `Bot API version: Bot API 7.2 (March 31, 2024)` @@ -52,16 +61,16 @@ That's it! Your bot is now ready to receive updates from Telegram. If you want to handle a specific command, such as the `/start` command, you can use the `bot.command` method. For example: ```dart -bot.command('start', (ctx) { - ctx.reply('Hello, World!'); +bot.command('start', (ctx) async { + await ctx.reply('Hello, World!'); }); ``` Starting from Televerse 1.3.1, you can use the `bot.start` method to start listening for updates and also set up a command listener for the `/start` command. That means you can simplify the code above like this: ```dart -bot.start((ctx) { - ctx.reply('Hello, World!'); +bot.start((ctx) async { + await ctx.reply('Hello, World!'); }); ``` @@ -83,21 +92,25 @@ bot.api.sendMessage(ChatID(123456), "Hello, World!"); ctx.api.sendMessage(ChatID(123456), "Hello, World!"); ``` + ## 🔊 Listening for Updates Televerse also offers a set of custom methods to simplify your development process. Some of these methods include: - `bot.command` to listen for commands -- `bot.chatType` and `bot.chatTypes` to listen for specific chat types -- `bot.text` to listen for text messages that contain specific text -There are also more advanced methods such as: +Let's see an example: -- `bot.filter` to create your own filters and listen for messages that match them -- `bot.hear` to listen for messages that match a RegExp -- `bot.on` to listen for specific events +```dart +bot.command("hello", (ctx) async { + await ctx.reply("Hello World 👋"); + // ... +}); +``` -These methods are very powerful and can be used to create your own custom filters. +Similarly, you have access to many listener methods including `bot.hear`, `bot.on`, `bot.callbackQuery` etc to listen to specific events. + +Televerse also let's you create your own filter to cherrypick the specific update and process it. For example, if you want to listen for messages that contain a photo with a size greater than 1MB, you can do it like this: @@ -109,60 +122,182 @@ bot.filter((ctx) { }); ``` -Please note that we are still working on improving the documentation. In the meantime, you can refer to the [example](./example/televerse_example.dart) file for more information. +More listener methods listed in the features section. + +## 🔑 Key Features -## 🦄 Even more +### 1. Strictly Typed +Televerse uses 0 dynamic types on the public interface, ensuring type safety and reliability throughout your bot development process. -Televerse provides a number of helper methods to make it easy to listen for specific types of updates. For example, you can use the `onURL`, `onHashtag`, `onMention`, `onEmail`, and `onPhoneNumber` methods to listen for messages that contain specific types of content: +### 2. 🏠 Local Bot API Server Support +Host your own Bot API server and listen to updates effortlessly with Televerse. Simply use the `Bot.local` constructor to configure your bot with a custom base URL. ```dart -bot.onURL((ctx) { - ctx.reply('I guess you want me to crawl this? 🕷️'); -}); +/// Creates the bot instance, optionally passing the base URL of the local Bot API Server. +final Bot bot = Bot.local( + "YOUR_BOT_TOKEN", + baseURL: "mybotapi.com", +); +``` -bot.onMention((ctx) { - ctx.reply("Someone mentioned someone! 🤭"); -}); +### 3. 🌐 Serverless Setup Compatibility +Whether you're using Cloud Functions or Lambda Functions, Televerse has you covered. Utilize `Bot.handleUpdate` to handle updates manually in your serverless environment. -// And so on... +```dart +// Create bot instance, and setup listeners +// ... + +final json = jsonDecode(event.body); +final update = Update.fromJson(json); +bot.handleUpdate(update); ``` -You can also use the `whenMentioned` method to listen for messages that mention your bot: + +### 4. 🔄 Network Interceptor Support +Say goodbye to uncertainty with Televerse's `LoggerOptions`, allowing you to customize network logging options to your heart's content. ```dart -bot.whenMentioned((ctx) { - ctx.reply('Oh hey, I was sleeping! What did I miss?'); -}); +final bot = Bot( + "YOUR_BOT_TOKEN", + loggerOptions: LoggerOptions( + requestBody: true, + requestHeader: true, + responseBody: true, + methods: [ + APIMethod.sendMessage, + APIMethod.sendPhoto, + ], + ), +); ``` -## 🎖️ Local Bot API Server Support -Televerse supports listening to a local Bot API Server. To use this feature, you can create a new bot instance using the `local` method: +### 5. ⌨️ `Keyboard` and `InlineKeyboard` Utility Classes +Easily create Keyboard Reply Markup and Inline Keyboard Markup with Televerse's intuitive utility classes. Easy as it sounds. ```dart -/// Creates the bot instance, optionally passing the base URL of the local Bot API Server. -final Bot bot = Bot.local( - "YOUR_BOT_TOKEN", - baseURL: "mybotapi.com", -); +bot.start((ctx) async { + final keyboard = Keyboard() + .text("Account") + .text("Referral") + .row() + .text("Settings") + .resized(); + + await ctx.reply( + "Choose an option:", + replyMarkup: keyboard, + ); +}); ``` -This will create a bot instance that listens to updates from your local Bot API Server. You can then use the same helper methods to listen for updates, messages, and events. +### 6. 🎧 Extensive Listener Methods +Televerse offers a wide array of listener methods to cater to your bot's needs, including: + +| Method | Description | +| --- | --- | +| `bot.command` | For listening commands | +| `bot.hears` | For listening to specified Regular Expression; | +| `bot.inlineQuery` | For listening to inline query with specified query text | +| `bot.text` | For listening to message with specified text | +| `bot.callbackQuery` | For listening to specified callback data | +| `bot.onDocument` | For listening to messages that contain a document | +| `bot.onPhoto` | For listening to photo messages | +| `bot.chatType` | This method can be used to listen to messages/updates coming from a specific type of chat such as Super Group or Channel or Private Chat | +| `bot.entity` | Sets up handler method for messages that contains specified entity type | +| `bot.myChatMember` | Listens to change in Bot's chat member status - such as bot is added to a channel or kicked from a group etc. } +- And much much more :) -## 🖥️ Serverless Support -You can even create bots that can run on your serverless platform such as AWS Lambda or Google Cloud Functions. On serverless platforms, you might not be able to listen for updates using a Fetcher. In this case, you can use the `bot.handleUpdate` method to handle updates manually. +### 7. 🛠️ Custom Listener Methods +Wait a second, did we miss your use case? Create your own listener methods with ease using `Bot.filter`. ```dart -// Create bot instance, and setup listeners +bot.filter((ctx) { + return (ctx.message.photo?.last.fileSize ?? 0) > 1000000; +}, (ctx) async { + ctx.reply('Wow, that\'s a big photo!'); +}); +``` + +### 8. 🚀 Latest Telegram Bot API Support +Stay up-to-date with the latest version of the Telegram Bot API, supported by Televerse. That's a promise. + +### 9. 🛡️ Error Handling +Handle errors gracefully with `Bot.onError`, catching almost all uncaught errors from any handlers. + +```dart +import 'dart:developer'; + // ... -final json = jsonDecode(event.body); -final update = Update.fromJson(json); -bot.handleUpdate(update); +bot.onError((err) { + log( + "Something went wrong: $err", + error: err.error, + stackTrace: err.stackTrace, + ); +}); +``` + +### 10. 💬 Conversation API +Engage users in one-on-one conversations with the Conversation API, waiting for their messages seamlessly. + +```dart +// Create your bot instance +final bot = Bot( + "YOUR_BOT_TOKEN", +); + +// Create the Conversation API instance +final conv = Conversation(bot); + +bot.start((ctx) async { + await ctx.reply("Hello, I am ${ctx.me.firstName}. What should I call you?"); + + // Now wait you can wait for the user's reply message. Easy, right? + final nameCtx = await conv.waitForTextMessage(chatId: ctx.id); + await nameCtx?.reply("Good to meet you, ${ctx.message?.text}"); +}); +``` + +### 11. 📲 `InlineMenu` and `KeyboardMenu` Utility Classes +Effortlessly build Inline Reply Markup and Keyboard Reply Markup with Televerse's utility classes, bound with handler methods for button taps. + +```dart +// Define handler methods +Future accountHandler(Context ctx) async { + await ctx.replyWithPhoto(InputFile.fromFile(File("hello.png"))); + await ctx.reply("Here's your account details..."); +} + +// Define menu options +final menu = KeyboardMenu() + .text("Account", accountHandler) + .text("Referral", referralHandler) + .text("Settings", settingsHandler) + .resized(); + +// Attach menu to bot +bot.attachMenu(menu); + +// Start bot +bot.start((ctx) async { + await ctx.reply( + "Hello, I am ${ctx.me.firstName}. Let's start.", + replyMarkup: menu, + ); +}); ``` +### 12. 🔍 Inline Query Result Builder +Efficiently build inline query results with the InlineQueryResultBuilder, simplifying the process of generating inline query results. + +--- + +Feel free to adjust the examples or add more details to them as needed. Let me know if there's anything else you'd like to add or modify! + ## 🌟 Shoot a Star If you find Televerse helpful, please consider shooting a star on our [Github repository](https://github.com/HeySreelal/televerse). This helps us to know that our work is appreciated and motivates us to continue improving Televerse. @@ -171,10 +306,9 @@ If you find Televerse helpful, please consider shooting a star on our [Github re We have an active Telegram group where you can discuss Televerse and get help from other users and developers. - - - - + + +
@@ -182,3 +316,5 @@ We have an active Telegram group where you can discuss Televerse and get help fr # Thank you ❤️ Televerse is a powerful and easy-to-use library for building Telegram bots in Dart. With its fully typed interface and helpful helper methods, you can write clean, maintainable code that responds to messages and updates on Telegram. So, what are you waiting for? Start building your Telegram bot with Televerse today! + +[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-Say%20Thanks-blue?style=flat-square&logo=buy-me-a-coffee)](https://www.buymeacoffee.com/heysreelal) \ No newline at end of file diff --git a/lib/src/televerse/bot.dart b/lib/src/televerse/bot.dart index f37da3ab..95a3230b 100644 --- a/lib/src/televerse/bot.dart +++ b/lib/src/televerse/bot.dart @@ -115,22 +115,26 @@ class Bot { this.fetcher.setApi(api); _instance = this; - api.getMe().then((value) { - _me = value; - }).catchError((Object err, StackTrace st) async { - if (_onError != null) { - final botErr = BotError(err, st); - await _onError!(botErr); - } else if (err is DioException) { - if (err.type == DioExceptionType.connectionTimeout || - err.type == DioExceptionType.receiveTimeout || - err.type == DioExceptionType.sendTimeout) { - throw TeleverseException.timeoutException(st, timeout!); - } - } else { - throw err; + getMe().then(_ignore).catchError(_thenHandleGetMeError); + } + + /// Function to ignore things. + void _ignore(_) {} + + /// Handles the error in initial `getMe` call + FutureOr _thenHandleGetMeError(Object err, StackTrace st) async { + if (_onError != null) { + final botErr = BotError(err, st); + await _onError!(botErr); + } else if (err is DioException) { + if (err.type == DioExceptionType.connectionTimeout || + err.type == DioExceptionType.receiveTimeout || + err.type == DioExceptionType.sendTimeout) { + throw TeleverseException.timeoutException(st, timeout!); } - }); + } else { + throw err; + } } /// Televerse local constructor. This constructor is used to create a bot instance that listens to a local Bot API server. @@ -177,6 +181,15 @@ class Bot { ); } + /// List of pending calls + final List<_PendingCall> _pendingCalls = []; + + /// Whether _me is filled or not + _GetMeStatus _getMeStatus = _GetMeStatus.notInitiated; + + /// Whether the `Bot.me` is filled + bool get initialized => _getMeStatus == _GetMeStatus.completed; + /// Information about the bot. late User _me; @@ -206,7 +219,19 @@ class Bot { /// Note: As of now this won't be handled in `onError` handler. Future getMe() async { try { + if (initialized) { + return _me; + } + _getMeStatus = _GetMeStatus.pending; _me = await api.getMe(); + _getMeStatus = _GetMeStatus.completed; + + if (_pendingCalls.isNotEmpty) { + for (final fn in _pendingCalls) { + fn.call(); + } + } + return _me; } catch (err, stack) { final exception = TeleverseException.getMeRequestFailed(err, stack); @@ -358,40 +383,35 @@ class Bot { Pattern command, Handler callback, ) async { - User? bot; - try { - bot = me; - } catch (err) { - try { - bot = await getMe(); - _me = bot; - } catch (err, st) { - if (_onError != null) { - final ex = TeleverseException.getMeRequestFailed(err, st); - final botErr = BotError(ex, st); - await _onError!(botErr); - } else { - rethrow; - } - } + if (initialized) { + final scope = HandlerScope( + isCommand: true, + handler: callback, + types: [UpdateType.message], + predicate: (ctx) { + if (ctx.msg?.text == null) return false; + if (command is RegExp) { + return command.hasMatch(ctx.msg?.text ?? ""); + } else if (command is String) { + final firstTerm = ctx.msg?.text!.split(' ').first; + final suffix = me.username != null ? '@${me.username}' : ''; + return firstTerm == '/$command' || firstTerm == '/$command$suffix'; + } + return false; + }, + ); + _handlerScopes.add(scope); + return; + } + + if (_getMeStatus == _GetMeStatus.pending) { + _pendingCalls.add( + _PendingCall( + fn: this.command, + params: [command, callback], + ), + ); } - final scope = HandlerScope( - isCommand: true, - handler: callback, - types: [UpdateType.message], - predicate: (ctx) { - if (ctx.msg?.text == null) return false; - if (command is RegExp) { - return command.hasMatch(ctx.msg?.text ?? ""); - } else if (command is String) { - final firstTerm = ctx.msg?.text!.split(' ').first; - final suffix = bot?.username != null ? '@${bot?.username}' : ''; - return firstTerm == '/$command' || firstTerm == '/$command$suffix'; - } - return false; - }, - ); - _handlerScopes.add(scope); } /// Registers a Handler Scope to listen to matching callback query. @@ -509,7 +529,6 @@ class Bot { _handlerScopes.add(scope); } - /// Filter /// Registers a callback for a message that matches the specified filter. /// /// The callback will be called when a message is received that matches the @@ -521,6 +540,11 @@ class Bot { /// /// Example: /// ```dart + /// bot.filter((ctx) { + /// return (ctx.message.photo?.last.fileSize ?? 0) > 1000000; + /// }, (ctx) async { + /// await ctx.reply('Wow, that\'s a big photo!'); + /// }); /// ``` void filter( bool Function(Context ctx) predicate, @@ -530,12 +554,8 @@ class Bot { final scope = HandlerScope( name: name, handler: callback, - types: [ - UpdateType.message, - ], - predicate: (ctx) { - return predicate(ctx); - }, + types: UpdateType.values, + predicate: predicate, ); _handlerScopes.add(scope); @@ -1090,16 +1110,18 @@ class Bot { Future whenMentioned( Handler callback, ) async { - try { + if (initialized) { return onMention( callback, username: me.username, ); - } on TeleverseException catch (_) { - _me = await getMe(); - return onMention( - callback, - username: me.username, + } + if (_getMeStatus == _GetMeStatus.pending) { + _pendingCalls.add( + _PendingCall( + fn: whenMentioned, + params: [callback], + ), ); } } diff --git a/lib/src/televerse/fetch/long_polling.dart b/lib/src/televerse/fetch/long_polling.dart index 9486ca0f..6b90c5ea 100644 --- a/lib/src/televerse/fetch/long_polling.dart +++ b/lib/src/televerse/fetch/long_polling.dart @@ -16,7 +16,7 @@ class LongPolling extends Fetcher { final int limit; /// A list of update types to fetch. - final List allowedUpdates; + final List? allowedUpdates; /// The maximum timeout allowed by the Telegram API. static const int maxTimeout = 50; @@ -34,7 +34,7 @@ class LongPolling extends Fetcher { this.offset = 0, this.timeout = 30, this.limit = 100, - this.allowedUpdates = const [], + this.allowedUpdates, this.delayDuration = const Duration(milliseconds: 200), }) { if (timeout > maxTimeout) throw LongPollingException.invalidTimeout; @@ -82,7 +82,7 @@ class LongPolling extends Fetcher { offset: offset, limit: limit, timeout: timeout, - allowedUpdates: allowedUpdates.map((e) => e.type).toList(), + allowedUpdates: allowedUpdates?.map((e) => e.type).toList(), ); int len = updates.length; for (int i = 0; i < len; i++) { diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index e06fe4f4..deba81df 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -92,3 +92,44 @@ extension FromAndChatExt on Update { return m; } } + +/// (Internal) Current status of the initial `Bot.getMe` call +enum _GetMeStatus { + /// Completed + completed, + + /// Pending + pending, + + /// Not Initiated + notInitiated, + ; +} + +/// Represents a pending method call +class _PendingCall { + final Function fn; + final List params; + + const _PendingCall({ + required this.fn, + required this.params, + }); + + 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; + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3f940d40..fa3c669a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: televerse description: Televerse lets you create your own efficient Telegram bots with ease in Dart. Supports latest Telegram Bot API - 7.2! -version: 1.15.8 +version: 1.15.9 homepage: https://github.com/HeySreelal/televerse topics: - telegram