diff --git a/CHANGELOG.md b/CHANGELOG.md index d53e08ee..1fc2a0fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 1.19.0 +# 1.19.1 - 🆕 Support for Middlewares & Transformers - Middlewares lets you attach handlers that are run before your main handler is @@ -15,6 +15,15 @@ [#257](https://github.com/HeySreelal/televerse/pull/257) (Thanks to @devsdocs) - ⚠️ Type of `Webhook.certificate` is changed to InputFile as described by the official documentation. +- Added examples for + [Middleware](https://github.com/xooniverse/TeleverseExamples/blob/1c30d889d3a0b1d7bdceaad48d6cfd88208e87be/lib/middleware_example.dart) + and + [Transformer](https://github.com/xooniverse/TeleverseExamples/blob/1c30d889d3a0b1d7bdceaad48d6cfd88208e87be/lib/transformer_example.dart) + usage in Examples repo. + +# 1.19.0 + +- [Retracted] # 1.18.0 diff --git a/README.md b/README.md index ce8be1b1..56e5a490 100644 --- a/README.md +++ b/README.md @@ -351,6 +351,63 @@ bot.start((ctx) async { Efficiently build inline query results with the InlineQueryResultBuilder, simplifying the process of generating inline query results. +### 13. 🔌 Plugin Support + +Televerse support Middlewares and Transformers in the library. These features +allow you to preprocess and manipulate API requests seamlessly. + +#### Middlewares + +Middlewares let you execute code before your main handler is run. This is useful +for tasks like logging, authentication, and more. + +#### Example: Logging Middleware + +```dart +class LoggingMiddleware implements Middleware { + @override + Future handle( + Context ctx, + NextFunction next, + ) async { + print('Received update: ${ctx.update}'); + await next(); + } +} + +// Usage +bot.use(LoggingMiddleware()); +``` + +#### Transformers + +Transformers allow you to alter the request payloads directly, providing a more +flexible way to modify requests before they are sent to the API. + +#### Example: Auto Replier Transformer + +```dart +class AutoReplier implements Transformer { + @override + FutureOr> transform( + APIMethod method, + Map payload, + Context? ctx, + ) { + final isSendMethod = APIMethod.sendMethods.contains(method); + final isNotChatAction = method != APIMethod.sendChatAction; + + if (isSendMethod && isNotChatAction) { + payload["reply_markup"] = ForceReply().toJson(); + } + return payload; + } +} + +// Usage +bot.use(AutoReplier()); +``` + --- ## 🌟 Shoot a Star @@ -380,3 +437,6 @@ 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) + +``` +``` diff --git a/lib/src/televerse/bot.dart b/lib/src/televerse/bot.dart index b2f0fde5..c8cb1b17 100644 --- a/lib/src/televerse/bot.dart +++ b/lib/src/televerse/bot.dart @@ -437,7 +437,7 @@ class Bot { if (index < _middlewares.length) { try { - await _middlewares[index].fn(ctx, next); + await _middlewares[index].handle(ctx, next); } catch (err, stack) { final botErr = BotError( err, diff --git a/lib/src/televerse/context/context.dart b/lib/src/televerse/context/context.dart index a5759738..28921ef1 100644 --- a/lib/src/televerse/context/context.dart +++ b/lib/src/televerse/context/context.dart @@ -109,7 +109,7 @@ class Context { /// Executes the middleware on the current context Future use(MiddlewareBase middleware) async { if (middleware is Middleware) { - await middleware.fn(this, () async {}); + await middleware.handle(this, () async {}); } if (middleware is Transformer) { diff --git a/lib/src/televerse/middlewares/middleware.dart b/lib/src/televerse/middlewares/middleware.dart index 7e3efa11..158e64ab 100644 --- a/lib/src/televerse/middlewares/middleware.dart +++ b/lib/src/televerse/middlewares/middleware.dart @@ -3,69 +3,119 @@ part of '../../../televerse.dart'; /// Base class for the middlewares sealed class MiddlewareBase {} -/// The next handler to be executed. +/// A typedef for the next function used in middleware. /// -/// Invoking this function basically means, you want the next handler to also run. +/// This function should be called within a middleware to pass control to the +/// next middleware in the stack. If it's not called, the subsequent middlewares +/// and the main handler won't be executed. typedef NextFunction = FutureOr Function(); -/// Represents a Middleware. +/// Represents a Middlware that can be used to process requests +/// before they reach the main handler. /// -/// Middlewares are attached to the bot with the `bot.use` method. Middlewares act as an intermediate -/// handler before the actual handler. +/// This class should be implemented by any middleware that needs to perform +/// operations on the [Context] object or decide whether to pass control to +/// the next middleware or the main handler. /// -/// These intermediate functions can be used to process or modify the Context before it reaches the main handler. +/// Example usage: +/// ```dart +/// class Logger implements Middleware { +/// @override +/// FutureOr handle(Context ctx, NextFunction next) async { +/// print('Request received: ${ctx.update}'); +/// await next(); // Pass control to the next middleware or handler +/// print('Response sent'); +/// } +/// } +/// ``` +/// +/// By using the `Middleware` class, you can create reusable components that +/// can be attached to the bot and executed in sequence. abstract interface class Middleware implements MiddlewareBase { - /// The middleware function + /// The middleware function that processes the [Context] and optionally + /// passes control to the next middleware or main handler. + /// + /// The [handle] function should call the [next] function to pass control + /// to the next middleware in the stack or to the main handler if there are + /// no more middlewares. If [next] is not called, the subsequent middlewares + /// and the main handler will not be executed. /// - /// - [ctx] - The current context where the middleware is operating on + /// Example usage: + /// ```dart + /// @override + /// FutureOr handle(Context ctx, NextFunction next) async { + /// // Perform some pre-processing + /// print('Before main handler'); /// - /// - [next] - The next handler method. Gives you ability to decide whether to run or skip the next handler. - FutureOr fn( + /// // Pass control to the next middleware or the main handler + /// await next(); + /// + /// // Perform some post-processing + /// print('After main handler'); + /// } + /// ``` + FutureOr handle( Context ctx, NextFunction next, ); - /// Constructs a Middleware + /// Constructs a [Middleware] instance. const Middleware(); } -/// Represents a interceptor which can alter the API requests. +/// Represents a transformer that can alter API requests before they are sent. +/// +/// This class should be implemented by any transformer that needs to modify +/// the request payload based on the API method being called, the provided +/// payload, or the context. +/// +/// Example usage: +/// ```dart +/// class AutoReplier implements Transformer { +/// @override +/// FutureOr> transform( +/// APIMethod method, +/// Map payload, +/// Context? ctx, +/// ) { +/// final isSendMethod = APIMethod.sendMethods.contains(method); +/// final isNotChatAction = method != APIMethod.sendChatAction; +/// +/// if (isSendMethod && isNotChatAction) { +/// payload["reply_markup"] = ForceReply().toJson(); +/// } +/// return payload; +/// } +/// } +/// ``` +/// +/// By using the `Transformer` class, you can create reusable components that +/// can be attached to the bot to modify the request payload dynamically. abstract interface class Transformer implements MiddlewareBase { - /// The API interceptor function. - /// - /// ## Available Arguments - /// Here are the different arugments available to the transfomer function. - /// - /// ### 1. `APIMethod method` - /// - /// This parameter tells which API Method is now being called. Let's you decide whether to - /// perform or not perform the transfomer on the payload. + /// The function that processes and alters the API request payload. /// - /// ### 2. `Map payload` + /// ## Parameters + /// - `APIMethod method`: Indicates which API method is being called. This allows + /// you to decide whether or not to transform the payload based on the method. /// - /// The payload is the actual JSON body object to be posted to the current method. - /// Check the [Telegram Bot API documentation](https://core.telegram.org/bots/api) to learn about all the possible - /// parameters that can be passed. In most fo the cases, you will be altering the payload with a transformer. So, - /// it is important to alter and return the valid JSON after transformation. + /// - `Map payload`: The actual JSON body object to be posted + /// to the current method. This payload can be altered and should be returned + /// as a valid JSON after transformation. Refer to the [Telegram Bot API documentation](https://core.telegram.org/bots/api) + /// to understand the possible parameters that can be passed. /// - /// ### 3. `Context? ctx` - /// The available context object. If the user is invoking RawAPI methods through the context object, such as - /// `ctx.reply` or `ctx.answerCallbackQuery` the particular context object can be accessed with this argument. - /// Having access to the Context object primarily lets you take control of the whole incoming update. This lets you - /// further enhance your transfomrmer. - /// - /// Be aware that `ctx` can be `null`, this occurs when user is invoking the method directly through the `RawAPI` instance. + /// - `Context? ctx`: The context object, if available. This is accessible when + /// the user invokes `RawAPI` methods through the context object, such as `ctx.reply` + /// or `ctx.answerCallbackQuery`. Access to the context object allows you to further + /// enhance your transformer by leveraging the incoming update. /// + /// **Note**: `ctx` can be `null` if the method is invoked directly through the `RawAPI` instance. /// /// ## Example - /// Here's a simple implementation of the `AutoReplier` transformer. The auto-replier transformer can be used to - /// automatically require users to reply to the current message the bot sent by passing the `ForceReply` reply markup with all the possible - /// send methods. - /// + /// Here's a simple implementation of the `AutoReplier` transformer: /// ```dart /// class AutoReplier implements Transformer { /// @override - /// FutureOr> fn( + /// FutureOr> transform( /// APIMethod method, /// Map payload, /// Context? ctx, @@ -81,19 +131,17 @@ abstract interface class Transformer implements MiddlewareBase { /// } /// ``` /// - /// The above code, simply creates a auto replier transformer. Now users can use it as: - /// + /// The above code creates an `AutoReplier` transformer. Users can attach it to the bot: /// ```dart /// bot.use(AutoReplier()); /// ``` - /// - /// That's it. Now whenever user makes a send method request, the force reply markup will be added to it. - FutureOr> fn( + /// Now, whenever a send method request is made, the force reply markup will be added. + FutureOr> transform( APIMethod method, Map payload, Context? ctx, ); - /// Constructs a API Interceptor middleware + /// Constructs a `Transformer` instance. const Transformer(); } diff --git a/lib/src/televerse/raw_api.dart b/lib/src/televerse/raw_api.dart index 5da409ed..5196ef53 100644 --- a/lib/src/televerse/raw_api.dart +++ b/lib/src/televerse/raw_api.dart @@ -162,7 +162,7 @@ class RawAPI { final ts = [..._transformers, ...(_context?._transfomers ?? [])]; while (i < ts.length) { - params = await ts[i].fn(method, params!, _context); + params = await ts[i].transform(method, params!, _context); i++; } diff --git a/pubspec.yaml b/pubspec.yaml index 0c7b9fa6..6515543a 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.5! -version: 1.19.0 +version: 1.19.1 homepage: https://github.com/HeySreelal/televerse topics: - telegram