diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bbc70..eab6a36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.25.0 + +- 🤖 Bot API 7.10 +- Added `Bot.onPaidMediaPurchase` for listening to [PaidMediaPurchased](https://core.telegram.org/bots/api#paidmediapurchased) updates. +- Added much more helper methods on `Context` + # 1.24.0 - 🤖 Bot API 7.9 diff --git a/README.md b/README.md index 1e429a4..5d32bd0 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,16 @@ [![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/xooniverse/televerse?color=green) -![](https://shields.io/badge/Latest-Bot%20API%207.9-blue) +![](https://shields.io/badge/Latest-Bot%20API%207.10-blue) - + - --- -🤖 `Bot API version: Bot API 7.9 (August 14, 2024)` +🤖 `Bot API version: Bot API 7.10 (September 6, 2024)` Televerse is a powerful, easy-to-use, and highly customizable Telegram bot framework built with Dart programming language. It provides a complete and @@ -23,13 +22,13 @@ public interface, making it easy for developers to write strictly typed code. ## 🔥 What's latest? -### 🤖 Bot API 7.9 +### 🤖 Bot API 7.10 -(🗓️ August 14, 2024) +(🗓️ September 6, 2024) -In a nutshell, this update brigngs support for channel subscription, and support for Paid Media across all chats. +In a nutshell, this update brigngs support for Telegram Star giveaway, new Update type for paid media purchases. -Checkout [changelog](https://core.telegram.org/bots/api-changelog#august-14-2024) for more +Checkout [changelog](https://core.telegram.org/bots/api-changelog#september-6-2024) for more details! 🚀 ### 🎉 Support for Custom Contexts! diff --git a/lib/src/telegram/models/birthdate.dart b/lib/src/telegram/models/birthdate.dart index 7fcb761..ef66747 100644 --- a/lib/src/telegram/models/birthdate.dart +++ b/lib/src/telegram/models/birthdate.dart @@ -33,6 +33,6 @@ class Birthdate { 'day': day, 'month': month, 'year': year, - }; + }..removeWhere(_nullFilter); } } diff --git a/lib/src/telegram/models/chat_boost_source_giveaway.dart b/lib/src/telegram/models/chat_boost_source_giveaway.dart index d84cc27..ad0968d 100644 --- a/lib/src/telegram/models/chat_boost_source_giveaway.dart +++ b/lib/src/telegram/models/chat_boost_source_giveaway.dart @@ -15,11 +15,15 @@ class ChatBoostSourceGiveaway implements ChatBoostSource { /// Optional. True, if the giveaway was completed, but there was no user to win the prize final bool? isUnclaimed; + /// Optional. The number of Telegram Stars to be split between giveaway winners; for Telegram Star giveaways only + final int? prizeStarCount; + /// Creates a source of a chat boost. const ChatBoostSourceGiveaway({ this.user, this.isUnclaimed, this.giveawayMessageId = 0, + this.prizeStarCount, }); /// Converts the `ChatBoostSourceGiveaway` object to a JSON object. @@ -30,6 +34,7 @@ class ChatBoostSourceGiveaway implements ChatBoostSource { 'user': user?.toJson(), 'is_unclaimed': isUnclaimed, 'giveaway_message_id': giveawayMessageId, + 'prize_star_count': prizeStarCount, }..removeWhere(_nullFilter); } @@ -39,6 +44,7 @@ class ChatBoostSourceGiveaway implements ChatBoostSource { user: User.fromJson(json['user']), isUnclaimed: json['is_unclaimed'], giveawayMessageId: json['giveaway_message_id'], + prizeStarCount: json['prize_star_count'], ); } } diff --git a/lib/src/telegram/models/giveaway.dart b/lib/src/telegram/models/giveaway.dart index 74e1de5..e59a909 100644 --- a/lib/src/telegram/models/giveaway.dart +++ b/lib/src/telegram/models/giveaway.dart @@ -26,6 +26,9 @@ class Giveaway { /// The number of months the Telegram Premium subscription won from the giveaway will be active for final int? premiumSubscriptionMonthCount; + /// Optional. The number of Telegram Stars to be split between giveaway winners; for Telegram Star giveaways only + final int? prizeStarCount; + /// Constructor const Giveaway({ required this.chats, @@ -36,6 +39,7 @@ class Giveaway { this.prizeDescription, this.countryCodes, this.premiumSubscriptionMonthCount, + this.prizeStarCount, }); /// Constructor from JSON data @@ -50,6 +54,7 @@ class Giveaway { prizeDescription: json['prize_description'], countryCodes: json['country_codes']?.cast(), premiumSubscriptionMonthCount: json['premium_subscription_month_count'], + prizeStarCount: json['prize_star_count'], ); } @@ -64,6 +69,7 @@ class Giveaway { 'prize_description': prizeDescription, 'country_codes': countryCodes, 'premium_subscription_month_count': premiumSubscriptionMonthCount, + 'prize_star_count': prizeStarCount, }..removeWhere(_nullFilter); } } diff --git a/lib/src/telegram/models/giveaway_completed.dart b/lib/src/telegram/models/giveaway_completed.dart index 23e917d..e7b1bd3 100644 --- a/lib/src/telegram/models/giveaway_completed.dart +++ b/lib/src/telegram/models/giveaway_completed.dart @@ -11,11 +11,15 @@ class GiveawayCompleted { /// Message with the giveaway that was completed, if it wasn't deleted final Message? giveawayMessage; + /// Optional. True, if the giveaway is a Telegram Star giveaway. Otherwise, currently, the giveaway is a Telegram Premium giveaway. + final bool? isStarGiveaway; + /// Constructor const GiveawayCompleted({ required this.winnerCount, this.unclaimedPrizeCount, this.giveawayMessage, + this.isStarGiveaway, }); /// Constructor from JSON data @@ -26,6 +30,7 @@ class GiveawayCompleted { giveawayMessage: json['giveaway_message'] != null ? Message.fromJson(json['giveaway_message']) : null, + isStarGiveaway: json['is_star_giveaway'], ); } @@ -35,6 +40,7 @@ class GiveawayCompleted { 'winner_count': winnerCount, 'unclaimed_prize_count': unclaimedPrizeCount, 'giveaway_message': giveawayMessage?.toJson(), + 'is_star_giveaway': isStarGiveaway, }..removeWhere(_nullFilter); } } diff --git a/lib/src/telegram/models/giveaway_created.dart b/lib/src/telegram/models/giveaway_created.dart index 6f2d0f2..6f28de7 100644 --- a/lib/src/telegram/models/giveaway_created.dart +++ b/lib/src/telegram/models/giveaway_created.dart @@ -2,16 +2,25 @@ part of 'models.dart'; /// This object represents a service message about the creation of a scheduled giveaway. Currently holds no information. class GiveawayCreated { + /// Optional. The number of Telegram Stars to be split between giveaway winners; for Telegram Star giveaways only + final int? prizeStarCount; + /// Creates a `GiveawayCreated` object. - const GiveawayCreated(); + const GiveawayCreated({ + this.prizeStarCount, + }); /// Creates a `GiveawayCreated` object from a JSON object. factory GiveawayCreated.fromJson(Map json) { - return GiveawayCreated(); + return GiveawayCreated( + prizeStarCount: json['prize_star_count'], + ); } /// Converts the `GiveawayCreated` object to a JSON object. Map toJson() { - return {}; + return { + 'prize_star_count': prizeStarCount, + }..removeWhere(_nullFilter); } } diff --git a/lib/src/telegram/models/giveaway_winners.dart b/lib/src/telegram/models/giveaway_winners.dart index 27ea357..b2f846a 100644 --- a/lib/src/telegram/models/giveaway_winners.dart +++ b/lib/src/telegram/models/giveaway_winners.dart @@ -35,6 +35,9 @@ class GiveawayWinners { /// Description of additional giveaway prize final String? prizeDescription; + /// Optional. The number of Telegram Stars to be split between giveaway winners; for Telegram Star giveaways only + final int? prizeStarCount; + /// Constructor const GiveawayWinners({ required this.chat, @@ -48,6 +51,7 @@ class GiveawayWinners { this.onlyNewMembers, this.wasRefunded, this.prizeDescription, + this.prizeStarCount, }); /// Constructor from JSON data @@ -65,6 +69,7 @@ class GiveawayWinners { onlyNewMembers: json['only_new_members'], wasRefunded: json['was_refunded'], prizeDescription: json['prize_description'], + prizeStarCount: json['prize_star_count'], ); } @@ -82,6 +87,7 @@ class GiveawayWinners { 'only_new_members': onlyNewMembers, 'was_refunded': wasRefunded, 'prize_description': prizeDescription, + 'prize_star_count': prizeStarCount, }..removeWhere(_nullFilter); } } diff --git a/lib/src/telegram/models/models.dart b/lib/src/telegram/models/models.dart index 19aeb84..42408d0 100644 --- a/lib/src/telegram/models/models.dart +++ b/lib/src/telegram/models/models.dart @@ -271,3 +271,6 @@ part 'refunded_payment.dart'; // Bot API 7.9 part 'reaction_type_paid.dart'; + +// Bot API 7.10 +part 'paid_media_purchased.dart'; diff --git a/lib/src/telegram/models/paid_media_purchased.dart b/lib/src/telegram/models/paid_media_purchased.dart new file mode 100644 index 0000000..7379cc5 --- /dev/null +++ b/lib/src/telegram/models/paid_media_purchased.dart @@ -0,0 +1,37 @@ +part of 'models.dart'; + +/// Represents a paid media purchase made by a user. +class PaidMediaPurchased implements WithUser { + /// The user who purchased the media. + @override + final User from; + + /// A bot-specified payload related to the paid media. + final String paidMediaPayload; + + /// Creates a [PaidMediaPurchased] object. + const PaidMediaPurchased({ + required this.from, + required this.paidMediaPayload, + }); + + /// Creates a [PaidMediaPurchased] object from a JSON map. + /// + /// The JSON map should contain `from` and `paid_media_payload` fields. + factory PaidMediaPurchased.fromJson(Map json) { + return PaidMediaPurchased( + from: User.fromJson(json['from']), + paidMediaPayload: json['paid_media_payload'], + ); + } + + /// Converts this object to a JSON map. + /// + /// Returns a map with `from` and `paid_media_payload` fields. + Map toJson() { + return { + 'from': from.toJson(), + 'paid_media_payload': paidMediaPayload, + }; + } +} diff --git a/lib/src/telegram/models/transaction_partner_user.dart b/lib/src/telegram/models/transaction_partner_user.dart index fd51dca..5f528c5 100644 --- a/lib/src/telegram/models/transaction_partner_user.dart +++ b/lib/src/telegram/models/transaction_partner_user.dart @@ -14,11 +14,15 @@ class TransactionPartnerUser extends TransactionPartner { /// Optional. Information about the paid media bought by the user final List? paidMedia; + /// Optional. Bot-specified paid media payload + final String? paidMediaPayload; + /// Constructs a [TransactionPartnerUser] object. const TransactionPartnerUser({ required this.user, this.invoicePayload, this.paidMedia, + this.paidMediaPayload, }); /// Creates a [TransactionPartnerUser] object from JSON. @@ -33,6 +37,7 @@ class TransactionPartnerUser extends TransactionPartner { ), ) : null, + paidMediaPayload: json['paid_media_payload'], ); } @@ -44,6 +49,7 @@ class TransactionPartnerUser extends TransactionPartner { 'user': user.toJson(), 'invoice_payload': invoicePayload, 'paid_media': paidMedia?.map((e) => e.toJson()).toList(), + 'paid_media_payload': paidMediaPayload, }..removeWhere(_nullFilter); } } diff --git a/lib/src/telegram/models/update.dart b/lib/src/telegram/models/update.dart index 3e6ae9d..1341aab 100644 --- a/lib/src/telegram/models/update.dart +++ b/lib/src/telegram/models/update.dart @@ -73,6 +73,9 @@ class Update { /// Optional. Messages were deleted from a connected business account final BusinessMessagesDeleted? deletedBusinessMessages; + /// Optional. A user purchased paid media with a non-empty payload sent by the bot in a non-channel chat + final PaidMediaPurchased? purchasedPaidMedia; + /// Update Constructor const Update({ required this.updateId, @@ -98,6 +101,7 @@ class Update { this.businessMessage, this.editedBusinessMessage, this.deletedBusinessMessages, + this.purchasedPaidMedia, }); /// Creates a [Update] from json [Map]. @@ -169,6 +173,9 @@ class Update { deletedBusinessMessages: json['deleted_business_messages'] != null ? BusinessMessagesDeleted.fromJson(json['deleted_business_messages']) : null, + purchasedPaidMedia: json['purchased_paid_media'] != null + ? PaidMediaPurchased.fromJson(json['purchased_paid_media']) + : null, ); } @@ -198,6 +205,7 @@ class Update { 'business_message': businessMessage?.toJson(), 'edited_business_message': editedBusinessMessage?.toJson(), 'deleted_business_messages': deletedBusinessMessages?.toJson(), + 'purchased_paid_media': purchasedPaidMedia?.toJson(), }..removeWhere(_nullFilter); } @@ -250,6 +258,8 @@ class Update { return UpdateType.editedBusinessMessage; } else if (deletedBusinessMessages != null) { return UpdateType.deletedBusinessMessages; + } else if (purchasedPaidMedia != null) { + return UpdateType.purchasedPaidMedia; } else { throw TeleverseException( "The update type is unknown", diff --git a/lib/src/televerse/api/raw_api.dart b/lib/src/televerse/api/raw_api.dart index e4f4fe1..2da4595 100644 --- a/lib/src/televerse/api/raw_api.dart +++ b/lib/src/televerse/api/raw_api.dart @@ -3984,6 +3984,7 @@ class RawAPI { ReplyParameters? replyParameters, ReplyMarkup? replyMarkup, String? businessConnectionId, + String? payload, }) async { final params = { "chat_id": chatId.id, @@ -3997,6 +3998,7 @@ class RawAPI { "reply_parameters": replyParameters?.toJson(), "reply_markup": replyMarkup?.toJson(), "business_connection_id": businessConnectionId, + "payload": payload, }; List<_MultipartHelper> helpers = []; diff --git a/lib/src/televerse/bot/bot.dart b/lib/src/televerse/bot/bot.dart index bb69b13..7b13c17 100644 --- a/lib/src/televerse/bot/bot.dart +++ b/lib/src/televerse/bot/bot.dart @@ -2653,4 +2653,14 @@ class Bot { options: options, ); } + + /// Registers a callback to be fired when a user purchases paid media sent by the bot + void onPaidMediaPurchase( + Handler callback, + ) { + return _acceptAll( + callback, + [UpdateType.purchasedPaidMedia], + ); + } } diff --git a/lib/src/televerse/context/context.dart b/lib/src/televerse/context/context.dart index 3dc16c7..f0d932e 100644 --- a/lib/src/televerse/context/context.dart +++ b/lib/src/televerse/context/context.dart @@ -160,6 +160,72 @@ class Context { ); } } + + /// Determine whether the update is a text message + bool hasText() => msg?.text != null; + + /// Determine whether the update contains a photo + bool hasPhoto() => msg?.photo != null; + + /// Determine whether the update contains a video + bool hasVideo() => msg?.video != null; + + /// Determine whether the update contains a document + bool hasDocument() => msg?.document != null; + + /// Determine whether the update contains a location + bool hasLocation() => msg?.location != null; + + /// Determine whether the update contains a live location + bool hasLiveLocation() => msg?.location?.livePeriod != null; + + /// Determine whether the update contains a GIF/Animation + bool hasAnimation() => msg?.animation != null; + + /// Determine whether the update is part of media group + bool hasMediaGroup() => msg?.mediaGroupId != null; + + /// Determine whether the incoming message has a message effect added to it + bool hasEffect() => msg?.hasEffect == true; + + /// Determine whether the update contains a paid media + bool hasPaidMedia() => msg?.paidMedia != null; + + /// Determine whether the update contains a contact + bool hasContact() => msg?.contact != null; + + /// Determine whether the update is a service message + bool isServiceMessage() { + return msg?.leftChatMember != null || + msg?.newChatTitle != null || + msg?.newChatPhoto != null || + msg?.deleteChatPhoto == true || + msg?.groupChatCreated == true || + msg?.supergroupChatCreated == true || + msg?.channelChatCreated == true || + msg?.messageAutoDeleteTimerChanged != null || + msg?.successfulPayment != null || + msg?.refundedPayment != null || + msg?.usersShared != null || + msg?.chatShared != null || + msg?.writeAccessAllowed != null || + msg?.proximityAlertTriggered != null || + msg?.boostAdded != null || + msg?.chatBackgroundSet != null || + msg?.forumTopicCreated != null || + msg?.forumTopicEdited != null || + msg?.forumTopicClosed != null || + msg?.forumTopicReopened != null || + msg?.generalForumTopicHidden != null || + msg?.generalForumTopicUnhidden != null || + msg?.giveawayCreated != null || + msg?.giveawayCompleted != null || + msg?.videoChatScheduled != null || + msg?.videoChatStarted != null || + msg?.videoChatEnded != null || + msg?.videoChatParticipantsInvited != null || + msg?.webAppData != null; + } } /// Base handler diff --git a/lib/src/types/update_type.dart b/lib/src/types/update_type.dart index 7b5a7b0..0c82538 100644 --- a/lib/src/types/update_type.dart +++ b/lib/src/types/update_type.dart @@ -67,6 +67,9 @@ enum UpdateType { /// Messages were deleted from a connected business account deletedBusinessMessages("deleted_business_messages"), + + /// Updates about purchased paid media + purchasedPaidMedia("purchased_paid_media"), ; /// The value of this enum. diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 8a657d4..c3822be 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -68,7 +68,8 @@ extension FromAndChatExt on Update { myChatMember ?? chatMember ?? chatJoinRequest ?? - businessConnection) + businessConnection ?? + purchasedPaidMedia) ?.from; if (callbackQuery?.message is Message) { x ??= (callbackQuery?.message as Message).from; diff --git a/pubspec.yaml b/pubspec.yaml index e07ba5b..469d429 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.9! -version: 1.24.0 +description: Televerse lets you create your own efficient Telegram bots with ease in Dart. Supports latest Telegram Bot API - 7.10! +version: 1.25.0 homepage: https://televerse.xooniverse.com repository: https://github.com/xooniverse/televerse topics: @@ -17,7 +17,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - dio: ^5.4.0 + dio: ^5.7.0 dev_dependencies: lints: ^4.0.0