diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e643f8..734c1cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 1.19.5 + +- 🧑🏻‍🔧 Fix in `InlineQueryResultBuilder` methods `voice` and `video`. +- Improved `Webhook` implementation. Works perfect now! +- Added example for building bot with `Webhook` fetcher in + [Examples Repo](https://github.com/xooniverse/TeleverseExamples/blob/main/lib/webhook_example.dart) +- Added detailed documentation for `InlineQueryResultBuilder` and `Webhook` + classes. + # 1.19.4 - Minor README fix @@ -39,9 +48,9 @@ - ⚠️ 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) + [Middleware](https://github.com/xooniverse/TeleverseExamples/blob/main/lib/middleware_example.dart) and - [Transformer](https://github.com/xooniverse/TeleverseExamples/blob/1c30d889d3a0b1d7bdceaad48d6cfd88208e87be/lib/transformer_example.dart) + [Transformer](https://github.com/xooniverse/TeleverseExamples/blob/main/lib/transformer_example.dart) usage in Examples repo. # 1.19.0 diff --git a/lib/src/televerse/builders/inline_query_result_builder.dart b/lib/src/televerse/builders/inline_query_result_builder.dart index b8afc1f5..38686e2d 100644 --- a/lib/src/televerse/builders/inline_query_result_builder.dart +++ b/lib/src/televerse/builders/inline_query_result_builder.dart @@ -2,16 +2,38 @@ part of '../../../televerse.dart'; /// A utility class to build Inline Query Results quickly and easily. /// -/// # Inline Query Result Builder +/// This class provides methods to construct various types of Inline Query Results, +/// such as articles, audio files, documents, and more, which can be used in Telegram bots. /// -/// Use the methods provided by this class to construct various types of Inline Query Results, -/// such as articles, audio files, documents, etc. +/// ## Inline Query Result Builder /// +/// Example usage: +/// ```dart +/// final builder = InlineQueryResultBuilder(); +/// builder.article( +/// '1', +/// 'Example Article', +/// (content) => content.text('This is an example article.'), +/// description: 'An example article for demonstration.', +/// ); +/// final results = builder.build(); +/// ``` class InlineQueryResultBuilder { /// Results final List _results = []; - /// Adds an Article result to the final results + /// Adds an Article result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [title] is the title of the result. + /// - [contentGenerator] is a function that generates the content of the article. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [url] is an optional URL of the result. + /// - [hideUrl] hides the URL in the message when set to true. + /// - [description] is an optional short description of the result. + /// - [thumbnailUrl] is an optional URL of the thumbnail for the result. + /// - [thumbnailWidth] is the optional width of the thumbnail. + /// - [thumbnailHeight] is the optional height of the thumbnail. InlineQueryResultBuilder article( String id, String title, @@ -45,7 +67,18 @@ class InlineQueryResultBuilder { return this; } - /// Adds an Audio result to the final results + /// Adds an Audio result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [audioUrl] is the URL of the audio file. + /// - [title] is the title of the result. + /// - [caption] is an optional caption for the audio file. + /// - [parseMode] specifies the parse mode of the caption. + /// - [captionEntities] specifies special entities in the caption. + /// - [performer] is the performer of the audio. + /// - [audioDuration] is the duration of the audio in seconds. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. InlineQueryResultBuilder audio( String id, String audioUrl, @@ -77,7 +110,18 @@ class InlineQueryResultBuilder { return this; } - /// Adds a contact result to the final results + /// Adds a Contact result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [phoneNumber] is the phone number of the contact. + /// - [firstName] is the first name of the contact. + /// - [lastName] is the optional last name of the contact. + /// - [vcard] is the optional vCard of the contact. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. + /// - [thumbnailUrl] is an optional URL of the thumbnail for the result. + /// - [thumbnailWidth] is the optional width of the thumbnail. + /// - [thumbnailHeight] is the optional height of the thumbnail. InlineQueryResultBuilder contact( String id, String phoneNumber, @@ -109,7 +153,11 @@ class InlineQueryResultBuilder { return this; } - /// Adds an game result to the final results + /// Adds a Game result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [gameShortName] is the short name of the game. + /// - [replyMarkup] is an optional inline keyboard attached to the message. InlineQueryResultBuilder game( String id, String gameShortName, { @@ -163,7 +211,21 @@ class InlineQueryResultBuilder { return this; } - /// Adds an GIF result to the final results + /// Adds a GIF result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [gifUrl] is the URL of the GIF file. + /// - [thumbnailUrl] is the URL of the thumbnail for the GIF. + /// - [gifWidth] is the optional width of the GIF. + /// - [gifHeight] is the optional height of the GIF. + /// - [gifDuration] is the optional duration of the GIF in seconds. + /// - [thumbnailMimeType] is the optional MIME type of the thumbnail. + /// - [title] is an optional title for the GIF. + /// - [caption] is an optional caption for the GIF. + /// - [parseMode] specifies the parse mode of the caption. + /// - [captionEntities] specifies special entities in the caption. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. InlineQueryResultBuilder gif( String id, String gifUrl, @@ -201,7 +263,21 @@ class InlineQueryResultBuilder { return this; } - /// Adds an location result to the final results + /// Adds a Location result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [latitude] is the latitude of the location. + /// - [longitude] is the longitude of the location. + /// - [title] is the title of the location. + /// - [horizontalAccuracy] is the optional radius of uncertainty for the location. + /// - [livePeriod] is the optional period in seconds for which the location can be updated. + /// - [heading] is the optional direction in which the user is moving. + /// - [proximityAlertRadius] is the optional radius in meters for proximity alerts. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. + /// - [thumbnailUrl] is an optional URL of the thumbnail for the location. + /// - [thumbnailWidth] is the optional width of the thumbnail. + /// - [thumbnailHeight] is the optional height of the thumbnail. InlineQueryResultBuilder location( String id, double latitude, @@ -239,7 +315,21 @@ class InlineQueryResultBuilder { return this; } - /// Adds an Mpeg4GIF result to the final results + /// Adds an Mpeg4 GIF result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [mpeg4Url] is the URL of the Mpeg4 GIF file. + /// - [thumbnailUrl] is the URL of the thumbnail for the Mpeg4 GIF. + /// - [mpeg4Width] is the optional width of the Mpeg4 GIF. + /// - [mpeg4Height] is the optional height of the Mpeg4 GIF. + /// - [mpeg4Duration] is the optional duration of the Mpeg4 GIF in seconds. + /// - [thumbnailMimeType] is the optional MIME type of the thumbnail. + /// - [title] is an optional title for the Mpeg4 GIF. + /// - [caption] is an optional caption for the Mpeg4 GIF. + /// - [parseMode] specifies the parse mode of the caption. + /// - [captionEntities] specifies special entities in the caption. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. InlineQueryResultBuilder mpeg4gif( String id, String mpeg4Url, @@ -277,7 +367,20 @@ class InlineQueryResultBuilder { return this; } - /// Adds an photo result to the final results + /// Adds a Photo result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [photoUrl] is the URL of the photo file. + /// - [thumbnailUrl] is the URL of the thumbnail for the photo. + /// - [photoWidth] is the optional width of the photo. + /// - [photoHeight] is the optional height of the photo. + /// - [title] is an optional title for the photo. + /// - [description] is an optional description for the photo. + /// - [caption] is an optional caption for the photo. + /// - [parseMode] specifies the parse mode of the caption. + /// - [captionEntities] specifies special entities in the caption. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. InlineQueryResultBuilder photo( String id, String photoUrl, @@ -313,7 +416,22 @@ class InlineQueryResultBuilder { return this; } - /// Adds an venue result to the final results + /// Adds a Venue result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [latitude] is the latitude of the venue. + /// - [longitude] is the longitude of the venue. + /// - [title] is the title of the venue. + /// - [address] is the address of the venue. + /// - [foursquareId] is the optional Foursquare identifier of the venue. + /// - [foursquareType] is the optional Foursquare type of the venue. + /// - [googlePlaceId] is the optional Google Places identifier of the venue. + /// - [googlePlaceType] is the optional Google Places type of the venue. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. + /// - [thumbnailUrl] is an optional URL of the thumbnail for the venue. + /// - [thumbnailWidth] is the optional width of the thumbnail. + /// - [thumbnailHeight] is the optional height of the thumbnail. InlineQueryResultBuilder venue( String id, double latitude, @@ -353,81 +471,99 @@ class InlineQueryResultBuilder { return this; } - /// Adds an video result to the final results + /// Adds a Video result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [title] is the title of the video. + /// - [videoUrl] is the URL of the video file. + /// - [mimeType] is the MIME type of the video. + /// - [thumbnailUrl] is the URL of the thumbnail for the video. + /// - [caption] is an optional caption for the video. + /// - [parseMode] specifies the parse mode of the caption. + /// - [captionEntities] specifies special entities in the caption. + /// - [videoWidth] is the optional width of the video. + /// - [videoHeight] is the optional height of the video. + /// - [videoDuration] is the optional duration of the video in seconds. + /// - [description] is an optional description for the video. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [showCaptionAboveMedia] specifies whether to show the caption above the media. + /// - [contentGenerator] is an optional function to generate additional content. InlineQueryResultBuilder video( String id, - double latitude, - double longitude, - String title, - String address, { - String? foursquareId, - String? foursquareType, - String? googlePlaceId, - String? googlePlaceType, + String title, { + required String videoUrl, + required String mimeType, + required String thumbnailUrl, + String? caption, + ParseMode? parseMode, + List? captionEntities, + int? videoWidth, + int? videoHeight, + int? videoDuration, + String? description, InlineKeyboardMarkup? replyMarkup, + bool? showCaptionAboveMedia, InputMessageContent? Function( MessageContentGenerator content, )? contentGenerator, - String? thumbnailUrl, - int? thumbnailWidth, - int? thumbnailHeight, }) { _results.add( - InlineQueryResultVenue( - latitude: latitude, - longitude: longitude, + InlineQueryResultVideo( + videoUrl: videoUrl, + mimeType: mimeType, + thumbnailUrl: thumbnailUrl, title: title, - address: address, id: id, - foursquareId: foursquareId, - foursquareType: foursquareType, - googlePlaceId: googlePlaceId, - googlePlaceType: googlePlaceType, + caption: caption, + parseMode: parseMode, + captionEntities: captionEntities, + videoWidth: videoWidth, + videoHeight: videoHeight, + videoDuration: videoDuration, + description: description, replyMarkup: replyMarkup, inputMessageContent: contentGenerator?.call(MessageContentGenerator.i), - thumbnailUrl: thumbnailUrl, - thumbnailWidth: thumbnailWidth, - thumbnailHeight: thumbnailHeight, + showCaptionAboveMedia: showCaptionAboveMedia, ), ); return this; } - /// Adds an voice result to the final results + /// Adds a Voice result to the final results. + /// + /// - [id] is the unique identifier for this result. + /// - [title] is the title of the voice message. + /// - [voiceUrl] is the URL of the voice message file. + /// - [caption] is an optional caption for the voice message. + /// - [parseMode] specifies the parse mode of the caption. + /// - [captionEntities] specifies special entities in the caption. + /// - [voiceDuration] is the optional duration of the voice message in seconds. + /// - [replyMarkup] is an optional inline keyboard attached to the message. + /// - [contentGenerator] is an optional function to generate additional content. InlineQueryResultBuilder voice( String id, - double latitude, - double longitude, - String title, - String address, { - String? foursquareId, - String? foursquareType, - String? googlePlaceId, - String? googlePlaceType, + String title, { + required String voiceUrl, + String? caption, + ParseMode? parseMode, + List? captionEntities, + int? voiceDuration, InlineKeyboardMarkup? replyMarkup, InputMessageContent? Function( MessageContentGenerator content, )? contentGenerator, - String? thumbnailUrl, - int? thumbnailWidth, - int? thumbnailHeight, }) { _results.add( - InlineQueryResultVenue( - latitude: latitude, - longitude: longitude, + InlineQueryResultVoice( + voiceUrl: voiceUrl, title: title, - address: address, id: id, - foursquareId: foursquareId, - foursquareType: foursquareType, - googlePlaceId: googlePlaceId, - googlePlaceType: googlePlaceType, + caption: caption, + parseMode: parseMode, + captionEntities: captionEntities, + voiceDuration: voiceDuration, replyMarkup: replyMarkup, inputMessageContent: contentGenerator?.call(MessageContentGenerator.i), - thumbnailUrl: thumbnailUrl, - thumbnailWidth: thumbnailWidth, - thumbnailHeight: thumbnailHeight, ), ); return this; diff --git a/lib/src/televerse/fetch/webhook.dart b/lib/src/televerse/fetch/webhook.dart index d1ec3b8d..6f736a06 100644 --- a/lib/src/televerse/fetch/webhook.dart +++ b/lib/src/televerse/fetch/webhook.dart @@ -1,82 +1,220 @@ part of 'fetch.dart'; -/// This class is used to create a webhook fetcher. It is a subclass of [Fetcher]. +/// A fetcher class that handles incoming updates from the Telegram Bot API via a webhook. +/// +/// The `Webhook` class is designed to handle incoming updates from the Telegram Bot API via a webhook. +/// When an update is available for the bot, Telegram sends an HTTPS POST request to the specified URL, +/// containing a JSON-serialized `Update`. The class is responsible for processing these requests and +/// managing the webhook lifecycle. +/// +/// ## Features +/// +/// - Listens for incoming updates via an HTTP server. +/// - Configures and sets the webhook URL for the bot. +/// - Handles incoming requests and parses updates. +/// - Supports secure communication with an optional certificate and secret token. +/// +/// ## Constructor +/// +/// ```dart +/// // Create a webhook fetcher instance +/// final webhook = Webhook( +/// server, +/// url: "https://mydomain.com", +/// ); +/// ``` +/// +/// - [server] is the HTTP server instance. (required) +/// - [url] is the webhook URL. (required) +/// - [ipAddress] is the webhook IP address. +/// - [path] is the webhook secret path. +/// - [port] is the webhook port. +/// - [maxConnections] is the maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery. +/// - [allowedUpdates] is the list of the types of updates you want your bot to receive. +/// - [dropPendingUpdates] is the flag to drop all pending updates. +/// - [certificate] is the public key certificate. +/// - [secretToken] is the secret token for additional security. +/// +/// Throws `WebhookException.invalidPort` if the port is not in the allowed ports. +/// Throws `WebhookException.invalidMaxConnections` if the max connections are less than 1 or greater than 100. +/// Throws `WebhookException.failedToSetWebhook` if the webhook failed to set. +/// +/// ## Usage Example +/// +/// ```dart +/// // Bind the server to an available port +/// final server = await HttpServer.bind(InternetAddress.anyIPv6, 8080); +/// +/// // Create a webhook fetcher instance +/// final webhook = Webhook( +/// server, +/// url: "https://mydomain.com", +/// ); +/// +/// // Now pass the webhook instance to the `fetcher` parameter. +/// final bot = Bot( +/// "", +/// fetcher: webhook, +/// ); +/// +/// // Start the bot :) +/// bot.start(); +/// ``` +/// +/// ## Notes +/// +/// According to the Telegram Bot API documentation, whenever there is an update for the bot, +/// an HTTPS POST request is sent to the specified URL containing a JSON-serialized `Update`. +/// In case of an unsuccessful request, Telegram will give up after a reasonable number of attempts. +/// The method returns `True` on success. +/// +/// If you'd like to ensure that the webhook was set by you, you can specify secret data in the parameter +/// `secret_token`. If specified, the request will contain a header `“X-Telegram-Bot-Api-Secret-Token”` +/// with the secret token as content. class Webhook extends Fetcher { /// Http server instance. - final io.HttpServer _server; + final io.HttpServer server; - /// Webhook url. + /// Webhook URL. This is the URL where your bot will listen for incoming updates. + /// + /// Example: `https://yourdomain.com` String url; - /// Webhook ip address. - String? ipAddress; - - /// Webhook secret path. - String secretPath; + /// The fixed IP address which will be used to send webhook requests + /// instead of the IP address resolved through DNS + final String? ipAddress; - /// Webhook port. - int port; + /// Webhook path. Basically, a custom path for the webhook endpoint. By default points to `/`. + /// + /// When the `setWebhook` method is called, the webhook URL will be composed as + /// `$url:$port$path`, and this path will be matched when processing incoming requests. + /// + /// Example: `/mybotwebhook` + String path; - /// Server port. - int? serverPort; + /// Webhook port. This is the port on which the webhook will listen for incoming requests. + /// + /// Must be one of the following: 443, 80, 88, or 8443. + /// + /// Example: `443` + final int port; /// Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery. - int maxConnections; + /// + /// The value must be between 1 and 100 inclusive. + /// + /// Example: `40` + final int maxConnections; - /// List the types of updates you want your bot to receive. - List? allowedUpdates; + /// List of the types of updates you want your bot to receive. This specifies which types of updates your bot should listen for. + /// + /// Example: `[UpdateType.message, UpdateType.callbackQuery]` + final List? allowedUpdates; - /// Pass True to drop all pending updates. - bool? dropPendingUpdates; + /// Pass True to drop all pending updates. This will clear any pending updates that the bot may have. + /// + /// Example: `true` + final bool? dropPendingUpdates; - /// Public key certificate. - InputFile? certificate; + /// Public key certificate. This is the public key certificate required to set up webhook with a self-signed certificate. + /// + /// Example: `InputFile.fromFile(File('public.pem'))` + final InputFile? certificate; - /// Whether to upload the certificate. - bool uploadCertificate; + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” + /// in every webhook request, 1-256 characters. Only characters + /// `A-Z`, `a-z`, `0-9`, `_` and `-` are allowed. The header is useful to + /// ensure that the request comes from a webhook set by you. + final String? secretToken; /// Allowed ports. final List _allowedPorts = [443, 80, 88, 8443]; + /// Internal flag to check if the webhook is running. + bool _isActive = false; + /// Creates a Webhook fetcher. /// - /// - [_server] is the http server instance. (required) - /// - [api] is the raw api instance. (required) - /// - [url] is the webhook url. (required) - /// - [ipAddress] is the webhook ip address. - /// - [secretPath] is the webhook secret path. + /// - [server] is the HTTP server instance. (required) + /// - [url] is the webhook URL. (required) + /// - [ipAddress] is the webhook IP address. + /// - [path] is the webhook secret path. /// - [port] is the webhook port. - /// - [serverPort] is the server port. /// - [maxConnections] is the maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery. - /// - [allowedUpdates] is the list the types of updates you want your bot to receive. - /// - [dropPendingUpdates] is the pass True to drop all pending updates. + /// - [allowedUpdates] is the list of the types of updates you want your bot to receive. + /// - [dropPendingUpdates] is the flag to drop all pending updates. /// - [certificate] is the public key certificate. - /// - [uploadCertificate] is the whether to upload the certificate. + /// - [secretToken] is the secret token for additional security. + /// + /// Example usage: + /// + /// ```dart + /// // Bind the server to an available port + /// final server = await HttpServer.bind(InternetAddress.anyIPv6, 8080); + /// + /// // Create a webhook fetcher instance + /// final webhook = Webhook( + /// server, + /// url: "https://mydomain.com", + /// ); + /// + /// // Now pass the webhook instance to the `fetcher` parameter. + /// final bot = Bot( + /// "", + /// fetcher: webhook, + /// ); + /// + /// + /// // Start the bot :) + /// bot.start(); + /// ``` + /// + /// Note that you don't have to manually invoke the `setWebhook` method to register the webhook. It'll be done implicitly. /// + /// ### Possible Exceptions /// - /// Throws `WebhookException.invalidPort` if the port is not in the allowed ports. - /// Throws `WebhookException.invalidMaxConnections` if the max connections is less than 1 or greater than 100. - /// Throws `WebhookException.failedToSetWebhook` if the webhook failed to set. + /// - Throws `WebhookException.invalidPort` if the port is not in the allowed ports. + /// - Throws `WebhookException.invalidMaxConnections` if the max connections are less than 1 or greater than 100. + /// - Throws `WebhookException.failedToSetWebhook` if the webhook failed to set. Webhook( - this._server, { + this.server, { required this.url, this.ipAddress, - this.secretPath = '', + this.path = '/', this.port = 443, - this.serverPort, this.maxConnections = 40, this.allowedUpdates, this.dropPendingUpdates, this.certificate, - this.uploadCertificate = true, + this.secretToken, }) { + _validatePort(); + _validateMaxConnections(); + _normalizePath(); + _normalizeUrl(); + } + + /// Validates the webhook port. + void _validatePort() { if (!_allowedPorts.contains(port)) throw WebhookException.invalidPort; - if (maxConnections > 100) throw WebhookException.invalidMaxConnections; - if (maxConnections < 1) throw WebhookException.invalidMaxConnections; + } + + /// Validates the max connections. + void _validateMaxConnections() { + if (maxConnections > 100 || maxConnections < 1) { + throw WebhookException.invalidMaxConnections; + } + } - if (secretPath.isNotEmpty && !secretPath.startsWith('/')) { - secretPath = '/$secretPath'; + /// Normalizes the webhook path. + void _normalizePath() { + if (path.isNotEmpty && !path.startsWith('/')) { + path = '/$path'; } + } + + /// Normalizes the webhook URL. + void _normalizeUrl() { if (url.endsWith('/')) { url = url.substring(0, url.length - 1); } @@ -85,53 +223,80 @@ class Webhook extends Fetcher { /// Sets the webhook. Future setWebhook() async { return api.setWebhook( - url: "$url:$port$secretPath", + url: "$url:$port$path", certificate: certificate, ipAddress: ipAddress, maxConnections: maxConnections, - allowedUpdates: allowedUpdates, + allowedUpdates: allowedUpdates?.map((e) => e.type).toList(), dropPendingUpdates: dropPendingUpdates, + secretToken: secretToken, ); } /// Starts the webhook fetcher. /// - /// It will set the webhook and listen to the server. + /// It sets the webhook and listens to the server. @override Future start() async { if (await setWebhook()) { _isActive = true; - _server.listen((io.HttpRequest r) async { - if (r.uri.path == secretPath) { - final b = await r.cast>().transform(utf8.decoder).join(); - final update = Update.fromJson(jsonDecode(b)); - addUpdate(update); - r.response.statusCode = 200; - r.response.write({'ok': true, 'result': update.toRawJson()}); - r.response.close(); - } else { - r.response.statusCode = 404; - r.response.write({'ok': false, 'error_code': 404}); - r.response.close(); - } - }); + server.listen(_handleRequest); } else { throw WebhookException.failedToSetWebhook; } } + /// Handles incoming HTTP requests. + Future _handleRequest(io.HttpRequest request) async { + final Map error = { + 'ok': false, + 'error_code': 404, + "description": "Not Found", + }; + + if (request.uri.path != path) { + _sendResponse(request, error["error_code"], error); + return; + } + + if (request.method == "GET") { + error["description"] = "GET Requests are not supported"; + error["error_code"] = 418; + _sendResponse(request, error["error_code"], error); + return; + } + + final body = await request + .cast>() + .transform( + utf8.decoder, + ) + .join(); + final update = Update.fromJson(jsonDecode(body)); + addUpdate(update); + _sendResponse(request, 200, {'ok': true, 'result': update.toRawJson()}); + } + + /// Sends a response to the client. + void _sendResponse( + io.HttpRequest request, + int statusCode, + Map body, + ) { + request.response.statusCode = statusCode; + request.response.write(JsonEncoder.withIndent(' ').convert(body)); + request.response.close(); + } + /// Stops the webhook fetcher. @override Future stop({bool dropPendingUpdates = false}) async { _updateStreamController.close(); await api.deleteWebhook(dropPendingUpdates: dropPendingUpdates); _isActive = false; - return _server.close(force: true); + return server.close(force: true); } - /// Internal flag to check if the webhook is running. - bool _isActive = false; - /// Flag to check if the webhook is running. @override bool get isActive => _isActive; diff --git a/pubspec.yaml b/pubspec.yaml index e40a7bc5..c788816e 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.4 +version: 1.19.5 homepage: https://github.com/HeySreelal/televerse topics: - telegram