diff --git a/CHANGELOG.md b/CHANGELOG.md index 7301f7c3..15acdff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.18.0 + +- 🤖 Bot API 7.5 +- Improved context aware methods +- Added `WithID` abstraction + # 1.17.2 - Added `considerCaption` parameter in `Bot.command` mehtod to match commands in the caption of video/photo messages. diff --git a/README.md b/README.md index 8b4eff40..3138273a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![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.4-blue) + ![](https://shields.io/badge/Latest-Bot%20API%207.5-blue) @@ -13,15 +13,15 @@ --- -🤖 `Bot API version: Bot API 7.4 (May 28, 2024)` +🤖 `Bot API version: Bot API 7.5 (June 18, 2024)` Televerse is a powerful, easy-to-use, and highly customizable Telegram bot framework built with Dart programming language. It provides a complete and well-structured API that enables developers to create and deploy complex Telegram bots with ease. Televerse provides a total of 0 dynamic types on its public interface, making it easy for developers to write strictly typed code. -## 🔥 Bot API 7.4 (May 28, 2024) +## 🔥 Bot API 7.5 (June 18, 2024) -In a nutshell, the updates include new Payment system with [Telegram Stars](https://t.me/BotNews/90), support for message effects and expandable blockquotes. +In a nutshell, the updates include new Payment system with [Telegram Stars](https://t.me/BotNews/90). Includes a bunch of new models related to Star Payments and new method to get Bot's Star Payment history. This update also includes support for Business Bots to edit messages as well as accept callback and inline queries. -Checkout [changelog](https://core.telegram.org/bots/api#may-28-2024) for more details! 🚀 +Checkout [changelog](https://core.telegram.org/bots/api#june-18-2024) for more details! 🚀
diff --git a/lib/src/telegram/models/abstractions.dart b/lib/src/telegram/models/abstractions.dart index 589c6737..4036b69b 100644 --- a/lib/src/telegram/models/abstractions.dart +++ b/lib/src/telegram/models/abstractions.dart @@ -12,5 +12,11 @@ abstract class WithUser { User? get from; } +/// The base class for all the models that have a user. +abstract class WithID { + /// The user who triggered the event. + int get id; +} + /// Null filter function. bool _nullFilter(String _, dynamic value) => value == null; diff --git a/lib/src/telegram/models/abstracts/revenue_withdrawal_state.dart b/lib/src/telegram/models/abstracts/revenue_withdrawal_state.dart new file mode 100644 index 00000000..7fa8adaa --- /dev/null +++ b/lib/src/telegram/models/abstracts/revenue_withdrawal_state.dart @@ -0,0 +1,29 @@ +part of '../models.dart'; + +/// This object describes the state of a revenue withdrawal operation. +/// Currently, it can be one of [RevenueWithdrawalStatePending], [RevenueWithdrawalStateSucceeded], [RevenueWithdrawalStateFailed]. +abstract class RevenueWithdrawalState { + /// Type of the revenue withdrawal state. + RevenueWithdrawalStateType get type; + + /// Creates a new [RevenueWithdrawalState] object. + /// This method decides which [RevenueWithdrawalState] subclass to use based on the [type] field. + static RevenueWithdrawalState fromJson(Map json) { + switch (RevenueWithdrawalStateType.fromJson(json['type'])) { + case RevenueWithdrawalStateType.pending: + return RevenueWithdrawalStatePending.fromJson(json); + case RevenueWithdrawalStateType.succeeded: + return RevenueWithdrawalStateSucceeded.fromJson(json); + case RevenueWithdrawalStateType.failed: + return RevenueWithdrawalStateFailed.fromJson(json); + default: + throw ArgumentError('Invalid revenue withdrawal state type'); + } + } + + /// Creates a new [RevenueWithdrawalState] object from JSON. + const RevenueWithdrawalState(); + + /// Converts a [RevenueWithdrawalState] to a [Map] for JSON encoding. + Map toJson(); +} diff --git a/lib/src/telegram/models/abstracts/transaction_partner.dart b/lib/src/telegram/models/abstracts/transaction_partner.dart new file mode 100644 index 00000000..373c44bf --- /dev/null +++ b/lib/src/telegram/models/abstracts/transaction_partner.dart @@ -0,0 +1,25 @@ +part of '../models.dart'; + +/// This object describes the source of a transaction, or its recipient for outgoing transactions. +/// Currently, it can be one of [TransactionPartnerFragment], [TransactionPartnerUser], [TransactionPartnerOther]. +abstract class TransactionPartner { + /// Type of the transaction partner. + TransactionPartnerType get type; + + /// Creates a new [TransactionPartner] object. + /// This method decides which [TransactionPartner] subclass to use based on the [type] field. + static TransactionPartner fromJson(Map json) { + return switch (json['type']) { + 'fragment' => TransactionPartnerFragment.fromJson(json), + 'user' => TransactionPartnerUser.fromJson(json), + 'other' => TransactionPartnerOther.fromJson(json), + _ => throw ArgumentError('Invalid transaction partner type') + }; + } + + /// Creates a new [TransactionPartner] object from JSON. + const TransactionPartner(); + + /// Converts a [TransactionPartner] to a [Map] for JSON encoding. + Map toJson(); +} diff --git a/lib/src/telegram/models/chat.dart b/lib/src/telegram/models/chat.dart index d1ccacf1..58af98ec 100644 --- a/lib/src/telegram/models/chat.dart +++ b/lib/src/telegram/models/chat.dart @@ -1,8 +1,9 @@ part of 'models.dart'; /// This object represents a chat. -class Chat { +class Chat implements WithID { /// Unique identifier for this chat. + @override final int id; /// Type of the chat, can be either "private", "group", "supergroup" or "channel". diff --git a/lib/src/telegram/models/chat_full_info.dart b/lib/src/telegram/models/chat_full_info.dart index 84bc7f6b..1e5630c7 100644 --- a/lib/src/telegram/models/chat_full_info.dart +++ b/lib/src/telegram/models/chat_full_info.dart @@ -1,7 +1,7 @@ part of 'models.dart'; /// This object represents a chat. -class ChatFullInfo extends Chat { +class ChatFullInfo extends Chat implements WithID { /// Optional. Chat photo. final ChatPhoto? photo; diff --git a/lib/src/telegram/models/models.dart b/lib/src/telegram/models/models.dart index c6c634ad..b5a9953d 100644 --- a/lib/src/telegram/models/models.dart +++ b/lib/src/telegram/models/models.dart @@ -242,3 +242,15 @@ part 'background_fill_freeform_gradient.dart'; part 'background_fill_gradient.dart'; part 'chat_background.dart'; part 'chat.dart'; + +// Bot API 7.5 +part 'star_transaction.dart'; +part 'star_transactions.dart'; +part 'abstracts/transaction_partner.dart'; +part 'transaction_partner_fragment.dart'; +part 'transaction_partner_other.dart'; +part 'transaction_partner_user.dart'; +part 'abstracts/revenue_withdrawal_state.dart'; +part 'revenue_withdrawal_state_pending.dart'; +part 'revenue_withdrawal_state_succeeded.dart'; +part 'revenue_withdrawal_state_failed.dart'; diff --git a/lib/src/telegram/models/revenue_withdrawal_state_failed.dart b/lib/src/telegram/models/revenue_withdrawal_state_failed.dart new file mode 100644 index 00000000..9d269b88 --- /dev/null +++ b/lib/src/telegram/models/revenue_withdrawal_state_failed.dart @@ -0,0 +1,23 @@ +part of 'models.dart'; + +/// The withdrawal failed and the transaction was refunded. +class RevenueWithdrawalStateFailed extends RevenueWithdrawalState { + @override + RevenueWithdrawalStateType get type => RevenueWithdrawalStateType.failed; + + /// Constructs a [RevenueWithdrawalStateFailed] object. + const RevenueWithdrawalStateFailed(); + + /// Creates a [RevenueWithdrawalStateFailed] object from JSON. + factory RevenueWithdrawalStateFailed.fromJson(Map json) { + return RevenueWithdrawalStateFailed(); + } + + /// Converts a [RevenueWithdrawalStateFailed] object to JSON. + @override + Map toJson() { + return { + 'type': type.toJson(), + }; + } +} diff --git a/lib/src/telegram/models/revenue_withdrawal_state_pending.dart b/lib/src/telegram/models/revenue_withdrawal_state_pending.dart new file mode 100644 index 00000000..e20f32ab --- /dev/null +++ b/lib/src/telegram/models/revenue_withdrawal_state_pending.dart @@ -0,0 +1,23 @@ +part of 'models.dart'; + +/// The withdrawal is in progress. +class RevenueWithdrawalStatePending extends RevenueWithdrawalState { + @override + RevenueWithdrawalStateType get type => RevenueWithdrawalStateType.pending; + + /// Constructs a [RevenueWithdrawalStatePending] object. + const RevenueWithdrawalStatePending(); + + /// Creates a [RevenueWithdrawalStatePending] object from JSON. + factory RevenueWithdrawalStatePending.fromJson(Map json) { + return RevenueWithdrawalStatePending(); + } + + /// Converts a [RevenueWithdrawalStatePending] object to JSON. + @override + Map toJson() { + return { + 'type': type.toJson(), + }; + } +} diff --git a/lib/src/telegram/models/revenue_withdrawal_state_succeeded.dart b/lib/src/telegram/models/revenue_withdrawal_state_succeeded.dart new file mode 100644 index 00000000..6ccf44e6 --- /dev/null +++ b/lib/src/telegram/models/revenue_withdrawal_state_succeeded.dart @@ -0,0 +1,37 @@ +part of 'models.dart'; + +/// The withdrawal succeeded. +class RevenueWithdrawalStateSucceeded extends RevenueWithdrawalState { + @override + RevenueWithdrawalStateType get type => RevenueWithdrawalStateType.succeeded; + + /// Date the withdrawal was completed in Unix time. + final int date; + + /// An HTTPS URL that can be used to see transaction details. + final String url; + + /// Constructs a [RevenueWithdrawalStateSucceeded] object. + const RevenueWithdrawalStateSucceeded({ + required this.date, + required this.url, + }); + + /// Creates a [RevenueWithdrawalStateSucceeded] object from JSON. + factory RevenueWithdrawalStateSucceeded.fromJson(Map json) { + return RevenueWithdrawalStateSucceeded( + date: json['date'], + url: json['url'], + ); + } + + /// Converts a [RevenueWithdrawalStateSucceeded] object to JSON. + @override + Map toJson() { + return { + 'type': type.toJson(), + 'date': date, + 'url': url, + }; + } +} diff --git a/lib/src/telegram/models/star_transaction.dart b/lib/src/telegram/models/star_transaction.dart new file mode 100644 index 00000000..8bc785c4 --- /dev/null +++ b/lib/src/telegram/models/star_transaction.dart @@ -0,0 +1,58 @@ +part of 'models.dart'; + +/// This object describes a Telegram Star transaction. +class StarTransaction { + /// Unique identifier of the transaction. + /// Coincides with the identifier of the original transaction for refund transactions. + /// Coincides with [SuccessfulPayment.telegramPaymentChargeId] for successful incoming payments from users. + final String id; + + /// Number of Telegram Stars transferred by the transaction. + final int amount; + + /// Date the transaction was created in Unix time. + final int date; + + /// Optional. Source of an incoming transaction (e.g., a user purchasing goods or services, + /// Fragment refunding a failed withdrawal). Only for incoming transactions. + final TransactionPartner? source; + + /// Optional. Receiver of an outgoing transaction (e.g., a user for a purchase refund, + /// Fragment for a withdrawal). Only for outgoing transactions. + final TransactionPartner? receiver; + + /// Creates a new [StarTransaction] object. + const StarTransaction({ + required this.id, + required this.amount, + required this.date, + this.source, + this.receiver, + }); + + /// Creates a new [StarTransaction] object from json. + factory StarTransaction.fromJson(Map json) { + return StarTransaction( + id: json['id'], + amount: json['amount'], + date: json['date'], + source: json['source'] != null + ? TransactionPartner.fromJson(json['source']) + : null, + receiver: json['receiver'] != null + ? TransactionPartner.fromJson(json['receiver']) + : null, + ); + } + + /// Converts a [StarTransaction] object to json. + Map toJson() { + return { + 'id': id, + 'amount': amount, + 'date': date, + 'source': source?.toJson(), + 'receiver': receiver?.toJson(), + }; + } +} diff --git a/lib/src/telegram/models/star_transactions.dart b/lib/src/telegram/models/star_transactions.dart new file mode 100644 index 00000000..fcc693b6 --- /dev/null +++ b/lib/src/telegram/models/star_transactions.dart @@ -0,0 +1,28 @@ +part of 'models.dart'; + +/// This object represents a list of Telegram Star transactions. +class StarTransactions { + /// The list of transactions. + final List transactions; + + /// Creates a new [StarTransactions] object. + const StarTransactions({ + required this.transactions, + }); + + /// Creates a new [StarTransactions] object from json. + factory StarTransactions.fromJson(Map json) { + return StarTransactions( + transactions: (json['transactions'] as List) + .map((item) => StarTransaction.fromJson(item)) + .toList(), + ); + } + + /// Converts a [StarTransactions] object to json. + Map toJson() { + return { + 'transactions': transactions.map((item) => item.toJson()).toList(), + }; + } +} diff --git a/lib/src/telegram/models/transaction_partner_fragment.dart b/lib/src/telegram/models/transaction_partner_fragment.dart new file mode 100644 index 00000000..9d191f8b --- /dev/null +++ b/lib/src/telegram/models/transaction_partner_fragment.dart @@ -0,0 +1,33 @@ +part of 'models.dart'; + +/// Describes a withdrawal transaction with Fragment. +class TransactionPartnerFragment extends TransactionPartner { + @override + TransactionPartnerType get type => TransactionPartnerType.fragment; + + /// State of the transaction if the transaction is outgoing. + final RevenueWithdrawalState? withdrawalState; + + /// Constructs a [TransactionPartnerFragment] object. + const TransactionPartnerFragment({ + this.withdrawalState, + }); + + /// Creates a [TransactionPartnerFragment] object from JSON. + factory TransactionPartnerFragment.fromJson(Map json) { + return TransactionPartnerFragment( + withdrawalState: json['withdrawal_state'] != null + ? RevenueWithdrawalState.fromJson(json['withdrawal_state']) + : null, + ); + } + + /// Converts a [TransactionPartnerFragment] object to JSON. + @override + Map toJson() { + return { + 'type': type, + 'withdrawal_state': withdrawalState?.toJson(), + }; + } +} diff --git a/lib/src/telegram/models/transaction_partner_other.dart b/lib/src/telegram/models/transaction_partner_other.dart new file mode 100644 index 00000000..5a6ae10d --- /dev/null +++ b/lib/src/telegram/models/transaction_partner_other.dart @@ -0,0 +1,23 @@ +part of 'models.dart'; + +/// Describes a transaction with an unknown source or recipient. +class TransactionPartnerOther extends TransactionPartner { + @override + TransactionPartnerType get type => TransactionPartnerType.other; + + /// Constructs a [TransactionPartnerOther] object. + const TransactionPartnerOther(); + + /// Creates a [TransactionPartnerOther] object from JSON. + factory TransactionPartnerOther.fromJson(Map json) { + return TransactionPartnerOther(); + } + + /// Converts a [TransactionPartnerOther] object to JSON. + @override + Map toJson() { + return { + 'type': type, + }; + } +} diff --git a/lib/src/telegram/models/transaction_partner_user.dart b/lib/src/telegram/models/transaction_partner_user.dart new file mode 100644 index 00000000..38a970e4 --- /dev/null +++ b/lib/src/telegram/models/transaction_partner_user.dart @@ -0,0 +1,31 @@ +part of 'models.dart'; + +/// Describes a transaction with a user. +class TransactionPartnerUser extends TransactionPartner { + @override + TransactionPartnerType get type => TransactionPartnerType.user; + + /// Information about the user. + final User user; + + /// Constructs a [TransactionPartnerUser] object. + const TransactionPartnerUser({ + required this.user, + }); + + /// Creates a [TransactionPartnerUser] object from JSON. + factory TransactionPartnerUser.fromJson(Map json) { + return TransactionPartnerUser( + user: User.fromJson(json['user']), + ); + } + + /// Converts a [TransactionPartnerUser] object to JSON. + @override + Map toJson() { + return { + 'type': type, + 'user': user.toJson(), + }; + } +} diff --git a/lib/src/telegram/models/user.dart b/lib/src/telegram/models/user.dart index ef255c5c..7f051cb2 100644 --- a/lib/src/telegram/models/user.dart +++ b/lib/src/telegram/models/user.dart @@ -1,8 +1,9 @@ part of 'models.dart'; /// This object represents a Telegram user or bot. -class User { +class User implements WithID { /// Unique identifier for this user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a 64-bit integer or double-precision float type are safe for storing this identifier. + @override final int id; /// True, if this user is a bot diff --git a/lib/src/televerse/context/context.dart b/lib/src/televerse/context/context.dart index d68a966b..0218b80e 100644 --- a/lib/src/televerse/context/context.dart +++ b/lib/src/televerse/context/context.dart @@ -821,7 +821,7 @@ class Context { /// Edit message live location /// /// This method will edit the message live location in the current context. - Future editMessageLiveLocation({ + Future editMessageLiveLocation({ String? inlineMessageId, double? latitude, double? longitude, @@ -829,18 +829,25 @@ class Context { int? heading, int? proximityAlertRadius, InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - _verifyInfo([_chatId, _msgId], APIMethod.editMessageLiveLocation); - return api.editMessageLiveLocation( - id, - _msgId!, + if (!_isInline()) { + _verifyInfo([_chatId, _msgId], APIMethod.editMessageLiveLocation); + } + + await api._editMessageLiveLocation<_Ignore>( + chatId: id, + messageId: _msgId, + inlineMessageId: _inlineMsgId, latitude: latitude, longitude: longitude, horizontalAccuracy: horizontalAccuracy, heading: heading, proximityAlertRadius: proximityAlertRadius, replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, ); + return true; } /// Forward the message. @@ -1214,30 +1221,23 @@ class Context { /// Context aware method for stopping live location of message: [APIMethod.stopMessageLiveLocation]. /// /// Use this method to stop updating a live location message before live_period expires. On success, if the message is not an inline message, the edited Message is returned, otherwise True is returned. - /// - /// **IMPORTANT NOTE** - /// - /// This only works for Messages but not INLINE MESSAGES. If you're looking for a way to stop updating inline live location messages, check out [stopInlineMessageLiveLocation]. - Future stopMessageLiveLocation({ + Future stopMessageLiveLocation({ InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - _verifyInfo([_chatId, _msgId], APIMethod.stopMessageLiveLocation); - return api.stopMessageLiveLocation( - id, - _msgId!, - replyMarkup: replyMarkup, - ); - } + if (!_isInline()) { + _verifyInfo([_chatId, _msgId], APIMethod.stopMessageLiveLocation); + } - /// Context aware method for stopping live location of inline message: [APIMethod.stopMessageLiveLocation]. - Future stopInlineMessageLiveLocation({ - InlineKeyboardMarkup? replyMarkup, - }) async { - _verifyInfo([_inlineMsgId], APIMethod.stopMessageLiveLocation); - return api.stopInlineMessageLiveLocation( - _inlineMsgId!, + await api._stopMessageLiveLocation<_Ignore>( + chatId: id, + messageId: _msgId, + inlineMessageId: _inlineMsgId, replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, ); + + return true; } /// Context aware method for send "Typing..." action: [APIMethod.sendChatAction]. @@ -1683,132 +1683,106 @@ class Context { return _inlineMsgId != null; } - /// Edit the message text + /// Edit the message text. Returns True on success. Future editMessageText( String text, { ParseMode? parseMode, List? entities, InlineKeyboardMarkup? replyMarkup, LinkPreviewOptions? linkPreviewOptions, + String? businessConnectionId, }) async { - if (_isInline()) { - await api.editInlineMessageText( - _inlineMsgId!, - text, - parseMode: parseMode, - entities: entities, - replyMarkup: replyMarkup, - linkPreviewOptions: linkPreviewOptions, - ); - } else { + if (!_isInline()) { _verifyInfo([_chatId, _msgId], APIMethod.editMessageText); - await api.editMessageText( - id, - _msgId!, - text, - parseMode: parseMode, - entities: entities, - replyMarkup: replyMarkup, - linkPreviewOptions: linkPreviewOptions, - ); } + + await api._editMessageText<_Ignore>( + text: text, + chatId: id, + inlineMessageId: _inlineMsgId, + messageId: _msgId, + parseMode: parseMode, + entities: entities, + replyMarkup: replyMarkup, + linkPreviewOptions: linkPreviewOptions, + businessConnectionId: businessConnectionId, + ); + return true; } - /// Edit the message caption + /// Edit the message caption. Returns `true` on success. Future editMessageCaption({ String? caption, ParseMode? parseMode, List? captionEntities, InlineKeyboardMarkup? replyMarkup, bool? showCaptionAboveMedia, + String? businessConnectionId, }) async { - if (_isInline()) { - await api.editInlineMessageCaption( - _inlineMsgId!, - caption: caption, - parseMode: parseMode, - captionEntities: captionEntities, - replyMarkup: replyMarkup, - showCaptionAboveMedia: showCaptionAboveMedia, - ); - } else { + if (!_isInline()) { _verifyInfo([_chatId, _msgId], APIMethod.editMessageCaption); - await api.editMessageCaption( - id, - _msgId!, - caption: caption, - parseMode: parseMode, - captionEntities: captionEntities, - replyMarkup: replyMarkup, - showCaptionAboveMedia: showCaptionAboveMedia, - ); } + + await api._editMessageCaption<_Ignore>( + chatId: id, + messageId: _msgId, + inlineMessageId: _inlineMsgId, + caption: caption, + parseMode: parseMode, + captionEntities: captionEntities, + replyMarkup: replyMarkup, + showCaptionAboveMedia: showCaptionAboveMedia, + businessConnectionId: businessConnectionId, + ); + return true; } /// Edit the message media /// Use this method to edit animation, audio, document, photo, or video messages. /// - /// Make sure to pass the type parameter for correct typing. If the message to be edited is an - /// inline message, the type parameter should be `bool`, otherwise `Message` should be passed. - /// - /// Since Televerse don't deal with `dynamic` types on the public interface, if you do not pass - /// type parameter, this method will throw a `TeleverseException`. - Future editMessageMedia( + /// This method ignores the result and always returns True on success. + Future editMessageMedia( InputMedia media, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - if (MessageOrBool != Message && MessageOrBool != bool) { - throw TeleverseException.typeParameterRequired( - "editMessageMedia", - MessageOrBool, - [Message, bool], - ); - } - - if (_isInline()) { - return await api.editInlineMessageMedia( - _inlineMsgId!, - media, - replyMarkup: replyMarkup, - ) as MessageOrBool; - } else { + if (!_isInline()) { _verifyInfo([_chatId, _msgId], APIMethod.editMessageMedia); - return await api.editMessageMedia( - id, - _msgId!, - media, - replyMarkup: replyMarkup, - ) as MessageOrBool; } + + await api._editMessageMedia<_Ignore>( + inlineMessageId: _inlineMsgId, + chatId: id, + messageId: _msgId, + media: media, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, + ); + + return true; } /// Edit the message reply markup /// Use this method to edit only the reply markup of messages. - Future editMessageReplyMarkup({ + Future editMessageReplyMarkup({ InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - if (MessageOrBool != Message && MessageOrBool != bool) { - throw TeleverseException.typeParameterRequired( - "editMessageReplyMarkup", - MessageOrBool, - [Message, bool], - ); - } - if (_isInline()) { - return await api.editInlineMessageReplyMarkup( - _inlineMsgId!, - replyMarkup: replyMarkup, - ) as MessageOrBool; - } else { + if (!_isInline()) { _verifyInfo([_chatId, _msgId], APIMethod.editMessageReplyMarkup); - return await api.editMessageReplyMarkup( - id, - _msgId!, - replyMarkup: replyMarkup, - ) as MessageOrBool; } + + await api._editMessageReplyMarkup<_Ignore>( + chatId: id, + messageId: _msgId, + inlineMessageId: _inlineMsgId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, + ); + + return true; } /// Answer inline query diff --git a/lib/src/televerse/fetch/fetch.dart b/lib/src/televerse/fetch/fetch.dart index 6f0c3b76..c0baf089 100644 --- a/lib/src/televerse/fetch/fetch.dart +++ b/lib/src/televerse/fetch/fetch.dart @@ -22,15 +22,27 @@ abstract class Fetcher { /// The stream controller that emits new updates. final StreamController _updateStreamController; + /// The stream controller that emits new updates. + final StreamController> _updatesStreamController; + /// Creates a new fetcher. - Fetcher() : _updateStreamController = StreamController.broadcast(); + Fetcher() + : _updateStreamController = StreamController.broadcast(), + _updatesStreamController = StreamController.broadcast(); /// Emit new update into the stream. void addUpdate(Update update) => _updateStreamController.add(update); + /// Emit new update into the stream. + void addUpdates(List updates) => + _updatesStreamController.add(updates); + /// Handler for new updates. Stream onUpdate() => _updateStreamController.stream; + /// Handler for new updates. + Stream> onUpdates() => _updatesStreamController.stream; + /// Starts fetching updates. Future start(); diff --git a/lib/src/televerse/fetch/long_polling.dart b/lib/src/televerse/fetch/long_polling.dart index 6b90c5ea..113cb362 100644 --- a/lib/src/televerse/fetch/long_polling.dart +++ b/lib/src/televerse/fetch/long_polling.dart @@ -84,6 +84,7 @@ class LongPolling extends Fetcher { timeout: timeout, allowedUpdates: allowedUpdates?.map((e) => e.type).toList(), ); + addUpdates(updates); int len = updates.length; for (int i = 0; i < len; i++) { if (_updateStreamController.isClosed) { @@ -95,7 +96,9 @@ class LongPolling extends Fetcher { offset = updates[updates.length - 1].updateId + 1; } await Future.delayed(delayDuration); - _resetRetryDelay(); + if (_retryDelay.inSeconds != 1) { + _resetRetryDelay(); + } } catch (err, stackTrace) { _isPolling = false; // If the error is a TelegramException, just go handle it. diff --git a/lib/src/televerse/raw_api.dart b/lib/src/televerse/raw_api.dart index b7eb59c4..fd85d646 100644 --- a/lib/src/televerse/raw_api.dart +++ b/lib/src/televerse/raw_api.dart @@ -901,15 +901,11 @@ class RawAPI { return Message.fromJson(response); } - /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [stopMessageLiveLocation]. - /// - /// On success, the edited [Message] is returned. - /// **IMPORTANT NOTE** - /// - /// This only works for Messages but not INLINE MESSAGES. If you're looking for a way to edit inline live location messages, check out [editInlineMessageLiveLocation]. - Future editMessageLiveLocation( - ID chatId, - int messageId, { + /// (Internal) Abstract method for editing message live location + Future _editMessageLiveLocation({ + ID? chatId, + int? messageId, + String? inlineMessageId, double? latitude, double? longitude, double? horizontalAccuracy, @@ -917,9 +913,11 @@ class RawAPI { int? proximityAlertRadius, InlineKeyboardMarkup? replyMarkup, int? livePeriod, + String? businessConnectionId, }) async { - Map params = { - "chat_id": chatId.id, + final params = { + "inline_message_id": inlineMessageId, + "chat_id": chatId?.id, "message_id": messageId, "latitude": latitude, "longitude": longitude, @@ -928,13 +926,53 @@ class RawAPI { "proximity_alert_radius": proximityAlertRadius, "reply_markup": replyMarkup?.toJson(), "live_period": livePeriod, + "business_connection_id": businessConnectionId, }; - Map response = await _httpClient.postURI( + final response = await _httpClient.postURI( _buildUri(APIMethod.editMessageLiveLocation), params, ); - return Message.fromJson(response); + if (MessageOrBool == _Ignore) { + return _ignore as MessageOrBool; + } + + if (MessageOrBool == Message) { + return Message.fromJson(response) as MessageOrBool; + } + return response; + } + + /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [stopMessageLiveLocation]. + /// + /// On success, the edited [Message] is returned. + /// **IMPORTANT NOTE** + /// + /// This only works for Messages but not INLINE MESSAGES. If you're looking for a way to edit inline live location messages, check out [editInlineMessageLiveLocation]. + Future editMessageLiveLocation( + ID chatId, + int messageId, { + double? latitude, + double? longitude, + double? horizontalAccuracy, + int? heading, + int? proximityAlertRadius, + InlineKeyboardMarkup? replyMarkup, + int? livePeriod, + String? businessConnectionId, + }) async { + return await _editMessageLiveLocation( + chatId: chatId, + messageId: messageId, + latitude: latitude, + longitude: longitude, + horizontalAccuracy: horizontalAccuracy, + heading: heading, + proximityAlertRadius: proximityAlertRadius, + replyMarkup: replyMarkup, + livePeriod: livePeriod, + businessConnectionId: businessConnectionId, + ); } /// Use this method to edit live location messages. A location can be edited until its live_period expires or editing is explicitly disabled by a call to [stopMessageLiveLocation]. @@ -953,22 +991,48 @@ class RawAPI { int? proximityAlertRadius, InlineKeyboardMarkup? replyMarkup, int? livePeriod, + String? businessConnectionId, }) async { - Map params = { - "inline_message_id": inlineMessageId, - "latitude": latitude, - "longitude": longitude, - "horizontal_accuracy": horizontalAccuracy, - "heading": heading, - "proximity_alert_radius": proximityAlertRadius, + return await _editMessageLiveLocation( + inlineMessageId: inlineMessageId, + latitude: latitude, + longitude: longitude, + horizontalAccuracy: horizontalAccuracy, + heading: heading, + proximityAlertRadius: proximityAlertRadius, + replyMarkup: replyMarkup, + livePeriod: livePeriod, + businessConnectionId: businessConnectionId, + ); + } + + /// (Internal) Abstract method for stopping live location + Future _stopMessageLiveLocation({ + ID? chatId, + int? messageId, + String? inlineMessageId, + InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, + }) async { + final params = { + "chat_id": chatId?.id, + "message_id": messageId, "reply_markup": replyMarkup?.toJson(), - "live_period": livePeriod, + "business_connection_id": businessConnectionId, + "inline_message_id": inlineMessageId, }; - bool response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageLiveLocation), + final response = await _httpClient.postURI( + _buildUri(APIMethod.stopMessageLiveLocation), params, ); + if (MessageOrBool == _Ignore) { + return _ignore as MessageOrBool; + } + + if (MessageOrBool == Message) { + return Message.fromJson(response) as MessageOrBool; + } return response; } @@ -981,17 +1045,14 @@ class RawAPI { ID chatId, int messageId, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - Map params = { - "chat_id": chatId.id, - "message_id": messageId, - "reply_markup": replyMarkup?.toJson(), - }; - Map response = await _httpClient.postURI( - _buildUri(APIMethod.stopMessageLiveLocation), - params, + return await _stopMessageLiveLocation( + chatId: chatId, + messageId: messageId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, ); - return Message.fromJson(response); } /// Use this method to stop updating a live location message before live_period expires. @@ -1002,18 +1063,15 @@ class RawAPI { /// /// On success, true is returned. Future stopInlineMessageLiveLocation( - String? inlineMessageId, { + String inlineMessageId, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - Map params = { - "inline_message_id": inlineMessageId, - "reply_markup": replyMarkup?.toJson(), - }; - bool response = await _httpClient.postURI( - _buildUri(APIMethod.stopMessageLiveLocation), - params, + return await _stopMessageLiveLocation( + inlineMessageId: inlineMessageId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, ); - return response; } /// Use this method to send information about a venue. On success, the sent Message is returned. @@ -2241,6 +2299,45 @@ class RawAPI { return ChatAdministratorRights.fromJson(response); } + /// (Internal) Abstract method for the `editMessageText` + Future _editMessageText({ + required String text, + ID? chatId, + int? messageId, + String? inlineMessageId, + ParseMode? parseMode, + List? entities, + InlineKeyboardMarkup? replyMarkup, + LinkPreviewOptions? linkPreviewOptions, + String? businessConnectionId, + }) async { + final params = { + "inline_message_id": inlineMessageId, + "chat_id": chatId?.id, + "message_id": messageId, + "text": text, + "parse_mode": parseMode?.value, + "entities": entities?.map((e) => e.toJson()).toList(), + "reply_markup": replyMarkup?.toJson(), + "link_preview": linkPreviewOptions?.toJson(), + "business_connection_id": businessConnectionId, + }; + + final response = await _httpClient.postURI( + _buildUri(APIMethod.editMessageText), + params, + ); + + if (MessageOrBool == _Ignore) { + return _ignore as MessageOrBool; + } + + if (MessageOrBool == Message) { + return Message.fromJson(response) as MessageOrBool; + } + return response; + } + /// Use this method to edit text and game messages. /// /// IMPORTANT: @@ -2255,21 +2352,18 @@ class RawAPI { List? entities, InlineKeyboardMarkup? replyMarkup, LinkPreviewOptions? linkPreviewOptions, + String? businessConnectionId, }) async { - Map params = { - "chat_id": chatId.id, - "message_id": messageId, - "text": text, - "parse_mode": parseMode?.value, - "entities": entities?.map((e) => e.toJson()).toList(), - "reply_markup": replyMarkup?.toJson(), - "link_preview": linkPreviewOptions?.toJson(), - }; - Map response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageText), - params, + return await _editMessageText( + chatId: chatId, + text: text, + messageId: messageId, + parseMode: parseMode, + entities: entities, + replyMarkup: replyMarkup, + linkPreviewOptions: linkPreviewOptions, + businessConnectionId: businessConnectionId, ); - return Message.fromJson(response); } /// Use this method to edit inline text messages. @@ -2287,19 +2381,55 @@ class RawAPI { List? entities, InlineKeyboardMarkup? replyMarkup, LinkPreviewOptions? linkPreviewOptions, + String? businessConnectionId, }) async { - Map params = { + return await _editMessageText( + text: text, + inlineMessageId: inlineMessageId, + parseMode: parseMode, + entities: entities, + replyMarkup: replyMarkup, + linkPreviewOptions: linkPreviewOptions, + businessConnectionId: businessConnectionId, + ); + } + + /// (Internal) Abstract method for editing message captions + Future _editMessageCaption({ + ID? chatId, + int? messageId, + String? inlineMessageId, + String? caption, + ParseMode? parseMode, + List? captionEntities, + InlineKeyboardMarkup? replyMarkup, + bool? showCaptionAboveMedia, + String? businessConnectionId, + }) async { + final params = { "inline_message_id": inlineMessageId, - "text": text, + "chat_id": chatId?.id, + "message_id": messageId, + "caption": caption, "parse_mode": parseMode?.value, - "entities": entities?.map((e) => e.toJson()).toList(), + "caption_entities": captionEntities?.map((e) => e.toJson()).toList(), "reply_markup": replyMarkup?.toJson(), - "link_preview": linkPreviewOptions?.toJson(), + "show_caption_above_media": showCaptionAboveMedia, + "business_connection_id": businessConnectionId, }; - bool response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageText), + final response = await _httpClient.postURI( + _buildUri(APIMethod.editMessageCaption), params, ); + + if (MessageOrBool == _Ignore) { + return _ignore as MessageOrBool; + } + + if (MessageOrBool == Message) { + return Message.fromJson(response) as MessageOrBool; + } + return response; } @@ -2319,21 +2449,18 @@ class RawAPI { List? captionEntities, InlineKeyboardMarkup? replyMarkup, bool? showCaptionAboveMedia, + String? businessConnectionId, }) async { - Map params = { - "chat_id": chatId.id, - "message_id": messageId, - "caption": caption, - "parse_mode": parseMode?.value, - "caption_entities": captionEntities?.map((e) => e.toJson()).toList(), - "reply_markup": replyMarkup?.toJson(), - "show_caption_above_media": showCaptionAboveMedia, - }; - Map response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageCaption), - params, + return await _editMessageCaption( + chatId: chatId, + messageId: messageId, + caption: caption, + parseMode: parseMode, + captionEntities: captionEntities, + replyMarkup: replyMarkup, + showCaptionAboveMedia: showCaptionAboveMedia, + businessConnectionId: businessConnectionId, ); - return Message.fromJson(response); } /// Use this method to edit inline captions of messages. @@ -2351,43 +2478,38 @@ class RawAPI { List? captionEntities, InlineKeyboardMarkup? replyMarkup, bool? showCaptionAboveMedia, + String? businessConnectionId, }) async { - Map params = { - "inline_message_id": inlineMessageId, - "caption": caption, - "parse_mode": parseMode?.value, - "caption_entities": captionEntities?.map((e) => e.toJson()).toList(), - "reply_markup": replyMarkup?.toJson(), - "show_caption_above_media": showCaptionAboveMedia, - }; - bool response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageCaption), - params, + return await _editMessageCaption( + inlineMessageId: inlineMessageId, + caption: caption, + parseMode: parseMode, + captionEntities: captionEntities, + replyMarkup: replyMarkup, + showCaptionAboveMedia: showCaptionAboveMedia, + businessConnectionId: businessConnectionId, ); - return response; } - /// Use this method to edit animation, audio, document, photo, or video messages. If a message is part of a message album, then it can be edited only to an audio for audio albums, only to a document for document albums and to a photo or a video otherwise. When an inline message is edited, a new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. - /// - /// IMPORTANT: - /// * This method is only for editing messages. This won't work for inline messages. - /// * If you're looking for a way to edit inline messages, use [editInlineMessageMedia]. - /// - /// On success, [Message] is returned. - Future editMessageMedia( - ID chatId, - int messageId, - InputMedia media, { + /// (Internal) Abstract method for editing message media + Future _editMessageMedia({ + required InputMedia media, + ID? chatId, + int? messageId, + String? inlineMessageId, InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - Map params = { - "chat_id": chatId.id, + final params = { + "chat_id": chatId?.id, "message_id": messageId, + "inline_message_id": inlineMessageId, "reply_markup": replyMarkup?.toJson(), + "business_connection_id": businessConnectionId, }; const field = "media"; - Map response; + dynamic response; final files = _getFiles([ _MultipartHelper(media.media, field), ]); @@ -2407,7 +2529,38 @@ class RawAPI { ); } - return Message.fromJson(response); + if (MessageOrBool == _Ignore) { + return _ignore as MessageOrBool; + } + + if (MessageOrBool == Message) { + return Message.fromJson(response) as MessageOrBool; + } + + return response; + } + + /// Use this method to edit animation, audio, document, photo, or video messages. If a message is part of a message album, then it can be edited only to an audio for audio albums, only to a document for document albums and to a photo or a video otherwise. When an inline message is edited, a new file can't be uploaded; use a previously uploaded file via its file_id or specify a URL. On success, if the edited message is not an inline message, the edited Message is returned, otherwise True is returned. + /// + /// IMPORTANT: + /// * This method is only for editing messages. This won't work for inline messages. + /// * If you're looking for a way to edit inline messages, use [editInlineMessageMedia]. + /// + /// On success, [Message] is returned. + Future editMessageMedia( + ID chatId, + int messageId, + InputMedia media, { + InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, + }) async { + return await _editMessageMedia( + media: media, + chatId: chatId, + messageId: messageId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, + ); } /// Use this method to edit inline media of messages. @@ -2421,31 +2574,42 @@ class RawAPI { String inlineMessageId, InputMedia media, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - Map params = { + return await _editMessageMedia( + media: media, + inlineMessageId: inlineMessageId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, + ); + } + + /// (Internal) Abstract method for editing message reply markup + Future _editMessageReplyMarkup({ + ID? chatId, + int? messageId, + String? inlineMessageId, + InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, + }) async { + final params = { + "chat_id": chatId?.id, "inline_message_id": inlineMessageId, + "message_id": messageId, "reply_markup": replyMarkup?.toJson(), + "business_connection_id": businessConnectionId, }; - const field = "media"; + final response = await _httpClient.postURI( + _buildUri(APIMethod.editMessageReplyMarkup), + params, + ); - bool response; - final files = _getFiles([ - _MultipartHelper(media.media, field), - ]); - params[field] = media.getValue(field); - if (files.isNotEmpty) { - response = await _httpClient.multipartPost( - _buildUri( - APIMethod.editMessageMedia, - ), - files, - params, - ); - } else { - response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageMedia), - params, - ); + if (MessageOrBool == _Ignore) { + return _ignore as MessageOrBool; + } + + if (MessageOrBool == Message) { + return Message.fromJson(response) as MessageOrBool; } return response; @@ -2463,17 +2627,14 @@ class RawAPI { ID chatId, int messageId, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - Map params = { - "chat_id": chatId.id, - "message_id": messageId, - "reply_markup": replyMarkup?.toJson(), - }; - Map response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageReplyMarkup), - params, + return await _editMessageReplyMarkup( + chatId: chatId, + messageId: messageId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, ); - return Message.fromJson(response); } /// Use this method to edit only the reply markup of inline messages. @@ -2487,16 +2648,13 @@ class RawAPI { Future editInlineMessageReplyMarkup( String inlineMessageId, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { - Map params = { - "inline_message_id": inlineMessageId, - "reply_markup": replyMarkup?.toJson(), - }; - bool response = await _httpClient.postURI( - _buildUri(APIMethod.editMessageReplyMarkup), - params, + return await _editMessageReplyMarkup( + inlineMessageId: inlineMessageId, + replyMarkup: replyMarkup, + businessConnectionId: businessConnectionId, ); - return response; } /// Use this method to stop a poll which was sent by the bot. On success, the stopped [Poll] is returned. @@ -2504,11 +2662,13 @@ class RawAPI { ID chatId, int messageId, { InlineKeyboardMarkup? replyMarkup, + String? businessConnectionId, }) async { Map params = { "chat_id": chatId.id, "message_id": messageId, "reply_markup": replyMarkup?.toJson(), + "business_connection_id": businessConnectionId, }; Map response = await _httpClient.postURI( _buildUri(APIMethod.stopPoll), @@ -3710,4 +3870,23 @@ class RawAPI { ); return response; } + + /// Returns the bot's Telegram Star transactions in chronological order. + /// On success, returns a [StarTransactions] object. + /// + /// See more at https://core.telegram.org/bots/api#getstartransactions + Future getStarTransactions({ + int? offset, + int? limit = 100, + }) async { + Map params = { + "offset": offset, + "limit": limit, + }; + + Uri uri = _buildUri(APIMethod.getStarTransactions); + Map response = await _httpClient.postURI(uri, params); + + return StarTransactions.fromJson(response); + } } diff --git a/lib/src/types/methods.dart b/lib/src/types/methods.dart index d310b935..eeafcfb9 100644 --- a/lib/src/types/methods.dart +++ b/lib/src/types/methods.dart @@ -373,6 +373,9 @@ enum APIMethod { /// To refunds a successful payment in Telegram Stars. refundStarPayment._("refundStarPayment"), + + /// Returns the bot's Telegram Star transactions in chronological order + getStarTransactions._("getStarTransactions"), ; /// The name of the method. diff --git a/lib/src/types/revenue_withdrawal_state_type.dart b/lib/src/types/revenue_withdrawal_state_type.dart new file mode 100644 index 00000000..2e5ec2f9 --- /dev/null +++ b/lib/src/types/revenue_withdrawal_state_type.dart @@ -0,0 +1,31 @@ +part of 'types.dart'; + +/// This object represents the type of the revenue withdrawal state. +enum RevenueWithdrawalStateType { + /// The withdrawal is in progress. + pending("pending"), + + /// The withdrawal succeeded. + succeeded("succeeded"), + + /// The withdrawal failed and the transaction was refunded. + failed("failed"), + ; + + /// The value of this enum. + final String value; + + /// Constructs a new [RevenueWithdrawalStateType]. + const RevenueWithdrawalStateType(this.value); + + /// Constructs a new [RevenueWithdrawalStateType] from a [String]. + static RevenueWithdrawalStateType fromJson(String value) { + return RevenueWithdrawalStateType.values + .firstWhere((e) => e.value == value); + } + + /// Converts this [RevenueWithdrawalStateType] to a [String]. + String toJson() { + return value; + } +} diff --git a/lib/src/types/transaction_partner_type.dart b/lib/src/types/transaction_partner_type.dart new file mode 100644 index 00000000..73c68579 --- /dev/null +++ b/lib/src/types/transaction_partner_type.dart @@ -0,0 +1,30 @@ +part of 'types.dart'; + +/// This object represents the type of the transaction partner. +enum TransactionPartnerType { + /// Represents a withdrawal transaction with Fragment. + fragment("fragment"), + + /// Represents a transaction with a user. + user("user"), + + /// Represents a transaction with an unknown source or recipient. + other("other"), + ; + + /// The value of this enum. + final String value; + + /// Constructs a new [TransactionPartnerType]. + const TransactionPartnerType(this.value); + + /// Constructs a new [TransactionPartnerType] from a [String]. + static TransactionPartnerType fromJson(String value) { + return TransactionPartnerType.values.firstWhere((e) => e.value == value); + } + + /// Converts this [TransactionPartnerType] to a [String]. + String toJson() { + return value; + } +} diff --git a/lib/src/types/types.dart b/lib/src/types/types.dart index d148df28..8f7d2cac 100644 --- a/lib/src/types/types.dart +++ b/lib/src/types/types.dart @@ -29,3 +29,5 @@ part 'message_origin_type.dart'; part 'input_sticker_format.dart'; part 'background_fill_type.dart'; part 'background_type_type.dart'; +part 'transaction_partner_type.dart'; +part 'revenue_withdrawal_state_type.dart'; diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 8f0966a5..7212ce54 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -168,3 +168,11 @@ extension GetUserChatID on User { return false; } } + +/// (Internal) Ignore value +class _Ignore { + const _Ignore(); +} + +/// Ignores the value +const _ignore = _Ignore(); diff --git a/pubspec.yaml b/pubspec.yaml index 554ba9bc..cc799116 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.4! -version: 1.17.2 +description: Televerse lets you create your own efficient Telegram bots with ease in Dart. Supports latest Telegram Bot API - 7.5! +version: 1.18.0 homepage: https://github.com/HeySreelal/televerse topics: - telegram