diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e013d781777..66c5724d647a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.8.30 LANGUAGES CXX C) +project(TDLib VERSION 1.8.31 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -547,6 +547,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/SharedDialog.cpp td/telegram/SpecialStickerSetType.cpp td/telegram/SponsoredMessageManager.cpp + td/telegram/StarManager.cpp td/telegram/StateManager.cpp td/telegram/StatisticsManager.cpp td/telegram/StickerFormat.cpp @@ -753,6 +754,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/MessageContentType.h td/telegram/MessageCopyOptions.h td/telegram/MessageDb.h + td/telegram/MessageEffectId.h td/telegram/MessageEntity.h td/telegram/MessageExtendedMedia.h td/telegram/MessageForwardInfo.h @@ -880,6 +882,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/SharedDialog.h td/telegram/SpecialStickerSetType.h td/telegram/SponsoredMessageManager.h + td/telegram/StarManager.h td/telegram/StateManager.h td/telegram/StatisticsManager.h td/telegram/StickerFormat.h diff --git a/README.md b/README.md index 589bfab288b9..006c4b5aecdd 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic) Or you could install `TDLib` and then reference it in your CMakeLists.txt like this: ``` -find_package(Td 1.8.30 REQUIRED) +find_package(Td 1.8.31 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/blob/master/example/cpp/CMakeLists.txt). diff --git a/SplitSource.php b/SplitSource.php index 1f41df6cd70b..1806926cff49 100644 --- a/SplitSource.php +++ b/SplitSource.php @@ -369,6 +369,7 @@ function ($matches) use ($needed_std_headers) { 'link_manager[_(-](?![.]get[(][)])|LinkManager' => 'LinkManager', 'LogeventIdWithGeneration|add_log_event|delete_log_event|get_erase_log_event_promise|parse_time|store_time' => 'logevent/LogEventHelper', 'MessageCopyOptions' => 'MessageCopyOptions', + 'MessageEffectId' => 'MessageEffectId', 'MessageForwardInfo|LastForwardedMessageInfo|forward_info' => 'MessageForwardInfo', 'MessageFullId' => 'MessageFullId', 'MessageId' => 'MessageId', @@ -407,6 +408,7 @@ function ($matches) use ($needed_std_headers) { 'SentEmailCode' => 'SentEmailCode', 'SharedDialog' => 'SharedDialog', 'sponsored_message_manager[_(-](?![.]get[(][)])|SponsoredMessageManager' => 'SponsoredMessageManager', + 'star_manager[_(-](?![.]get[(][)])|StarManager' => 'StarManager', 'state_manager[_(-](?![.]get[(][)])|StateManager' => 'StateManager', 'statistics_manager[_(-](?![.]get[(][)])|StatisticsManager' => 'StatisticsManager', 'StickerSetId' => 'StickerSetId', diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index 114528616c2f..313ddfa5231e 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.8.30 REQUIRED) +find_package(Td 1.8.31 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/java/org/drinkless/tdlib/Client.java b/example/java/org/drinkless/tdlib/Client.java index 8783135437a3..5c61b1c6be2c 100644 --- a/example/java/org/drinkless/tdlib/Client.java +++ b/example/java/org/drinkless/tdlib/Client.java @@ -132,8 +132,8 @@ public static T execute(TdApi.Function query) throws * Creates new Client. * * @param updateHandler Handler for incoming updates. - * @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be iggnored. - * @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be iggnored. + * @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be ignored. + * @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be ignored. * @return created Client */ public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) { diff --git a/example/uwp/Telegram.Td.UWP.nuspec b/example/uwp/Telegram.Td.UWP.nuspec index f67e3662ced8..eea22e632d1d 100644 --- a/example/uwp/Telegram.Td.UWP.nuspec +++ b/example/uwp/Telegram.Td.UWP.nuspec @@ -2,7 +2,7 @@ Telegram.Td.UWP - 1.8.30 + 1.8.31 TDLib for Universal Windows Platform Telegram Telegram diff --git a/example/uwp/extension.vsixmanifest b/example/uwp/extension.vsixmanifest index 494cc9f85272..e925782da363 100644 --- a/example/uwp/extension.vsixmanifest +++ b/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index e1c2ff6b214c..03b4a745efee 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -832,7 +832,7 @@ premiumGiftCodeInfo creator_id:MessageSender creation_date:int32 is_from_giveawa //@description Describes an option for buying Telegram stars //@currency ISO 4217 currency code for the payment //@amount The amount to pay, in the smallest units of the currency -//@star_count Number of stars that will be purchased +//@star_count Number of Telegram stars that will be purchased //@store_product_id Identifier of the store product associated with the option; may be empty if none //@is_additional True, if the option must be shown only in the full list of payment options starPaymentOption currency:string amount:int53 star_count:int53 store_product_id:string is_additional:Bool = StarPaymentOption; @@ -850,25 +850,28 @@ starTransactionDirectionIncoming = StarTransactionDirection; starTransactionDirectionOutgoing = StarTransactionDirection; -//@class StarTransactionSource @description Describes source or recipient of a transaction with Telegram stars +//@class StarTransactionPartner @description Describes source or recipient of a transaction with Telegram stars //@description The transaction is a transaction with Telegram through a bot -starTransactionSourceTelegram = StarTransactionSource; +starTransactionPartnerTelegram = StarTransactionPartner; //@description The transaction is a transaction with App Store -starTransactionSourceAppStore = StarTransactionSource; +starTransactionPartnerAppStore = StarTransactionPartner; //@description The transaction is a transaction with Google Play -starTransactionSourceGooglePlay = StarTransactionSource; +starTransactionPartnerGooglePlay = StarTransactionPartner; -//@description The transaction is a transaction with Fragment -starTransactionSourceFragment = StarTransactionSource; +//@description The transaction is a transaction with Fragment @withdrawal_state State of the withdrawal; may be null for refunds from Fragment +starTransactionPartnerFragment withdrawal_state:RevenueWithdrawalState = StarTransactionPartner; //@description The transaction is a transaction with another user @user_id Identifier of the user @product_info Information about the bought product; may be null if none -starTransactionSourceUser user_id:int53 product_info:productInfo = StarTransactionSource; +starTransactionPartnerUser user_id:int53 product_info:productInfo = StarTransactionPartner; -//@description The transaction is a transaction with unknown source -starTransactionSourceUnsupported = StarTransactionSource; +//@description The transaction is a transaction with a channel chat @chat_id Identifier of the chat +starTransactionPartnerChannel chat_id:int53 = StarTransactionPartner; + +//@description The transaction is a transaction with unknown partner +starTransactionPartnerUnsupported = StarTransactionPartner; //@description Represents a transaction changing the amount of owned Telegram stars @@ -876,8 +879,8 @@ starTransactionSourceUnsupported = StarTransactionSource; //@star_count The amount of added owned Telegram stars; negative for outgoing transactions //@is_refund True, if the transaction is a refund of a previous transaction //@date Point in time (Unix timestamp) when the transaction was completed -//@source Source of the transaction, or its recipient for outgoing transactions -starTransaction id:string star_count:int53 is_refund:Bool date:int32 source:StarTransactionSource = StarTransaction; +//@partner Source of the incoming transaction, or its recipient for outgoing transactions +starTransaction id:string star_count:int53 is_refund:Bool date:int32 partner:StarTransactionPartner = StarTransaction; //@description Represents a list of Telegram star transactions //@star_count The amount of owned Telegram stars @@ -1048,14 +1051,14 @@ chatAdministrators administrators:vector = ChatAdministrators //@class ChatMemberStatus @description Provides information about the status of a member in a chat //@description The user is the owner of the chat and has all the administrator privileges -//@custom_title A custom title of the owner; 0-16 characters without emojis; applicable to supergroups only +//@custom_title A custom title of the owner; 0-16 characters without emoji; applicable to supergroups only //@is_anonymous True, if the creator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only //@is_member True, if the user is a member of the chat chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = ChatMemberStatus; //@description The user is a member of the chat and has some additional privileges. In basic groups, administrators can edit and delete messages sent by others, add new members, ban unprivileged members, and manage video chats. //-In supergroups and channels, there are more detailed options for administrator privileges -//@custom_title A custom title of the administrator; 0-16 characters without emojis; applicable to supergroups only +//@custom_title A custom title of the administrator; 0-16 characters without emoji; applicable to supergroups only //@can_be_edited True, if the current user can edit the administrator privileges for the called user //@rights Rights of the administrator chatMemberStatusAdministrator custom_title:string can_be_edited:Bool rights:chatAdministratorRights = ChatMemberStatus; @@ -1520,11 +1523,16 @@ messageReplyToStory story_sender_chat_id:int53 story_id:int32 = MessageReplyTo; //@class InputMessageReplyTo @description Contains information about the message or the story to be replied -//@description Describes a message to be replied -//@chat_id The identifier of the chat to which the message to be replied belongs; pass 0 if the message to be replied is in the same chat. Must always be 0 for replies in secret chats. A message can be replied in another chat or topic only if message.can_be_replied_in_another_chat -//@message_id The identifier of the message to be replied in the same or the specified chat +//@description Describes a message to be replied in the same chat and forum topic +//@message_id The identifier of the message to be replied in the same chat and forum topic //@quote Quote from the message to be replied; pass null if none. Must always be null for replies in secret chats -inputMessageReplyToMessage chat_id:int53 message_id:int53 quote:inputTextQuote = InputMessageReplyTo; +inputMessageReplyToMessage message_id:int53 quote:inputTextQuote = InputMessageReplyTo; + +//@description Describes a message to be replied that is from a different chat or a forum topic; not supported in secret chats +//@chat_id The identifier of the chat to which the message to be replied belongs +//@message_id The identifier of the message to be replied in the specified chat. A message can be replied in another chat or topic only if message.can_be_replied_in_another_chat +//@quote Quote from the message to be replied; pass null if none +inputMessageReplyToExternalMessage chat_id:int53 message_id:int53 quote:inputTextQuote = InputMessageReplyTo; //@description Describes a story to be replied //@story_sender_chat_id The identifier of the sender of the story. Currently, stories can be replied only in the sender's chat and channel stories can't be replied @@ -1783,7 +1791,8 @@ reactionNotificationSettings message_reaction_source:ReactionNotificationSource //@reply_to Information about the message to be replied; must be of the type inputMessageReplyToMessage; may be null if none //@date Point in time (Unix timestamp) when the draft was created //@input_message_text Content of the message draft; must be of the type inputMessageText, inputMessageVideoNote, or inputMessageVoiceNote -draftMessage reply_to:InputMessageReplyTo date:int32 input_message_text:InputMessageContent = DraftMessage; +//@effect_id Identifier of the effect to apply to the message when it is sent; 0 if none +draftMessage reply_to:InputMessageReplyTo date:int32 input_message_text:InputMessageContent effect_id:int64 = DraftMessage; //@class ChatType @description Describes the type of chat @@ -2606,6 +2615,13 @@ bankCardInfo title:string actions:vector = BankCardInfo; //@postal_code Address postal code address country_code:string state:string city:string street_line1:string street_line2:string postal_code:string = Address; +//@description Describes an address of a location +//@country_code A two-letter ISO 3166-1 alpha-2 country code +//@state State, if applicable; empty if unknown +//@city City; empty if unknown +//@street The address; empty if unknown +locationAddress country_code:string state:string city:string street:string = LocationAddress; + //@description Contains parameters of the application theme //@background_color A color of the background in the RGB24 format @@ -2709,7 +2725,7 @@ paymentOption title:string url:string = PaymentOption; //@need_password True, if the user will be able to save credentials, if sets up a 2-step verification password paymentFormTypeRegular invoice:invoice payment_provider_user_id:int53 payment_provider:PaymentProvider additional_payment_options:vector saved_order_info:orderInfo saved_credentials:vector can_save_credentials:Bool need_password:Bool = PaymentFormType; -//@description The payment form is for a payment in Telegram stars @star_count Number of stars that will be paid +//@description The payment form is for a payment in Telegram stars @star_count Number of Telegram stars that will be paid paymentFormTypeStars star_count:int53 = PaymentFormType; @@ -2739,7 +2755,7 @@ paymentResult success:Bool verification_url:string = PaymentResult; paymentReceiptTypeRegular payment_provider_user_id:int53 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string tip_amount:int53 = PaymentReceiptType; //@description The payment was done using Telegram stars -//@star_count Number of stars that were paid +//@star_count Number of Telegram stars that were paid //@transaction_id Unique identifier of the transaction that can be used to dispute it paymentReceiptTypeStars star_count:int53 transaction_id:string = PaymentReceiptType; @@ -3483,7 +3499,7 @@ messageSelfDestructTypeImmediately = MessageSelfDestructType; //@protect_content Pass true if the content of the message must be protected from forwarding and saving; for bots only //@update_order_of_installed_sticker_sets Pass true if the user explicitly chosen a sticker or a custom emoji from an installed sticker set; applicable only to sendMessage and sendMessageAlbum //@scheduling_state Message scheduling state; pass null to send message immediately. Messages sent to a secret chat, live location messages and self-destructing messages can't be scheduled -//@effect_id Identifier of the effect to apply to the message; applicable only to sendMessage and sendMessageAlbum in private chats +//@effect_id Identifier of the effect to apply to the message; pass 0 if none; applicable only to sendMessage and sendMessageAlbum in private chats //@sending_id Non-persistent identifier, which will be returned back in messageSendingStatePending object and can be used to match sent messages and corresponding updateNewMessage updates //@only_preview Pass true to get a fake message instead of actually sending them messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState effect_id:int64 sending_id:int32 only_preview:Bool = MessageSendOptions; @@ -3766,13 +3782,13 @@ userStatusLastMonth by_my_privacy_settings:Bool = UserStatus; //@description Represents an emoji with its keyword @emoji The emoji @keyword The keyword emojiKeyword emoji:string keyword:string = EmojiKeyword; -//@description Represents a list of emoji with their keywords @emoji_keywords List of emoji with their keywords +//@description Represents a list of emojis with their keywords @emoji_keywords List of emojis with their keywords emojiKeywords emoji_keywords:vector = EmojiKeywords; //@description Represents a list of stickers @stickers List of stickers stickers stickers:vector = Stickers; -//@description Represents a list of emoji @emojis List of emojis +//@description Represents a list of emojis @emojis List of emojis emojis emojis:vector = Emojis; //@description Represents a sticker set @@ -3790,7 +3806,7 @@ emojis emojis:vector = Emojis; //@is_allowed_as_chat_emoji_status True, if stickers in the sticker set are custom emoji that can be used as chat emoji status; for custom emoji sticker sets only //@is_viewed True for already viewed trending sticker sets //@stickers List of stickers in this set -//@emojis A list of emoji corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object +//@emojis A list of emojis corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_owned:Bool is_installed:Bool is_archived:Bool is_official:Bool sticker_type:StickerType needs_repainting:Bool is_allowed_as_chat_emoji_status:Bool is_viewed:Bool stickers:vector emojis:vector = StickerSet; //@description Represents short information about a sticker set @@ -3822,7 +3838,7 @@ trendingStickerSets total_count:int32 sets:vector is_premium:Boo //@description The category contains a list of similar emoji to search for in getStickers and searchStickers for stickers, //-or getInlineQueryResults with the bot getOption("animation_search_bot_username") for animations -//@emojis List of emojis for search for +//@emojis List of emojis to search for emojiCategorySourceSearch emojis:vector = EmojiCategorySource; //@description The category contains premium stickers that must be found by getPremiumStickers @@ -3861,13 +3877,14 @@ emojiCategoryTypeChatPhoto = EmojiCategoryType; //@width_percentage The width of the rectangle, as a percentage of the media width //@height_percentage The height of the rectangle, as a percentage of the media height //@rotation_angle Clockwise rotation angle of the rectangle, in degrees; 0-360 -storyAreaPosition x_percentage:double y_percentage:double width_percentage:double height_percentage:double rotation_angle:double = StoryAreaPosition; +//@corner_radius_percentage The radius of the rectangle corner rounding, as a percentage of the media width +storyAreaPosition x_percentage:double y_percentage:double width_percentage:double height_percentage:double rotation_angle:double corner_radius_percentage:double = StoryAreaPosition; //@class StoryAreaType @description Describes type of clickable rectangle area on a story media -//@description An area pointing to a location @location The location -storyAreaTypeLocation location:location = StoryAreaType; +//@description An area pointing to a location @location The location @address Address of the location; may be null if unknown +storyAreaTypeLocation location:location address:locationAddress = StoryAreaType; //@description An area pointing to a venue @venue Information about the venue storyAreaTypeVenue venue:venue = StoryAreaType; @@ -3882,6 +3899,9 @@ storyAreaTypeSuggestedReaction reaction_type:ReactionType total_count:int32 is_d //@description An area pointing to a message @chat_id Identifier of the chat with the message @message_id Identifier of the message storyAreaTypeMessage chat_id:int53 message_id:int53 = StoryAreaType; +//@description An area pointing to a HTTP or tg:// link @url HTTP or tg:// URL to be opened when the area is clicked +storyAreaTypeLink url:string = StoryAreaType; + //@description Describes a clickable rectangle area on a story media @position Position of the area @type Type of the area storyArea position:storyAreaPosition type:StoryAreaType = StoryArea; @@ -3889,8 +3909,8 @@ storyArea position:storyAreaPosition type:StoryAreaType = StoryArea; //@class InputStoryAreaType @description Describes type of clickable rectangle area on a story media to be added -//@description An area pointing to a location @location The location -inputStoryAreaTypeLocation location:location = InputStoryAreaType; +//@description An area pointing to a location @location The location @address Address of the location; pass null if unknown +inputStoryAreaTypeLocation location:location address:locationAddress = InputStoryAreaType; //@description An area pointing to a venue found by the bot getOption("venue_search_bot_username") //@query_id Identifier of the inline query, used to found the venue @@ -3913,13 +3933,19 @@ inputStoryAreaTypeSuggestedReaction reaction_type:ReactionType is_dark:Bool is_f //@message_id Identifier of the message. Only successfully sent non-scheduled messages can be specified inputStoryAreaTypeMessage chat_id:int53 message_id:int53 = InputStoryAreaType; +//@description An area pointing to a HTTP or tg:// link +//@url HTTP or tg:// URL to be opened when the area is clicked +inputStoryAreaTypeLink url:string = InputStoryAreaType; + //@description Describes a clickable rectangle area on a story media to be added @position Position of the area @type Type of the area inputStoryArea position:storyAreaPosition type:InputStoryAreaType = InputStoryArea; //@description Contains a list of story areas to be added @areas List of input story areas. Currently, a story can have //-up to 10 inputStoryAreaTypeLocation, inputStoryAreaTypeFoundVenue, and inputStoryAreaTypePreviousVenue areas, -//-up to getOption("story_suggested_reaction_area_count_max") inputStoryAreaTypeSuggestedReaction areas, and up to 1 inputStoryAreaTypeMessage area +//-up to getOption("story_suggested_reaction_area_count_max") inputStoryAreaTypeSuggestedReaction areas, +//-up to 1 inputStoryAreaTypeMessage area, and +//-up to getOption("story_link_area_count_max") inputStoryAreaTypeLink areas if the current user is a Telegram Premium user inputStoryAreas areas:vector = InputStoryAreas; @@ -4026,6 +4052,9 @@ story id:int32 sender_chat_id:int53 sender_id:MessageSender date:int32 is_being_ //@pinned_story_ids Identifiers of the pinned stories; returned only in getChatPostedToChatPageStories with from_story_id == 0 stories total_count:int32 stories:vector pinned_story_ids:vector = Stories; +//@description Contains a list of stories found by a search @total_count Approximate total number of stories found @stories List of stories @next_offset The offset for the next request. If empty, then there are no more results +foundStories total_count:int32 stories:vector next_offset:string = FoundStories; + //@description Contains identifier of a story along with identifier of its sender //@sender_chat_id Identifier of the chat that posted the story //@story_id Unique story identifier among stories of the given sender @@ -4286,7 +4315,7 @@ callStateExchangingKeys = CallState; //@servers List of available call servers //@config A JSON-encoded call config //@encryption_key Call encryption key -//@emojis Encryption key emojis fingerprint +//@emojis Encryption key fingerprint represented as 4 emoji //@allow_p2p True, if peer-to-peer connection is allowed by users privacy settings //@custom_parameters Custom JSON-encoded call parameters to be passed to tgcalls callStateReady protocol:callProtocol servers:vector config:string encryption_key:bytes emojis:vector allow_p2p:Bool custom_parameters:string = CallState; @@ -5271,7 +5300,7 @@ premiumStoryFeatureCustomExpirationDuration = PremiumStoryFeature; //@description The ability to save other's unprotected stories premiumStoryFeatureSaveStories = PremiumStoryFeature; -//@description The ability to use links and formatting in story caption +//@description The ability to use links and formatting in story caption, and use inputStoryAreaTypeLink areas premiumStoryFeatureLinksAndFormatting = PremiumStoryFeature; //@description The ability to choose better quality for viewed stories @@ -6840,18 +6869,18 @@ messageStatistics message_interaction_graph:StatisticalGraph message_reaction_gr storyStatistics story_interaction_graph:StatisticalGraph story_reaction_graph:StatisticalGraph = StoryStatistics; -//@class ChatRevenueWithdrawalState @description Describes state of a chat revenue withdrawal +//@class RevenueWithdrawalState @description Describes state of a revenue withdrawal //@description Withdrawal is pending -chatRevenueWithdrawalStatePending = ChatRevenueWithdrawalState; +revenueWithdrawalStatePending = RevenueWithdrawalState; -//@description Withdrawal was completed +//@description Withdrawal succeeded //@date Point in time (Unix timestamp) when the withdrawal was completed //@url The URL where the withdrawal transaction can be viewed -chatRevenueWithdrawalStateCompleted date:int32 url:string = ChatRevenueWithdrawalState; +revenueWithdrawalStateSucceeded date:int32 url:string = RevenueWithdrawalState; -//@description Withdrawal has_failed -chatRevenueWithdrawalStateFailed = ChatRevenueWithdrawalState; +//@description Withdrawal failed +revenueWithdrawalStateFailed = RevenueWithdrawalState; //@class ChatRevenueTransactionType @description Describes type of transaction for revenue earned from sponsored messages in a chat @@ -6865,7 +6894,7 @@ chatRevenueTransactionTypeEarnings start_date:int32 end_date:int32 = ChatRevenue //@withdrawal_date Point in time (Unix timestamp) when the earnings withdrawal started //@provider Name of the payment provider //@state State of the withdrawal -chatRevenueTransactionTypeWithdrawal withdrawal_date:int32 provider:string state:ChatRevenueWithdrawalState = ChatRevenueTransactionType; +chatRevenueTransactionTypeWithdrawal withdrawal_date:int32 provider:string state:RevenueWithdrawalState = ChatRevenueTransactionType; //@description Describes a refund for failed withdrawal of earnings //@refund_date Point in time (Unix timestamp) when the transaction was refunded @@ -6882,6 +6911,21 @@ chatRevenueTransaction cryptocurrency:string cryptocurrency_amount:int64 type:Ch chatRevenueTransactions total_count:int32 transactions:vector = ChatRevenueTransactions; +//@description Contains information about Telegram stars earned by a bot or a chat +//@total_count Total number of the stars earned +//@current_count The number of Telegram stars that aren't withdrawn yet +//@available_count The number of Telegram stars that are available for withdrawal +//@withdrawal_enabled True, if Telegram stars can be withdrawn now or later +//@next_withdrawal_in Time left before the next withdrawal can be started, in seconds; 0 if withdrawal can be started now +starRevenueStatus total_count:int53 current_count:int53 available_count:int53 withdrawal_enabled:Bool next_withdrawal_in:int32 = StarRevenueStatus; + +//@description A detailed statistics about Telegram stars earned by a bot or a chat +//@revenue_by_day_graph A graph containing amount of revenue in a given day +//@status Telegram star revenue status +//@usd_rate Current conversion rate of a Telegram star to USD +starRevenueStatistics revenue_by_day_graph:StatisticalGraph status:starRevenueStatus usd_rate:double = StarRevenueStatistics; + + //@description A point on a Cartesian plane @x The point's first coordinate @y The point's second coordinate point x:double y:double = Point; @@ -7403,6 +7447,11 @@ updateOwnedStarCount star_count:int53 = Update; //@revenue_amount New amount of earned revenue updateChatRevenueAmount chat_id:int53 revenue_amount:chatRevenueAmount = Update; +//@description The Telegram star revenue earned by a bot or a chat has changed. If star transactions screen of the chat is opened, then getStarTransactions may be called to fetch new transactions +//@owner_id Identifier of the owner of the Telegram stars +//@status New Telegram star revenue status +updateStarRevenueStatus owner_id:MessageSender status:starRevenueStatus = Update; + //@description The parameters of speech recognition without Telegram Premium subscription has changed //@max_media_duration The maximum allowed duration of media for speech recognition without Telegram Premium subscription, in seconds //@weekly_count The total number of allowed speech recognitions per week; 0 if none @@ -7485,6 +7534,15 @@ updateNewCallbackQuery id:int64 sender_user_id:int53 chat_id:int53 message_id:in //@payload Query payload updateNewInlineCallbackQuery id:int64 sender_user_id:int53 inline_message_id:string chat_instance:int64 payload:CallbackQueryPayload = Update; +//@description A new incoming callback query from a business message; for bots only +//@id Unique query identifier +//@sender_user_id Identifier of the user who sent the query +//@connection_id Unique identifier of the business connection +//@message The message from the business account from which the query originated +//@chat_instance An identifier uniquely corresponding to the chat a message was sent to +//@payload Query payload +updateNewBusinessCallbackQuery id:int64 sender_user_id:int53 connection_id:string message:businessMessage chat_instance:int64 payload:CallbackQueryPayload = Update; + //@description A new incoming shipping query; for bots only. Only for invoices with flexible price //@id Unique query identifier //@sender_user_id Identifier of the user who sent the query @@ -8065,20 +8123,39 @@ searchCallMessages offset:string limit:int32 only_missed:Bool = FoundMessages; //@limit The maximum number of messages to be returned; up to 100 searchOutgoingDocumentMessages query:string limit:int32 = FoundMessages; -//@description Searches for public channel posts with the given hashtag or cashtag. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit -//@hashtag Hashtag or cashtag to search for +//@description Searches for public channel posts containing the given hashtag or cashtag. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit +//@tag Hashtag or cashtag to search for //@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results //@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit -searchPublicHashtagMessages hashtag:string offset:string limit:int32 = FoundMessages; +searchPublicMessagesByTag tag:string offset:string limit:int32 = FoundMessages; + +//@description Searches for public stories containing the given hashtag or cashtag. For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +//@tag Hashtag or cashtag to search for +//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results +//@limit The maximum number of stories to be returned; up to 100. For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +searchPublicStoriesByTag tag:string offset:string limit:int32 = FoundStories; + +//@description Searches for public stories by the given address location. For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +//@address Address of the location +//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results +//@limit The maximum number of stories to be returned; up to 100. For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +searchPublicStoriesByLocation address:locationAddress offset:string limit:int32 = FoundStories; + +//@description Searches for public stories from the given venue. For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +//@venue_provider Provider of the venue +//@venue_id Identifier of the venue in the provider database +//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results +//@limit The maximum number of stories to be returned; up to 100. For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +searchPublicStoriesByVenue venue_provider:string venue_id:string offset:string limit:int32 = FoundStories; -//@description Returns recently searched for hashtags or cashtags by their prefix @prefix Prefix of hashtags or cashtags to return @limit The maximum number of items to be returned -getSearchedForHashtags prefix:string limit:int32 = Hashtags; +//@description Returns recently searched for hashtags or cashtags by their prefix @tag_prefix Prefix of hashtags or cashtags to return @limit The maximum number of items to be returned +getSearchedForTags tag_prefix:string limit:int32 = Hashtags; -//@description Removes a hashtag or a cashtag from the list of recently searched for hashtags or cashtags @hashtag Hashtag or cashtag to delete -removeSearchedForHashtag hashtag:string = Ok; +//@description Removes a hashtag or a cashtag from the list of recently searched for hashtags or cashtags @tag Hashtag or cashtag to delete +removeSearchedForTag tag:string = Ok; -//@description Clears the list of recently searched for hashtags -clearSearchedForHashtags = Ok; +//@description Clears the list of recently searched for hashtags or cashtags @clear_cashtags Pass true to clear the list of recently searched for cashtags; otherwise, the list of recently searched for hashtags will be cleared +clearSearchedForTags clear_cashtags:Bool = Ok; //@description Deletes all call messages @revoke Pass true to delete the messages for all users deleteAllCallMessages revoke:Bool = Ok; @@ -8394,6 +8471,57 @@ sendBusinessMessage business_connection_id:string chat_id:int53 reply_to:InputMe //@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album. All messages must have the same value of show_caption_above_media sendBusinessMessageAlbum business_connection_id:string chat_id:int53 reply_to:InputMessageReplyTo disable_notification:Bool protect_content:Bool effect_id:int64 input_message_contents:vector = BusinessMessages; +//@description Edits the text of a text or game message sent on behalf of a business account; for bots only +//@business_connection_id Unique identifier of business connection on behalf of which the message was sent +//@chat_id The chat the message belongs to +//@message_id Identifier of the message +//@reply_markup The new message reply markup; pass null if none +//@input_message_content New text content of the message. Must be of type inputMessageText +editBusinessMessageText business_connection_id:string chat_id:int53 message_id:int53 reply_markup:ReplyMarkup input_message_content:InputMessageContent = BusinessMessage; + +//@description Edits the content of a live location in a message sent on behalf of a business account; for bots only +//@business_connection_id Unique identifier of business connection on behalf of which the message was sent +//@chat_id The chat the message belongs to +//@message_id Identifier of the message +//@reply_markup The new message reply markup; pass null if none +//@location New location content of the message; pass null to stop sharing the live location +//@live_period New time relative to the message send date, for which the location can be updated, in seconds. If 0x7FFFFFFF specified, then the location can be updated forever. +//-Otherwise, must not exceed the current live_period by more than a day, and the live location expiration date must remain in the next 90 days. Pass 0 to keep the current live_period +//@heading The new direction in which the location moves, in degrees; 1-360. Pass 0 if unknown +//@proximity_alert_radius The new maximum distance for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled +editBusinessMessageLiveLocation business_connection_id:string chat_id:int53 message_id:int53 reply_markup:ReplyMarkup location:location live_period:int32 heading:int32 proximity_alert_radius:int32 = BusinessMessage; + +//@description Edits the content of a message with an animation, an audio, a document, a photo or a video in a message sent on behalf of a business account; for bots only +//@business_connection_id Unique identifier of business connection on behalf of which the message was sent +//@chat_id The chat the message belongs to +//@message_id Identifier of the message +//@reply_markup The new message reply markup; pass null if none; for bots only +//@input_message_content New content of the message. Must be one of the following types: inputMessageAnimation, inputMessageAudio, inputMessageDocument, inputMessagePhoto or inputMessageVideo +editBusinessMessageMedia business_connection_id:string chat_id:int53 message_id:int53 reply_markup:ReplyMarkup input_message_content:InputMessageContent = BusinessMessage; + +//@description Edits the caption of a message sent on behalf of a business account; for bots only +//@business_connection_id Unique identifier of business connection on behalf of which the message was sent +//@chat_id The chat the message belongs to +//@message_id Identifier of the message +//@reply_markup The new message reply markup; pass null if none +//@caption New message content caption; pass null to remove caption; 0-getOption("message_caption_length_max") characters +//@show_caption_above_media Pass true to show the caption above the media; otherwise, caption will be shown below the media. Can be true only for animation, photo, and video messages +editBusinessMessageCaption business_connection_id:string chat_id:int53 message_id:int53 reply_markup:ReplyMarkup caption:formattedText show_caption_above_media:Bool = BusinessMessage; + +//@description Edits the reply markup of a message sent on behalf of a business account; for bots only +//@business_connection_id Unique identifier of business connection on behalf of which the message was sent +//@chat_id The chat the message belongs to +//@message_id Identifier of the message +//@reply_markup The new message reply markup; pass null if none +editBusinessMessageReplyMarkup business_connection_id:string chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = BusinessMessage; + +//@description Stops a poll sent on behalf of a business account; for bots only +//@business_connection_id Unique identifier of business connection on behalf of which the message with the poll was sent +//@chat_id The chat the message belongs to +//@message_id Identifier of the message containing the poll +//@reply_markup The new message reply markup; pass null if none +stopBusinessPoll business_connection_id:string chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = BusinessMessage; + //@description Checks validness of a name for a quick reply shortcut. Can be called synchronously @name The name of the shortcut; 1-32 characters checkQuickReplyShortcutName name:string = Ok; @@ -8456,7 +8584,7 @@ readdQuickReplyShortcutMessages shortcut_name:string message_ids:vector = editQuickReplyMessage shortcut_id:int32 message_id:int53 input_message_content:InputMessageContent = Ok; -//@description Returns the list of custom emojis, which can be used as forum topic icon by all users +//@description Returns the list of custom emoji, which can be used as forum topic icon by all users getForumTopicDefaultIcons = Stickers; //@description Creates a topic in a forum supergroup chat; requires can_manage_topics administrator or can_create_topics member right in the supergroup @@ -9869,7 +9997,7 @@ getUserProfilePhotos user_id:int53 offset:int32 limit:int32 = ChatPhotos; //@description Returns stickers from the installed sticker sets that correspond to any of the given emoji or can be found by sticker-specific keywords. If the query is non-empty, then favorite, recently used or trending stickers may also be returned //@sticker_type Type of the stickers to return -//@query Search query; a space-separated list of emoji or a keyword prefix. If empty, returns all known installed stickers +//@query Search query; a space-separated list of emojis or a keyword prefix. If empty, returns all known installed stickers //@limit The maximum number of stickers to be returned //@chat_id Chat identifier for which to return stickers. Available custom emoji stickers may be different for different chats getStickers sticker_type:StickerType query:string limit:int32 chat_id:int53 = Stickers; @@ -9883,7 +10011,7 @@ getAllStickerEmojis sticker_type:StickerType query:string chat_id:int53 return_o //@description Searches for stickers from public sticker sets that correspond to any of the given emoji //@sticker_type Type of the stickers to return -//@emojis Space-separated list of emoji to search for; must be non-empty +//@emojis Space-separated list of emojis to search for; must be non-empty //@limit The maximum number of stickers to be returned; 0-100 searchStickers sticker_type:StickerType emojis:string limit:int32 = Stickers; @@ -9973,7 +10101,7 @@ searchEmojis text:string input_language_codes:vector = EmojiKeywords; //@input_language_codes List of possible IETF language tags of the user's input language; may be empty if unknown getKeywordEmojis text:string input_language_codes:vector = Emojis; -//@description Returns available emojis categories @type Type of emoji categories to return; pass null to get default emoji categories +//@description Returns available emoji categories @type Type of emoji categories to return; pass null to get default emoji categories getEmojiCategories type:EmojiCategoryType = EmojiCategories; //@description Returns an animated emoji corresponding to a given emoji. Returns a 404 error if the emoji has no animated emoji @emoji The emoji @@ -10603,6 +10731,18 @@ getChatRevenueWithdrawalUrl chat_id:int53 password:string = HttpUrl; getChatRevenueTransactions chat_id:int53 offset:int32 limit:int32 = ChatRevenueTransactions; +//@description Returns detailed Telegram star revenue statistics +//@owner_id Identifier of the owner of the Telegram stars; can be identifier of an owned bot, or identifier of a channel chat with supergroupFullInfo.can_get_revenue_statistics == true +//@is_dark Pass true if a dark theme is used by the application +getStarRevenueStatistics owner_id:MessageSender is_dark:Bool = StarRevenueStatistics; + +//@description Returns URL for Telegram star withdrawal +//@owner_id Identifier of the owner of the Telegram stars; can be identifier of an owned bot, or identifier of a channel chat with supergroupFullInfo.can_get_revenue_statistics == true +//@star_count The number of Telegram stars to withdraw. Must be at least getOption("star_withdrawal_count_min") +//@password The 2-step verification password of the current user +getStarWithdrawalUrl owner_id:MessageSender star_count:int53 password:string = HttpUrl; + + //@description Returns detailed statistics about a chat. Currently, this method can be used only for supergroups and channels. Can be used only if supergroupFullInfo.can_get_statistics == true @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the application getChatStatistics chat_id:int53 is_dark:Bool = ChatStatistics; @@ -10797,7 +10937,7 @@ setStickerPositionInSet sticker:InputFile position:int32 = Ok; //@description Removes a sticker from the set to which it belongs. The sticker set must be owned by the current user @sticker Sticker to remove from the set removeStickerFromSet sticker:InputFile = Ok; -//@description Changes the list of emoji corresponding to a sticker. The sticker must belong to a regular or custom emoji sticker set that is owned by the current user +//@description Changes the list of emojis corresponding to a sticker. The sticker must belong to a regular or custom emoji sticker set that is owned by the current user //@sticker Sticker //@emojis New string with 1-20 emoji corresponding to the sticker setStickerEmojis sticker:InputFile emojis:string = Ok; @@ -10869,10 +11009,13 @@ getPremiumGiveawayInfo chat_id:int53 message_id:int53 = PremiumGiveawayInfo; //@description Returns available options for Telegram stars purchase getStarPaymentOptions = StarPaymentOptions; -//@description Returns the list of Telegram star transactions for the current user -//@offset Offset of the first transaction to return as received from the previous request; use empty string to get the first chunk of results +//@description Returns the list of Telegram star transactions for the specified owner +//@owner_id Identifier of the owner of the Telegram stars; can be the identifier of the current user, identifier of an owned bot, +//-or identifier of a channel chat with supergroupFullInfo.can_get_revenue_statistics == true //@direction Direction of the transactions to receive; pass null to get all transactions -getStarTransactions offset:string direction:StarTransactionDirection = StarTransactions; +//@offset Offset of the first transaction to return as received from the previous request; use empty string to get the first chunk of results +//@limit The maximum number of transactions to return +getStarTransactions owner_id:MessageSender direction:StarTransactionDirection offset:string limit:int32 = StarTransactions; //@description Checks whether an in-store purchase is possible. Must be called before any in-store purchase @purpose Transaction purpose canPurchaseFromStore purpose:StorePaymentPurpose = Ok; diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index decf43b43bfe..84ffbfac982c 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -439,6 +439,8 @@ updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages: updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; updateStarsBalance#fb85198 balance:long = Update; +updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long connection_id:string message:Message reply_to_message:flags.2?Message chat_instance:long data:flags.0?bytes = Update; +updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -816,7 +818,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#3fccf7ef flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int = DraftMessage; +draftMessage#2d65321f flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int effect:flags.7?long = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -1627,14 +1629,15 @@ exportedStoryLink#3fc9053b link:string = ExportedStoryLink; storiesStealthMode#712e27fd flags:# active_until_date:flags.0?int cooldown_until_date:flags.1?int = StoriesStealthMode; -mediaAreaCoordinates#3d1ea4e x:double y:double w:double h:double rotation:double = MediaAreaCoordinates; +mediaAreaCoordinates#cfc9e002 flags:# x:double y:double w:double h:double rotation:double radius:flags.0?double = MediaAreaCoordinates; mediaAreaVenue#be82db9c coordinates:MediaAreaCoordinates geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MediaArea; inputMediaAreaVenue#b282217f coordinates:MediaAreaCoordinates query_id:long result_id:string = MediaArea; -mediaAreaGeoPoint#df8b3b22 coordinates:MediaAreaCoordinates geo:GeoPoint = MediaArea; +mediaAreaGeoPoint#cad5452d flags:# coordinates:MediaAreaCoordinates geo:GeoPoint address:flags.0?GeoPointAddress = MediaArea; mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?true coordinates:MediaAreaCoordinates reaction:Reaction = MediaArea; mediaAreaChannelPost#770416af coordinates:MediaAreaCoordinates channel_id:long msg_id:int = MediaArea; inputMediaAreaChannelPost#2271f2bf coordinates:MediaAreaCoordinates channel:InputChannel msg_id:int = MediaArea; +mediaAreaUrl#37381085 coordinates:MediaAreaCoordinates url:string = MediaArea; peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector = PeerStories; @@ -1827,10 +1830,22 @@ starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#cc7079b2 flags:# refund:flags.3?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument = StarsTransaction; +starsTransaction#aa00c898 flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string = StarsTransaction; payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; +foundStory#e87acbc0 peer:Peer story:StoryItem = FoundStory; + +stories.foundStories#e2de7737 flags:# count:int stories:Vector next_offset:flags.0?string chats:Vector users:Vector = stories.FoundStories; + +geoPointAddress#de4c5d93 flags:# country_iso2:string state:flags.0?string city:flags.1?string street:flags.2?string = GeoPointAddress; + +starsRevenueStatus#79342946 flags:# withdrawal_enabled:flags.0?true current_balance:long available_balance:long overall_revenue:long next_withdrawal_at:flags.1?int = StarsRevenueStatus; + +payments.starsRevenueStats#c92bb73b revenue_graph:StatsGraph status:StarsRevenueStatus usd_rate:double = payments.StarsRevenueStats; + +payments.starsRevenueWithdrawalUrl#1dab80b7 url:string = payments.StarsRevenueWithdrawalUrl; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2074,7 +2089,7 @@ messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_me messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#7ff3b806 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia = Bool; +messages.saveDraft#d372c5ce flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia effect:flags.7?long = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -2373,9 +2388,11 @@ payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayI payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; payments.getStarsTopupOptions#c00ec7d3 = Vector; payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; -payments.getStarsTransactions#673ac2f9 flags:# inbound:flags.0?true outbound:flags.1?true peer:InputPeer offset:string = payments.StarsStatus; +payments.getStarsTransactions#97938d5a flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true peer:InputPeer offset:string limit:int = payments.StarsStatus; payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; +payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true peer:InputPeer = payments.StarsRevenueStats; +payments.getStarsRevenueWithdrawalUrl#13bbe8b3 peer:InputPeer stars:long password:InputCheckPasswordSRP = payments.StarsRevenueWithdrawalUrl; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -2477,6 +2494,7 @@ stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; stories.getStoryReactionsList#b9b2881f flags:# forwards_first:flags.2?true peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = stories.StoryReactionsList; stories.togglePinnedToTop#b297e9b peer:InputPeer id:Vector = Bool; +stories.searchPosts#6cea116a flags:# hashtag:flags.0?string area:flags.1?MediaArea offset:string limit:int = stories.FoundStories; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; diff --git a/td/telegram/BusinessConnectionManager.cpp b/td/telegram/BusinessConnectionManager.cpp index 5e9ae1eb9478..623728ab638c 100644 --- a/td/telegram/BusinessConnectionManager.cpp +++ b/td/telegram/BusinessConnectionManager.cpp @@ -14,6 +14,8 @@ #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" +#include "td/telegram/InputMessageText.h" +#include "td/telegram/Location.h" #include "td/telegram/MessageContent.h" #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageCopyOptions.h" @@ -22,6 +24,7 @@ #include "td/telegram/MessageQuote.h" #include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/OptionManager.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" @@ -105,13 +108,14 @@ struct BusinessConnectionManager::BusinessConnection { struct BusinessConnectionManager::PendingMessage { BusinessConnectionId business_connection_id_; DialogId dialog_id_; + MessageId message_id_; MessageInputReplyTo input_reply_to_; string send_emoji_; MessageSelfDestructType ttl_; unique_ptr content_; unique_ptr reply_markup_; int64 random_id_ = 0; - int64 effect_id_ = 0; + MessageEffectId effect_id_; bool noforwards_ = false; bool disable_notification_ = false; bool invert_media_ = false; @@ -140,7 +144,7 @@ class BusinessConnectionManager::SendBusinessMessageQuery final : public Td::Res if (message_->noforwards_) { flags |= telegram_api::messages_sendMessage::NOFORWARDS_MASK; } - if (message_->effect_id_) { + if (message_->effect_id_.is_valid()) { flags |= telegram_api::messages_sendMessage::EFFECT_MASK; } if (message_->invert_media_) { @@ -172,7 +176,7 @@ class BusinessConnectionManager::SendBusinessMessageQuery final : public Td::Res flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), message_text->text, message_->random_id_, get_input_reply_markup(td_->user_manager_.get(), message_->reply_markup_), - std::move(entities), 0, nullptr, nullptr, message_->effect_id_), + std::move(entities), 0, nullptr, nullptr, message_->effect_id_.get()), td_->business_connection_manager_->get_business_connection_dc_id(message_->business_connection_id_), {{message_->dialog_id_}})); } @@ -214,7 +218,7 @@ class BusinessConnectionManager::SendBusinessMediaQuery final : public Td::Resul if (message_->noforwards_) { flags |= telegram_api::messages_sendMedia::NOFORWARDS_MASK; } - if (message_->effect_id_) { + if (message_->effect_id_.is_valid()) { flags |= telegram_api::messages_sendMedia::EFFECT_MASK; } if (message_->invert_media_) { @@ -246,7 +250,7 @@ class BusinessConnectionManager::SendBusinessMediaQuery final : public Td::Resul std::move(reply_to), std::move(input_media), message_text == nullptr ? string() : message_text->text, message_->random_id_, get_input_reply_markup(td_->user_manager_.get(), message_->reply_markup_), - std::move(entities), 0, nullptr, nullptr, message_->effect_id_), + std::move(entities), 0, nullptr, nullptr, message_->effect_id_.get()), td_->business_connection_manager_->get_business_connection_dc_id(message_->business_connection_id_), {{message_->dialog_id_}})); } @@ -289,7 +293,7 @@ class BusinessConnectionManager::SendBusinessMultiMediaQuery final : public Td:: if (messages_[0]->noforwards_) { flags |= telegram_api::messages_sendMultiMedia::NOFORWARDS_MASK; } - if (messages_[0]->effect_id_) { + if (messages_[0]->effect_id_.is_valid()) { flags |= telegram_api::messages_sendMultiMedia::EFFECT_MASK; } if (messages_[0]->invert_media_) { @@ -309,7 +313,7 @@ class BusinessConnectionManager::SendBusinessMultiMediaQuery final : public Td:: telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_single_media), - 0, nullptr, nullptr, messages_[0]->effect_id_), + 0, nullptr, nullptr, messages_[0]->effect_id_.get()), td_->business_connection_manager_->get_business_connection_dc_id(messages_[0]->business_connection_id_), {{messages_[0]->dialog_id_}})); } @@ -404,6 +408,117 @@ class BusinessConnectionManager::UploadBusinessMediaQuery final : public Td::Res } }; +class BusinessConnectionManager::EditBusinessMessageQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit EditBusinessMessageQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(int32 flags, BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + const string &text, vector> &&entities, + telegram_api::object_ptr &&input_media, bool invert_media, + telegram_api::object_ptr &&reply_markup) { + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Know); + CHECK(input_peer != nullptr); + + if (reply_markup != nullptr) { + flags |= telegram_api::messages_editMessage::REPLY_MARKUP_MASK; + } + if (!entities.empty()) { + flags |= telegram_api::messages_editMessage::ENTITIES_MASK; + } + if (!text.empty()) { + flags |= telegram_api::messages_editMessage::MESSAGE_MASK; + } + if (input_media != nullptr) { + flags |= telegram_api::messages_editMessage::MEDIA_MASK; + } + if (invert_media) { + flags |= telegram_api::messages_editMessage::INVERT_MEDIA_MASK; + } + + int32 server_message_id = message_id.get_server_message_id().get(); + send_query(G()->net_query_creator().create_with_prefix( + business_connection_id.get_invoke_prefix(), + telegram_api::messages_editMessage(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), + server_message_id, text, std::move(input_media), std::move(reply_markup), + std::move(entities), 0, 0), + td_->business_connection_manager_->get_business_connection_dc_id(business_connection_id), {{dialog_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for EditBusinessMessageQuery: " << to_string(ptr); + td_->business_connection_manager_->process_sent_business_message(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.code() != 403 && !(status.code() == 500 && G()->close_flag())) { + LOG(WARNING) << "Failed to edit business message with the error " << status.message(); + } else { + LOG(INFO) << "Receive error for EditBusinessMessageQuery: " << status; + } + promise_.set_error(std::move(status)); + } +}; + +class BusinessConnectionManager::StopBusinessPollQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit StopBusinessPollQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + unique_ptr &&reply_markup) { + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Know); + CHECK(input_peer != nullptr); + + int32 flags = telegram_api::messages_editMessage::MEDIA_MASK; + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), reply_markup); + if (input_reply_markup != nullptr) { + flags |= telegram_api::messages_editMessage::REPLY_MARKUP_MASK; + } + + auto poll = telegram_api::make_object( + 0, telegram_api::poll::CLOSED_MASK, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + telegram_api::make_object(string(), Auto()), Auto(), 0, 0); + auto input_media = telegram_api::make_object(0, std::move(poll), + vector(), string(), Auto()); + int32 server_message_id = message_id.get_server_message_id().get(); + send_query(G()->net_query_creator().create_with_prefix( + business_connection_id.get_invoke_prefix(), + telegram_api::messages_editMessage(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), + server_message_id, string(), std::move(input_media), + std::move(input_reply_markup), + vector>(), 0, 0), + td_->business_connection_manager_->get_business_connection_dc_id(business_connection_id), {{dialog_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for StopBusinessPollQuery: " << to_string(ptr); + td_->business_connection_manager_->process_sent_business_message(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class BusinessConnectionManager::UploadMediaCallback final : public FileManager::UploadCallback { public: void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { @@ -458,6 +573,7 @@ void BusinessConnectionManager::tear_down() { Status BusinessConnectionManager::check_business_connection(const BusinessConnectionId &connection_id, DialogId dialog_id) const { + CHECK(td_->auth_manager_->is_bot()); auto connection = business_connections_.get_pointer(connection_id); if (connection == nullptr) { return Status::Error(400, "Business connection not found"); @@ -472,6 +588,16 @@ Status BusinessConnectionManager::check_business_connection(const BusinessConnec return Status::OK(); } +Status BusinessConnectionManager::check_business_message_id(MessageId message_id) const { + if (!message_id.is_valid()) { + return Status::Error(400, "Invalid message identifier specified"); + } + if (!message_id.is_server()) { + return Status::Error(400, "Wrong message identifier specified"); + } + return Status::OK(); +} + DcId BusinessConnectionManager::get_business_connection_dc_id(const BusinessConnectionId &connection_id) const { if (connection_id.is_empty()) { return DcId::main(); @@ -638,11 +764,10 @@ MessageInputReplyTo BusinessConnectionManager::create_business_message_input_rep if (!message_id.is_valid() || !message_id.is_server()) { return {}; } - if (reply_to_message->chat_id_ != 0) { - return {}; - } return MessageInputReplyTo{message_id, DialogId(), MessageQuote(td_, std::move(reply_to_message->quote_))}; } + case td_api::inputMessageReplyToExternalMessage::ID: + return {}; default: UNREACHABLE(); return {}; @@ -662,7 +787,7 @@ Result BusinessConnectionManager::process_input_message_con unique_ptr BusinessConnectionManager::create_business_message_to_send( BusinessConnectionId business_connection_id, DialogId dialog_id, MessageInputReplyTo &&input_reply_to, - bool disable_notification, bool protect_content, int64 effect_id, unique_ptr &&reply_markup, + bool disable_notification, bool protect_content, MessageEffectId effect_id, unique_ptr &&reply_markup, InputMessageContent &&input_content) const { auto content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), input_content.content.get(), MessageContentDupType::Send, MessageCopyOptions()); @@ -685,7 +810,7 @@ unique_ptr BusinessConnectionManager: void BusinessConnectionManager::send_message(BusinessConnectionId business_connection_id, DialogId dialog_id, td_api::object_ptr &&reply_to, - bool disable_notification, bool protect_content, int64 effect_id, + bool disable_notification, bool protect_content, MessageEffectId effect_id, td_api::object_ptr &&reply_markup, td_api::object_ptr &&input_message_content, Promise> &&promise) { @@ -957,7 +1082,7 @@ void BusinessConnectionManager::complete_upload_media(unique_ptr void BusinessConnectionManager::send_message_album( BusinessConnectionId business_connection_id, DialogId dialog_id, td_api::object_ptr &&reply_to, bool disable_notification, bool protect_content, - int64 effect_id, vector> &&input_message_contents, + MessageEffectId effect_id, vector> &&input_message_contents, Promise> &&promise) { TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); @@ -1073,6 +1198,196 @@ void BusinessConnectionManager::process_sent_business_message_album( promise.set_value(std::move(messages)); } +void BusinessConnectionManager::edit_business_message_text( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_STATUS_PROMISE(promise, check_business_message_id(message_id)); + + if (input_message_content == nullptr) { + return promise.set_error(Status::Error(400, "Can't edit message without new content")); + } + int32 new_message_content_type = input_message_content->get_id(); + if (new_message_content_type != td_api::inputMessageText::ID) { + return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText")); + } + + TRY_RESULT_PROMISE( + promise, input_message_text, + process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot())); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); + + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), new_reply_markup); + int32 flags = 0; + if (input_message_text.disable_web_page_preview) { + flags |= telegram_api::messages_editMessage::NO_WEBPAGE_MASK; + } + td_->create_handler(std::move(promise)) + ->send(flags, business_connection_id, dialog_id, message_id, input_message_text.text.text, + get_input_message_entities(td_->user_manager_.get(), input_message_text.text.entities, + "edit_business_message_text"), + input_message_text.get_input_media_web_page(), input_message_text.show_above_text, + std::move(input_reply_markup)); +} + +void BusinessConnectionManager::edit_business_message_live_location( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, td_api::object_ptr &&input_location, + int32 live_period, int32 heading, int32 proximity_alert_radius, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_STATUS_PROMISE(promise, check_business_message_id(message_id)); + + Location location(input_location); + if (location.empty() && input_location != nullptr) { + return promise.set_error(Status::Error(400, "Invalid location specified")); + } + + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), new_reply_markup); + + int32 flags = 0; + if (location.empty()) { + flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK; + } + if (live_period != 0) { + flags |= telegram_api::inputMediaGeoLive::PERIOD_MASK; + } + if (heading != 0) { + flags |= telegram_api::inputMediaGeoLive::HEADING_MASK; + } + flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK; + auto input_media = telegram_api::make_object( + flags, false /*ignored*/, location.get_input_geo_point(), heading, live_period, proximity_alert_radius); + td_->create_handler(std::move(promise)) + ->send(0, business_connection_id, dialog_id, message_id, string(), + vector>(), std::move(input_media), false /*ignored*/, + std::move(input_reply_markup)); +} + +void BusinessConnectionManager::edit_business_message_media( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_STATUS_PROMISE(promise, check_business_message_id(message_id)); + + if (input_message_content == nullptr) { + return promise.set_error(Status::Error(400, "Can't edit message without new content")); + } + int32 new_message_content_type = input_message_content->get_id(); + if (new_message_content_type != td_api::inputMessageAnimation::ID && + new_message_content_type != td_api::inputMessageAudio::ID && + new_message_content_type != td_api::inputMessageDocument::ID && + new_message_content_type != td_api::inputMessagePhoto::ID && + new_message_content_type != td_api::inputMessageVideo::ID) { + return promise.set_error(Status::Error(400, "Unsupported input message content type")); + } + + bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); + TRY_RESULT_PROMISE(promise, content, + get_input_message_content(DialogId(), std::move(input_message_content), td_, is_premium)); + if (!content.ttl.is_empty()) { + return promise.set_error(Status::Error(400, "Can't enable self-destruction for media")); + } + + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); + + auto input_media = get_input_media(content.content.get(), td_, MessageSelfDestructType(), string(), true); + if (input_media != nullptr) { + auto file_id = get_message_content_any_file_id(content.content.get()); + CHECK(file_id.is_valid()); + FileView file_view = td_->file_manager_->get_file_view(file_id); + if (file_view.has_remote_location()) { + const FormattedText *caption = get_message_content_caption(content.content.get()); + td_->create_handler(std::move(promise)) + ->send(1 << 11, business_connection_id, dialog_id, message_id, caption == nullptr ? "" : caption->text, + get_input_message_entities(td_->user_manager_.get(), caption, "edit_business_message_media"), + std::move(input_media), content.invert_media, + get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); + return; + } + } + + auto message = create_business_message_to_send(business_connection_id, dialog_id, MessageInputReplyTo(), false, false, + MessageEffectId(), std::move(new_reply_markup), std::move(content)); + message->message_id_ = message_id; + + upload_media(std::move(message), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( + Result &&result) mutable { + send_closure(actor_id, &BusinessConnectionManager::do_edit_business_message_media, std::move(result), + std::move(promise)); + })); +} + +void BusinessConnectionManager::do_edit_business_message_media( + Result &&result, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + TRY_RESULT_PROMISE(promise, upload_result, std::move(result)); + CHECK(upload_result.input_media_ != nullptr); + + auto message = std::move(upload_result.message_); + CHECK(message != nullptr); + const FormattedText *caption = get_message_content_caption(message->content_.get()); + td_->create_handler(std::move(promise)) + ->send(1 << 11, message->business_connection_id_, message->dialog_id_, message->message_id_, + caption == nullptr ? "" : caption->text, + get_input_message_entities(td_->user_manager_.get(), caption, "do_edit_business_message_media"), + std::move(upload_result.input_media_), message->invert_media_, + get_input_reply_markup(td_->user_manager_.get(), message->reply_markup_)); +} + +void BusinessConnectionManager::edit_business_message_caption( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, td_api::object_ptr &&input_caption, + bool invert_media, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_STATUS_PROMISE(promise, check_business_message_id(message_id)); + TRY_RESULT_PROMISE(promise, caption, + get_formatted_text(td_, td_->dialog_manager_->get_my_dialog_id(), std::move(input_caption), + td_->auth_manager_->is_bot(), true, false, false)); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); + + td_->create_handler(std::move(promise)) + ->send(1 << 11, business_connection_id, dialog_id, message_id, caption.text, + get_input_message_entities(td_->user_manager_.get(), caption.entities, "edit_business_message_caption"), + nullptr, invert_media, get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); +} + +void BusinessConnectionManager::edit_business_message_reply_markup( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_STATUS_PROMISE(promise, check_business_message_id(message_id)); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); + + td_->create_handler(std::move(promise)) + ->send(0, business_connection_id, dialog_id, message_id, string(), + vector>(), nullptr, false /*ignored*/, + get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); +} + +void BusinessConnectionManager::stop_poll(BusinessConnectionId business_connection_id, DialogId dialog_id, + MessageId message_id, td_api::object_ptr &&reply_markup, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_STATUS_PROMISE(promise, check_business_message_id(message_id)); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); + + td_->create_handler(std::move(promise)) + ->send(business_connection_id, dialog_id, message_id, std::move(new_reply_markup)); +} + td_api::object_ptr BusinessConnectionManager::get_update_business_connection( const BusinessConnection *connection) const { return td_api::make_object(connection->get_business_connection_object(td_)); diff --git a/td/telegram/BusinessConnectionManager.h b/td/telegram/BusinessConnectionManager.h index 34d0fd079add..97207c848e4c 100644 --- a/td/telegram/BusinessConnectionManager.h +++ b/td/telegram/BusinessConnectionManager.h @@ -9,6 +9,8 @@ #include "td/telegram/BusinessConnectionId.h" #include "td/telegram/DialogId.h" #include "td/telegram/files/FileId.h" +#include "td/telegram/MessageEffectId.h" +#include "td/telegram/MessageId.h" #include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/net/DcId.h" #include "td/telegram/td_api.h" @@ -61,16 +63,46 @@ class BusinessConnectionManager final : public Actor { void send_message(BusinessConnectionId business_connection_id, DialogId dialog_id, td_api::object_ptr &&reply_to, bool disable_notification, - bool protect_content, int64 effect_id, td_api::object_ptr &&reply_markup, + bool protect_content, MessageEffectId effect_id, + td_api::object_ptr &&reply_markup, td_api::object_ptr &&input_message_content, Promise> &&promise); void send_message_album(BusinessConnectionId business_connection_id, DialogId dialog_id, td_api::object_ptr &&reply_to, bool disable_notification, - bool protect_content, int64 effect_id, + bool protect_content, MessageEffectId effect_id, vector> &&input_message_contents, Promise> &&promise); + void edit_business_message_text(BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise); + + void edit_business_message_live_location(BusinessConnectionId business_connection_id, DialogId dialog_id, + MessageId message_id, td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_location, int32 live_period, + int32 heading, int32 proximity_alert_radius, + Promise> &&promise); + + void edit_business_message_media(BusinessConnectionId business_connection_id, DialogId dialog_id, + MessageId message_id, td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise); + + void edit_business_message_caption(BusinessConnectionId business_connection_id, DialogId dialog_id, + MessageId message_id, td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_caption, bool invert_media, + Promise> &&promise); + + void edit_business_message_reply_markup(BusinessConnectionId business_connection_id, DialogId dialog_id, + MessageId message_id, td_api::object_ptr &&reply_markup, + Promise> &&promise); + + void stop_poll(BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, + td_api::object_ptr &&reply_markup, + Promise> &&promise); + void get_current_state(vector> &updates) const; private: @@ -82,6 +114,8 @@ class BusinessConnectionManager final : public Actor { class UploadBusinessMediaQuery; class UploadMediaCallback; class UploadThumbnailCallback; + class EditBusinessMessageQuery; + class StopBusinessPollQuery; struct UploadMediaResult { unique_ptr message_; @@ -102,6 +136,8 @@ class BusinessConnectionManager final : public Actor { void tear_down() final; + Status check_business_message_id(MessageId message_id) const; + void on_get_business_connection(const BusinessConnectionId &connection_id, Result> r_updates); @@ -114,7 +150,8 @@ class BusinessConnectionManager final : public Actor { unique_ptr create_business_message_to_send(BusinessConnectionId business_connection_id, DialogId dialog_id, MessageInputReplyTo &&input_reply_to, bool disable_notification, bool protect_content, - int64 effect_id, unique_ptr &&reply_markup, + MessageEffectId effect_id, + unique_ptr &&reply_markup, InputMessageContent &&input_content) const; void do_send_message(unique_ptr &&message, @@ -155,6 +192,9 @@ class BusinessConnectionManager final : public Actor { void process_sent_business_message_album(telegram_api::object_ptr &&updates_ptr, Promise> &&promise); + void do_edit_business_message_media(Result &&result, + Promise> &&promise); + td_api::object_ptr get_update_business_connection( const BusinessConnection *connection) const; diff --git a/td/telegram/CallbackQueriesManager.cpp b/td/telegram/CallbackQueriesManager.cpp index 661583f7d721..0aa4eb21b948 100644 --- a/td/telegram/CallbackQueriesManager.cpp +++ b/td/telegram/CallbackQueriesManager.cpp @@ -154,10 +154,10 @@ tl_object_ptr CallbackQueriesManager::get_query_pa } if (has_data) { - return make_tl_object(data.as_slice().str()); + return td_api::make_object(data.as_slice().str()); } if (has_game) { - return make_tl_object(game_short_name); + return td_api::make_object(game_short_name); } UNREACHABLE(); return nullptr; @@ -208,7 +208,7 @@ void CallbackQueriesManager::on_new_inline_query( } LOG_IF(ERROR, !td_->user_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; if (!td_->auth_manager_->is_bot()) { - LOG(ERROR) << "Receive new callback query"; + LOG(ERROR) << "Receive new inline callback query"; return; } CHECK(inline_message_id != nullptr); @@ -219,12 +219,40 @@ void CallbackQueriesManager::on_new_inline_query( } send_closure( G()->td(), &Td::send_update, - make_tl_object( + td_api::make_object( callback_query_id, td_->user_manager_->get_user_id_object(sender_user_id, "updateNewInlineCallbackQuery"), InlineQueriesManager::get_inline_message_id(std::move(inline_message_id)), chat_instance, std::move(payload))); } +void CallbackQueriesManager::on_new_business_query(int64 callback_query_id, UserId sender_user_id, + string &&connection_id, + telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message, + BufferSlice &&data, int64 chat_instance) { + if (!sender_user_id.is_valid()) { + LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id; + return; + } + LOG_IF(ERROR, !td_->user_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; + if (!td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Receive new business callback query"; + return; + } + auto message_object = + td_->messages_manager_->get_business_message_object(std::move(message), std::move(reply_to_message)); + if (message_object == nullptr) { + return; + } + + auto payload = td_api::make_object(data.as_slice().str()); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + callback_query_id, td_->user_manager_->get_user_id_object(sender_user_id, "updateNewInlineCallbackQuery"), + connection_id, std::move(message_object), chat_instance, std::move(payload))); +} + void CallbackQueriesManager::send_callback_query(MessageFullId message_full_id, tl_object_ptr &&payload, Promise> &&promise) { diff --git a/td/telegram/CallbackQueriesManager.h b/td/telegram/CallbackQueriesManager.h index 41a268072187..9deae9dae046 100644 --- a/td/telegram/CallbackQueriesManager.h +++ b/td/telegram/CallbackQueriesManager.h @@ -35,6 +35,11 @@ class CallbackQueriesManager { tl_object_ptr &&inline_message_id, BufferSlice &&data, int64 chat_instance, string &&game_short_name); + void on_new_business_query(int64 callback_query_id, UserId sender_user_id, string &&connection_id, + telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message, BufferSlice &&data, + int64 chat_instance); + void send_callback_query(MessageFullId message_full_id, tl_object_ptr &&payload, Promise> &&promise); diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index 85e8af650926..79f7a00b85b3 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -2002,8 +2002,15 @@ void ConfigManager::process_app_config(tl_object_ptr &c continue; } if (key == "factcheck_length_limit") { - G()->set_option_integer("fact_check_length_max", - get_json_value_int(std::move(key_value->value_), key)); + G()->set_option_integer("fact_check_length_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stars_revenue_withdrawal_min") { + G()->set_option_integer("star_withdrawal_count_min", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stories_area_url_max") { + G()->set_option_integer("story_link_area_count_max", get_json_value_int(std::move(key_value->value_), key)); continue; } diff --git a/td/telegram/ConfigManager.h b/td/telegram/ConfigManager.h index 0acfb299f509..8baa225a24d4 100644 --- a/td/telegram/ConfigManager.h +++ b/td/telegram/ConfigManager.h @@ -85,7 +85,7 @@ class ConfigManager final : public NetQueryCallback { private: struct AppConfig { - static constexpr int32 CURRENT_VERSION = 46; + static constexpr int32 CURRENT_VERSION = 48; int32 version_ = 0; int32 hash_ = 0; telegram_api::object_ptr config_; diff --git a/td/telegram/DraftMessage.cpp b/td/telegram/DraftMessage.cpp index 0bce1b514f5d..56d80a1f9957 100644 --- a/td/telegram/DraftMessage.cpp +++ b/td/telegram/DraftMessage.cpp @@ -64,12 +64,15 @@ class SaveDraftMessageQuery final : public Td::ResultHandler { if (media != nullptr) { flags |= telegram_api::messages_saveDraft::MEDIA_MASK; } + if (draft_message->message_effect_id_.is_valid()) { + flags |= telegram_api::messages_saveDraft::EFFECT_MASK; + } } send_query(G()->net_query_creator().create( telegram_api::messages_saveDraft( flags, false /*ignored*/, false /*ignored*/, std::move(input_reply_to), std::move(input_peer), draft_message == nullptr ? string() : draft_message->input_message_text_.text.text, - std::move(input_message_entities), std::move(media)), + std::move(input_message_entities), std::move(media), draft_message->message_effect_id_.get()), {{dialog_id}})); } @@ -381,7 +384,8 @@ bool DraftMessage::need_update_to(const DraftMessage &other, bool from_update) c if (is_local()) { return !from_update || other.is_local(); } - if (message_input_reply_to_ == other.message_input_reply_to_ && input_message_text_ == other.input_message_text_) { + if (message_input_reply_to_ == other.message_input_reply_to_ && input_message_text_ == other.input_message_text_ && + message_effect_id_ == other.message_effect_id_) { return date_ < other.date_; } else { return !from_update || date_ <= other.date_; @@ -401,7 +405,7 @@ td_api::object_ptr DraftMessage::get_draft_message_object( input_message_content = input_message_text_.get_input_message_text_object(); } return td_api::make_object(message_input_reply_to_.get_input_message_reply_to_object(td), date_, - std::move(input_message_content)); + std::move(input_message_content), message_effect_id_.get()); } DraftMessage::DraftMessage(Td *td, telegram_api::object_ptr &&draft_message) { @@ -428,6 +432,7 @@ DraftMessage::DraftMessage(Td *td, telegram_api::object_ptrno_webpage_, force_small_media, force_large_media, draft_message->invert_media_, false); + message_effect_id_ = MessageEffectId(draft_message->effect_); } Result> DraftMessage::get_draft_message( @@ -440,6 +445,7 @@ Result> DraftMessage::get_draft_message( auto result = make_unique(); result->message_input_reply_to_ = td->messages_manager_->create_message_input_reply_to( dialog_id, top_thread_message_id, std::move(draft_message->reply_to_), true); + result->message_effect_id_ = MessageEffectId(draft_message->effect_id_); auto input_message_content = std::move(draft_message->input_message_text_); if (input_message_content != nullptr) { diff --git a/td/telegram/DraftMessage.h b/td/telegram/DraftMessage.h index 1f7db7207437..02446c000e7b 100644 --- a/td/telegram/DraftMessage.h +++ b/td/telegram/DraftMessage.h @@ -10,6 +10,7 @@ #include "td/telegram/InputMessageText.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessageContentType.h" +#include "td/telegram/MessageEffectId.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/td_api.h" @@ -46,6 +47,7 @@ class DraftMessage { MessageInputReplyTo message_input_reply_to_; InputMessageText input_message_text_; unique_ptr local_content_; + MessageEffectId message_effect_id_; friend class SaveDraftMessageQuery; diff --git a/td/telegram/DraftMessage.hpp b/td/telegram/DraftMessage.hpp index 400731723469..c2d58da4f747 100644 --- a/td/telegram/DraftMessage.hpp +++ b/td/telegram/DraftMessage.hpp @@ -22,10 +22,12 @@ void DraftMessage::store(StorerT &storer) const { bool has_input_message_text = !input_message_text_.is_empty(); bool has_message_input_reply_to = !message_input_reply_to_.is_empty(); bool has_local_content = local_content_ != nullptr; + bool has_message_effect_id = message_effect_id_.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_input_message_text); STORE_FLAG(has_message_input_reply_to); STORE_FLAG(has_local_content); + STORE_FLAG(has_message_effect_id); END_STORE_FLAGS(); td::store(date_, storer); if (has_input_message_text) { @@ -37,6 +39,9 @@ void DraftMessage::store(StorerT &storer) const { if (has_local_content) { store_draft_message_content(local_content_.get(), storer); } + if (has_message_effect_id) { + td::store(message_effect_id_, storer); + } } template @@ -45,12 +50,14 @@ void DraftMessage::parse(ParserT &parser) { bool has_input_message_text; bool has_message_input_reply_to = false; bool has_local_content = false; + bool has_message_effect_id = false; if (parser.version() >= static_cast(Version::SupportRepliesInOtherChats)) { has_legacy_reply_to_message_id = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_input_message_text); PARSE_FLAG(has_message_input_reply_to); PARSE_FLAG(has_local_content); + PARSE_FLAG(has_message_effect_id); END_PARSE_FLAGS(); } else { has_legacy_reply_to_message_id = true; @@ -71,6 +78,9 @@ void DraftMessage::parse(ParserT &parser) { if (has_local_content) { parse_draft_message_content(local_content_, parser); } + if (has_message_effect_id) { + td::parse(message_effect_id_, parser); + } } } // namespace td diff --git a/td/telegram/InlineMessageManager.cpp b/td/telegram/InlineMessageManager.cpp index a16022c08370..8eb5851cff52 100644 --- a/td/telegram/InlineMessageManager.cpp +++ b/td/telegram/InlineMessageManager.cpp @@ -46,7 +46,7 @@ static int32 get_inline_message_dc_id( } } -static telegram_api::object_ptr get_input_bot_inline_message_id( +static telegram_api::object_ptr parse_input_bot_inline_message_id( const string &inline_message_id) { auto r_binary = base64url_decode(inline_message_id); if (r_binary.is_error()) { @@ -67,6 +67,15 @@ static telegram_api::object_ptr get_input return result; } +static Result> get_input_bot_inline_message_id( + const string &inline_message_id) { + auto result = parse_input_bot_inline_message_id(inline_message_id); + if (result == nullptr) { + return Status::Error(400, "Invalid inline message identifier specified"); + } + return std::move(result); +} + class EditInlineMessageQuery final : public Td::ResultHandler { Promise promise_; @@ -232,11 +241,7 @@ void InlineMessageManager::edit_inline_message_text( process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot())); TRY_RESULT_PROMISE(promise, new_reply_markup, get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); - - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_bot_inline_message_id), true, input_message_text.text.text, @@ -255,11 +260,7 @@ void InlineMessageManager::edit_inline_message_live_location(const string &inlin TRY_RESULT_PROMISE(promise, new_reply_markup, get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); - - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); Location location(input_location); if (location.empty() && input_location != nullptr) { @@ -311,11 +312,7 @@ void InlineMessageManager::edit_inline_message_media( TRY_RESULT_PROMISE(promise, new_reply_markup, get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); - - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); auto input_media = get_input_media(content.content.get(), td_, MessageSelfDestructType(), string(), true); if (input_media == nullptr) { @@ -341,11 +338,7 @@ void InlineMessageManager::edit_inline_message_caption(const string &inline_mess td_->auth_manager_->is_bot(), true, false, false)); TRY_RESULT_PROMISE(promise, new_reply_markup, get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); - - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_bot_inline_message_id), true, caption.text, @@ -360,11 +353,7 @@ void InlineMessageManager::edit_inline_message_reply_markup(const string &inline TRY_RESULT_PROMISE(promise, new_reply_markup, get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); - - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_bot_inline_message_id), false, string(), @@ -376,11 +365,7 @@ void InlineMessageManager::set_inline_game_score(const string &inline_message_id int32 score, bool force, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } - + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) @@ -391,11 +376,7 @@ void InlineMessageManager::get_inline_game_high_scores(const string &inline_mess Promise> &&promise) { CHECK(td_->auth_manager_->is_bot()); - auto input_bot_inline_message_id = get_input_bot_inline_message_id(inline_message_id); - if (input_bot_inline_message_id == nullptr) { - return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); - } - + TRY_RESULT_PROMISE(promise, input_bot_inline_message_id, get_input_bot_inline_message_id(inline_message_id)); TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) diff --git a/td/telegram/MediaArea.cpp b/td/telegram/MediaArea.cpp index 461384a8924c..07203192ef57 100644 --- a/td/telegram/MediaArea.cpp +++ b/td/telegram/MediaArea.cpp @@ -14,6 +14,7 @@ #include "td/telegram/InlineQueriesManager.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/misc.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" @@ -28,6 +29,12 @@ MediaArea::MediaArea(Td *td, telegram_api::object_ptr & auto area = telegram_api::move_object_as(media_area_ptr); coordinates_ = MediaAreaCoordinates(area->coordinates_); location_ = Location(td, area->geo_); + if (area->address_ != nullptr) { + address_.country_iso2_ = area->address_->country_iso2_; + address_.state_ = area->address_->state_; + address_.city_ = area->address_->city_; + address_.street_ = area->address_->street_; + } if (coordinates_.is_valid() && !location_.empty()) { type_ = Type::Location; } else { @@ -73,6 +80,17 @@ MediaArea::MediaArea(Td *td, telegram_api::object_ptr & } break; } + case telegram_api::mediaAreaUrl::ID: { + auto area = telegram_api::move_object_as(media_area_ptr); + coordinates_ = MediaAreaCoordinates(area->coordinates_); + if (coordinates_.is_valid()) { + type_ = Type::Url; + url_ = std::move(area->url_); + } else { + LOG(ERROR) << "Receive " << to_string(area); + } + break; + } case telegram_api::inputMediaAreaVenue::ID: LOG(ERROR) << "Receive " << to_string(media_area_ptr); break; @@ -97,6 +115,16 @@ MediaArea::MediaArea(Td *td, td_api::object_ptr &&input_ case td_api::inputStoryAreaTypeLocation::ID: { auto type = td_api::move_object_as(input_story_area->type_); location_ = Location(type->location_); + if (type->address_ != nullptr) { + address_.country_iso2_ = std::move(type->address_->country_code_); + address_.state_ = std::move(type->address_->state_); + address_.city_ = std::move(type->address_->city_); + address_.street_ = std::move(type->address_->street_); + if (!clean_input_string(address_.country_iso2_) || !clean_input_string(address_.state_) || + !clean_input_string(address_.city_) || !clean_input_string(address_.street_)) { + break; + } + } if (!location_.empty()) { type_ = Type::Location; } @@ -167,6 +195,15 @@ MediaArea::MediaArea(Td *td, td_api::object_ptr &&input_ } break; } + case td_api::inputStoryAreaTypeLink::ID: { + auto type = td_api::move_object_as(input_story_area->type_); + if (!clean_input_string(type->url_)) { + break; + } + url_ = std::move(type->url_); + type_ = Type::Url; + break; + } default: UNREACHABLE(); } @@ -182,7 +219,11 @@ td_api::object_ptr MediaArea::get_story_area_object( td_api::object_ptr type; switch (type_) { case Type::Location: - type = td_api::make_object(location_.get_location_object()); + type = td_api::make_object( + location_.get_location_object(), + address_.is_empty() ? nullptr + : td_api::make_object(address_.country_iso2_, address_.state_, + address_.city_, address_.street_)); break; case Type::Venue: type = td_api::make_object(venue_.get_venue_object()); @@ -203,6 +244,9 @@ td_api::object_ptr MediaArea::get_story_area_object( td->dialog_manager_->get_chat_id_object(message_full_id_.get_dialog_id(), "storyAreaTypeMessage"), message_full_id_.get_message_id().get()); break; + case Type::Url: + type = td_api::make_object(url_); + break; default: UNREACHABLE(); } @@ -212,9 +256,28 @@ td_api::object_ptr MediaArea::get_story_area_object( telegram_api::object_ptr MediaArea::get_input_media_area(const Td *td) const { CHECK(is_valid()); switch (type_) { - case Type::Location: - return telegram_api::make_object(coordinates_.get_input_media_area_coordinates(), - location_.get_fake_geo_point()); + case Type::Location: { + int32 flags = 0; + telegram_api::object_ptr address; + if (!address_.is_empty()) { + int32 address_flags = 0; + if (!address_.state_.empty()) { + address_flags |= telegram_api::geoPointAddress::STATE_MASK; + } + if (!address_.city_.empty()) { + address_flags |= telegram_api::geoPointAddress::CITY_MASK; + } + if (!address_.street_.empty()) { + address_flags |= telegram_api::geoPointAddress::STREET_MASK; + } + address = telegram_api::make_object( + address_flags, address_.country_iso2_, address_.state_, address_.city_, address_.street_); + flags |= telegram_api::mediaAreaGeoPoint::ADDRESS_MASK; + } + + return telegram_api::make_object( + flags, coordinates_.get_input_media_area_coordinates(), location_.get_fake_geo_point(), std::move(address)); + } case Type::Venue: if (input_query_id_ != 0) { return telegram_api::make_object( @@ -233,20 +296,23 @@ telegram_api::object_ptr MediaArea::get_input_media_are flags, false /*ignored*/, false /*ignored*/, coordinates_.get_input_media_area_coordinates(), reaction_type_.get_input_reaction()); } - case Type::Message: + case Type::Message: { + auto channel_id = message_full_id_.get_dialog_id().get_channel_id(); + auto server_message_id = message_full_id_.get_message_id().get_server_message_id(); if (!is_old_message_) { - auto input_channel = td->chat_manager_->get_input_channel(message_full_id_.get_dialog_id().get_channel_id()); + auto input_channel = td->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return nullptr; } return telegram_api::make_object( - coordinates_.get_input_media_area_coordinates(), std::move(input_channel), - message_full_id_.get_message_id().get_server_message_id().get()); + coordinates_.get_input_media_area_coordinates(), std::move(input_channel), server_message_id.get()); } return telegram_api::make_object( - coordinates_.get_input_media_area_coordinates(), - message_full_id_.get_message_id().get_server_message_id().get(), - message_full_id_.get_message_id().get_server_message_id().get()); + coordinates_.get_input_media_area_coordinates(), channel_id.get(), server_message_id.get()); + } + case Type::Url: + return telegram_api::make_object(coordinates_.get_input_media_area_coordinates(), + url_); default: UNREACHABLE(); return nullptr; diff --git a/td/telegram/MediaArea.h b/td/telegram/MediaArea.h index d1b9af61eb49..ad561775e3d5 100644 --- a/td/telegram/MediaArea.h +++ b/td/telegram/MediaArea.h @@ -25,15 +25,34 @@ class Dependencies; class Td; class MediaArea { - enum class Type : int32 { None, Location, Venue, Reaction, Message }; + struct GeoPointAddress { + string country_iso2_; + string state_; + string city_; + string street_; + + bool is_empty() const { + return country_iso2_.empty(); + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + enum class Type : int32 { None, Location, Venue, Reaction, Message, Url }; Type type_ = Type::None; MediaAreaCoordinates coordinates_; Location location_; + GeoPointAddress address_; Venue venue_; MessageFullId message_full_id_; int64 input_query_id_ = 0; string input_result_id_; ReactionType reaction_type_; + string url_; bool is_dark_ = false; bool is_flipped_ = false; bool is_old_message_ = false; diff --git a/td/telegram/MediaArea.hpp b/td/telegram/MediaArea.hpp index 250ac9c0df47..594b5efad7c3 100644 --- a/td/telegram/MediaArea.hpp +++ b/td/telegram/MediaArea.hpp @@ -13,15 +13,69 @@ namespace td { +template +void MediaArea::GeoPointAddress::store(StorerT &storer) const { + bool has_country_iso2 = !country_iso2_.empty(); + bool has_state = !state_.empty(); + bool has_city = !city_.empty(); + bool has_street = !street_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_country_iso2); + STORE_FLAG(has_state); + STORE_FLAG(has_city); + STORE_FLAG(has_street); + END_STORE_FLAGS(); + if (has_country_iso2) { + td::store(country_iso2_, storer); + } + if (has_state) { + td::store(state_, storer); + } + if (has_city) { + td::store(city_, storer); + } + if (has_street) { + td::store(street_, storer); + } +} + +template +void MediaArea::GeoPointAddress::parse(ParserT &parser) { + bool has_country_iso2; + bool has_state; + bool has_city; + bool has_street; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_country_iso2); + PARSE_FLAG(has_state); + PARSE_FLAG(has_city); + PARSE_FLAG(has_street); + END_PARSE_FLAGS(); + if (has_country_iso2) { + td::parse(country_iso2_, parser); + } + if (has_state) { + td::parse(state_, parser); + } + if (has_city) { + td::parse(city_, parser); + } + if (has_street) { + td::parse(street_, parser); + } +} + template void MediaArea::store(StorerT &storer) const { using td::store; bool has_input_query_id = input_query_id_ != 0; + bool has_address = !address_.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_input_query_id); STORE_FLAG(is_dark_); STORE_FLAG(is_flipped_); STORE_FLAG(is_old_message_); + STORE_FLAG(has_address); END_STORE_FLAGS(); store(type_, storer); store(coordinates_, storer); @@ -42,20 +96,28 @@ void MediaArea::store(StorerT &storer) const { case Type::Message: store(message_full_id_, storer); break; + case Type::Url: + store(url_, storer); + break; default: UNREACHABLE(); } + if (has_address) { + store(address_, storer); + } } template void MediaArea::parse(ParserT &parser) { using td::parse; bool has_input_query_id; + bool has_address; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_input_query_id); PARSE_FLAG(is_dark_); PARSE_FLAG(is_flipped_); PARSE_FLAG(is_old_message_); + PARSE_FLAG(has_address); END_PARSE_FLAGS(); parse(type_, parser); parse(coordinates_, parser); @@ -76,9 +138,15 @@ void MediaArea::parse(ParserT &parser) { case Type::Message: parse(message_full_id_, parser); break; + case Type::Url: + parse(url_, parser); + break; default: parser.set_error("Load invalid area type"); } + if (has_address) { + parse(address_, parser); + } } } // namespace td diff --git a/td/telegram/MediaAreaCoordinates.cpp b/td/telegram/MediaAreaCoordinates.cpp index 02cc77666a57..0376d7ed6687 100644 --- a/td/telegram/MediaAreaCoordinates.cpp +++ b/td/telegram/MediaAreaCoordinates.cpp @@ -19,7 +19,7 @@ static double fix_double(double &value, double min_value = 0.0, double max_value return clamp(value, min_value, max_value); } -void MediaAreaCoordinates::init(double x, double y, double width, double height, double rotation_angle) { +void MediaAreaCoordinates::init(double x, double y, double width, double height, double rotation_angle, double radius) { x_ = fix_double(x); y_ = fix_double(y); width_ = fix_double(width); @@ -28,6 +28,7 @@ void MediaAreaCoordinates::init(double x, double y, double width, double height, if (rotation_angle_ < 0) { rotation_angle_ += 360.0; } + radius_ = fix_double(radius); } MediaAreaCoordinates::MediaAreaCoordinates( @@ -35,7 +36,8 @@ MediaAreaCoordinates::MediaAreaCoordinates( if (coordinates == nullptr) { return; } - init(coordinates->x_, coordinates->y_, coordinates->w_, coordinates->h_, coordinates->rotation_); + init(coordinates->x_, coordinates->y_, coordinates->w_, coordinates->h_, coordinates->rotation_, + coordinates->radius_); } MediaAreaCoordinates::MediaAreaCoordinates(const td_api::object_ptr &position) { @@ -44,24 +46,29 @@ MediaAreaCoordinates::MediaAreaCoordinates(const td_api::object_ptrx_percentage_, position->y_percentage_, position->width_percentage_, position->height_percentage_, - position->rotation_angle_); + position->rotation_angle_, position->corner_radius_percentage_); } td_api::object_ptr MediaAreaCoordinates::get_story_area_position_object() const { CHECK(is_valid()); - return td_api::make_object(x_, y_, width_, height_, rotation_angle_); + return td_api::make_object(x_, y_, width_, height_, rotation_angle_, radius_); } telegram_api::object_ptr MediaAreaCoordinates::get_input_media_area_coordinates() const { CHECK(is_valid()); - return telegram_api::make_object(x_, y_, width_, height_, rotation_angle_); + int32 flags = 0; + if (radius_ > 0) { + flags |= telegram_api::mediaAreaCoordinates::RADIUS_MASK; + } + return telegram_api::make_object(flags, x_, y_, width_, height_, rotation_angle_, + radius_); } bool operator==(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs) { return std::abs(lhs.x_ - rhs.x_) < 1e-6 && std::abs(lhs.y_ - rhs.y_) < 1e-6 && std::abs(lhs.width_ - rhs.width_) < 1e-6 && std::abs(lhs.height_ - rhs.height_) < 1e-6 && - std::abs(lhs.rotation_angle_ - rhs.rotation_angle_) < 1e-6; + std::abs(lhs.rotation_angle_ - rhs.rotation_angle_) < 1e-6 && std::abs(lhs.radius_ - rhs.radius_) < 1e-6; } bool operator!=(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs) { @@ -71,7 +78,7 @@ bool operator!=(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs StringBuilder &operator<<(StringBuilder &string_builder, const MediaAreaCoordinates &coordinates) { return string_builder << "StoryAreaPosition[" << coordinates.x_ << ", " << coordinates.y_ << ", " << coordinates.width_ << ", " << coordinates.height_ << ", " << coordinates.rotation_angle_ - << ']'; + << ", " << coordinates.radius_ << ']'; } } // namespace td diff --git a/td/telegram/MediaAreaCoordinates.h b/td/telegram/MediaAreaCoordinates.h index da1d1c8032af..90a86e794cbb 100644 --- a/td/telegram/MediaAreaCoordinates.h +++ b/td/telegram/MediaAreaCoordinates.h @@ -20,13 +20,14 @@ class MediaAreaCoordinates { double width_ = 0.0; double height_ = 0.0; double rotation_angle_ = 0.0; + double radius_ = 0.0; friend bool operator==(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs); friend bool operator!=(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs); friend StringBuilder &operator<<(StringBuilder &string_builder, const MediaAreaCoordinates &coordinates); - void init(double x, double y, double width, double height, double rotation_angle); + void init(double x, double y, double width, double height, double rotation_angle, double radius); public: MediaAreaCoordinates() = default; diff --git a/td/telegram/MediaAreaCoordinates.hpp b/td/telegram/MediaAreaCoordinates.hpp index 6afcb57c2101..6565fc0167ab 100644 --- a/td/telegram/MediaAreaCoordinates.hpp +++ b/td/telegram/MediaAreaCoordinates.hpp @@ -15,31 +15,42 @@ namespace td { template void MediaAreaCoordinates::store(StorerT &storer) const { using td::store; + bool has_radius = radius_ > 0.0; BEGIN_STORE_FLAGS(); + STORE_FLAG(has_radius); END_STORE_FLAGS(); store(x_, storer); store(y_, storer); store(width_, storer); store(height_, storer); store(rotation_angle_, storer); + if (has_radius) { + store(radius_, storer); + } } template void MediaAreaCoordinates::parse(ParserT &parser) { using td::parse; + bool has_radius; BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_radius); END_PARSE_FLAGS(); double x; double y; double width; double height; double rotation_angle; + double radius = 0.0; parse(x, parser); parse(y, parser); parse(width, parser); parse(height, parser); parse(rotation_angle, parser); - init(x, y, width, height, rotation_angle); + if (has_radius) { + parse(radius, parser); + } + init(x, y, width, height, rotation_angle, radius); } } // namespace td diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index cc78b68d9d24..f8411fdee057 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -4456,6 +4456,10 @@ static void merge_location_access_hash(const Location &first, const Location &se } static bool need_message_text_changed_warning(const MessageText *old_content, const MessageText *new_content) { + const int32 MAX_CUSTOM_ENTITIES_COUNT = 100; // server-side limit + if (old_content->text.entities.size() > MAX_CUSTOM_ENTITIES_COUNT) { + return false; + } if (new_content->text.text == "Unsupported characters" || new_content->text.text == "This channel is blocked because it was used to spread pornographic content." || begins_with(new_content->text.text, @@ -4539,9 +4543,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo } } if (old_->text.entities != new_->text.entities) { - const int32 MAX_CUSTOM_ENTITIES_COUNT = 100; // server-side limit if (need_message_changed_warning && need_message_text_changed_warning(old_, new_) && - old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT && need_message_entities_changed_warning(old_->text.entities, new_->text.entities) && td->option_manager_->get_option_integer("session_count") <= 1) { LOG(WARNING) << "Entities have changed for a message in " << dialog_id << " from " @@ -5430,7 +5432,7 @@ void register_message_content(Td *td, const MessageContent *content, MessageFull if (text->web_page_id.is_valid()) { td->web_pages_manager_->register_web_page(text->web_page_id, message_full_id, source); } else if (can_be_animated_emoji(text->text)) { - td->stickers_manager_->register_emoji(text->text.text, get_custom_emoji_id(text->text), message_full_id, + td->stickers_manager_->register_emoji(text->text.text, get_custom_emoji_id(text->text), message_full_id, {}, source); } return; @@ -5446,7 +5448,7 @@ void register_message_content(Td *td, const MessageContent *content, MessageFull source); case MessageContentType::Dice: { auto dice = static_cast(content); - return td->stickers_manager_->register_dice(dice->emoji, dice->dice_value, message_full_id, source); + return td->stickers_manager_->register_dice(dice->emoji, dice->dice_value, message_full_id, {}, source); } case MessageContentType::GiftPremium: return td->stickers_manager_->register_premium_gift(static_cast(content)->months, @@ -5462,7 +5464,7 @@ void register_message_content(Td *td, const MessageContent *content, MessageFull static_cast(content)->photo); case MessageContentType::Story: return td->story_manager_->register_story(static_cast(content)->story_full_id, - message_full_id, source); + message_full_id, {}, source); default: return; } @@ -5557,7 +5559,7 @@ void unregister_message_content(Td *td, const MessageContent *content, MessageFu if (text->web_page_id.is_valid()) { td->web_pages_manager_->unregister_web_page(text->web_page_id, message_full_id, source); } else if (can_be_animated_emoji(text->text)) { - td->stickers_manager_->unregister_emoji(text->text.text, get_custom_emoji_id(text->text), message_full_id, + td->stickers_manager_->unregister_emoji(text->text.text, get_custom_emoji_id(text->text), message_full_id, {}, source); } return; @@ -5573,7 +5575,7 @@ void unregister_message_content(Td *td, const MessageContent *content, MessageFu source); case MessageContentType::Dice: { auto dice = static_cast(content); - return td->stickers_manager_->unregister_dice(dice->emoji, dice->dice_value, message_full_id, source); + return td->stickers_manager_->unregister_dice(dice->emoji, dice->dice_value, message_full_id, {}, source); } case MessageContentType::GiftPremium: return td->stickers_manager_->unregister_premium_gift(static_cast(content)->months, @@ -5586,7 +5588,7 @@ void unregister_message_content(Td *td, const MessageContent *content, MessageFu message_full_id, source); case MessageContentType::Story: return td->story_manager_->unregister_story(static_cast(content)->story_full_id, - message_full_id, source); + message_full_id, {}, source); default: return; } @@ -5610,6 +5612,56 @@ void unregister_reply_message_content(Td *td, const MessageContent *content) { } } +void register_quick_reply_message_content(Td *td, const MessageContent *content, + QuickReplyMessageFullId message_full_id, const char *source) { + switch (content->get_type()) { + case MessageContentType::Text: { + auto text = static_cast(content); + if (text->web_page_id.is_valid()) { + td->web_pages_manager_->register_quick_reply_web_page(text->web_page_id, message_full_id, source); + } else if (can_be_animated_emoji(text->text)) { + td->stickers_manager_->register_emoji(text->text.text, get_custom_emoji_id(text->text), {}, message_full_id, + source); + } + return; + } + case MessageContentType::Dice: { + auto dice = static_cast(content); + return td->stickers_manager_->register_dice(dice->emoji, dice->dice_value, {}, message_full_id, source); + } + case MessageContentType::Story: + return td->story_manager_->register_story(static_cast(content)->story_full_id, {}, + message_full_id, source); + default: + return; + } +} + +void unregister_quick_reply_message_content(Td *td, const MessageContent *content, + QuickReplyMessageFullId message_full_id, const char *source) { + switch (content->get_type()) { + case MessageContentType::Text: { + auto text = static_cast(content); + if (text->web_page_id.is_valid()) { + td->web_pages_manager_->unregister_quick_reply_web_page(text->web_page_id, message_full_id, source); + } else if (can_be_animated_emoji(text->text)) { + td->stickers_manager_->unregister_emoji(text->text.text, get_custom_emoji_id(text->text), {}, message_full_id, + source); + } + return; + } + case MessageContentType::Dice: { + auto dice = static_cast(content); + return td->stickers_manager_->unregister_dice(dice->emoji, dice->dice_value, {}, message_full_id, source); + } + case MessageContentType::Story: + return td->story_manager_->unregister_story(static_cast(content)->story_full_id, {}, + message_full_id, source); + default: + return; + } +} + template static tl_object_ptr secret_to_telegram(FromT &from); diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index b211ac6361e0..732fda455f01 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -20,6 +20,7 @@ #include "td/telegram/MessageId.h" #include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/Photo.h" +#include "td/telegram/QuickReplyMessageFullId.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/secret_api.h" #include "td/telegram/SecretInputMedia.h" @@ -217,6 +218,12 @@ void register_reply_message_content(Td *td, const MessageContent *content); void unregister_reply_message_content(Td *td, const MessageContent *content); +void register_quick_reply_message_content(Td *td, const MessageContent *content, + QuickReplyMessageFullId message_full_id, const char *source); + +void unregister_quick_reply_message_content(Td *td, const MessageContent *content, + QuickReplyMessageFullId message_full_id, const char *source); + unique_ptr get_secret_message_content( Td *td, string message_text, unique_ptr file, tl_object_ptr &&media_ptr, diff --git a/td/telegram/MessageEffectId.h b/td/telegram/MessageEffectId.h new file mode 100644 index 000000000000..f41017e80f77 --- /dev/null +++ b/td/telegram/MessageEffectId.h @@ -0,0 +1,65 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +#include + +namespace td { + +class MessageEffectId { + int64 id = 0; + + public: + MessageEffectId() = default; + + explicit constexpr MessageEffectId(int64 message_effect_id) : id(message_effect_id) { + } + template ::value>> + MessageEffectId(T message_effect_id) = delete; + + bool is_valid() const { + return id != 0; + } + + int64 get() const { + return id; + } + + bool operator==(const MessageEffectId &other) const { + return id == other.id; + } + + bool operator!=(const MessageEffectId &other) const { + return id != other.id; + } + + template + void store(StorerT &storer) const { + storer.store_long(id); + } + + template + void parse(ParserT &parser) { + id = parser.fetch_long(); + } +}; + +struct MessageEffectIdHash { + uint32 operator()(MessageEffectId message_effect_id) const { + return Hash()(message_effect_id.get()); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, MessageEffectId message_effect_id) { + return string_builder << "message effect " << message_effect_id.get(); +} + +} // namespace td diff --git a/td/telegram/MessageEntity.cpp b/td/telegram/MessageEntity.cpp index 85f003526a7e..c50486a5ddfb 100644 --- a/td/telegram/MessageEntity.cpp +++ b/td/telegram/MessageEntity.cpp @@ -1086,15 +1086,15 @@ static bool is_common_tld(Slice str) { "mitsubishi", "mk", "ml", "mlb", "mls", "mm", "mma", "mn", "mo", "mobi", "mobile", "moda", "moe", "moi", "mom", "monash", "money", "monster", "mormon", "mortgage", "moscow", "moto", "motorcycles", "mov", "movie", "mp", "mq", "mr", "ms", "msd", "mt", "mtn", "mtr", "mu", "museum", "music", "mv", "mw", "mx", "my", "mz", "na", "nab", - "nagoya", "name", "natura", "navy", "nba", "nc", "ne", "nec", "net", "netbank", "netflix", "network", "neustar", - "new", "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng", "ngo", "nhk", "ni", "nico", "nike", "nikon", - "ninja", "nissan", "nissay", "nl", "no", "nokia", "norton", "now", "nowruz", "nowtv", "np", "nr", "nra", "nrw", - "ntt", "nu", "nyc", "nz", "obi", "observer", "office", "okinawa", "olayan", "olayangroup", "ollo", "om", "omega", - "one", "ong", "onl", "online", "ooo", "open", "oracle", "orange", "org", "organic", "origins", "osaka", "otsuka", - "ott", "ovh", "pa", "page", "panasonic", "paris", "pars", "partners", "parts", "party", "pay", "pccw", "pe", - "pet", "pf", "pfizer", "pg", "ph", "pharmacy", "phd", "philips", "phone", "photo", "photography", "photos", - "physio", "pics", "pictet", "pictures", "pid", "pin", "ping", "pink", "pioneer", "pizza", "pk", "pl", "place", - "play", "playstation", "plumbing", "plus", "pm", "pn", "pnc", "pohl", "poker", "politie", "porn", "post", "pr", + "nagoya", "name", "navy", "nba", "nc", "ne", "nec", "net", "netbank", "netflix", "network", "neustar", "new", + "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng", "ngo", "nhk", "ni", "nico", "nike", "nikon", "ninja", + "nissan", "nissay", "nl", "no", "nokia", "norton", "now", "nowruz", "nowtv", "np", "nr", "nra", "nrw", "ntt", + "nu", "nyc", "nz", "obi", "observer", "office", "okinawa", "olayan", "olayangroup", "ollo", "om", "omega", "one", + "ong", "onl", "online", "ooo", "open", "oracle", "orange", "org", "organic", "origins", "osaka", "otsuka", "ott", + "ovh", "pa", "page", "panasonic", "paris", "pars", "partners", "parts", "party", "pay", "pccw", "pe", "pet", + "pf", "pfizer", "pg", "ph", "pharmacy", "phd", "philips", "phone", "photo", "photography", "photos", "physio", + "pics", "pictet", "pictures", "pid", "pin", "ping", "pink", "pioneer", "pizza", "pk", "pl", "place", "play", + "playstation", "plumbing", "plus", "pm", "pn", "pnc", "pohl", "poker", "politie", "porn", "post", "pr", "pramerica", "praxi", "press", "prime", "pro", "prod", "productions", "prof", "progressive", "promo", "properties", "property", "protection", "pru", "prudential", "ps", "pt", "pub", "pw", "pwc", "py", "qa", "qpon", "quebec", "quest", "racing", "radio", "re", "read", "realestate", "realtor", "realty", "recipes", "red", diff --git a/td/telegram/MessageInputReplyTo.cpp b/td/telegram/MessageInputReplyTo.cpp index 809fb191b18f..c04c10dd394c 100644 --- a/td/telegram/MessageInputReplyTo.cpp +++ b/td/telegram/MessageInputReplyTo.cpp @@ -120,9 +120,13 @@ td_api::object_ptr MessageInputReplyTo::get_input_m if (!message_id_.is_valid() && !message_id_.is_valid_scheduled()) { return nullptr; } - return td_api::make_object( - td->dialog_manager_->get_chat_id_object(dialog_id_, "inputMessageReplyToMessage"), message_id_.get(), - quote_.get_input_text_quote_object()); + if (dialog_id_ != DialogId()) { + return td_api::make_object( + td->dialog_manager_->get_chat_id_object(dialog_id_, "inputMessageReplyToExternalMessage"), message_id_.get(), + quote_.get_input_text_quote_object()); + } + return td_api::make_object(message_id_.get(), + quote_.get_input_text_quote_object()); } MessageId MessageInputReplyTo::get_same_chat_reply_to_message_id() const { diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 2d7b1022848f..20d731f641b2 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -1899,8 +1899,7 @@ class SearchMessagesQuery final : public Td::ResultHandler { void send(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, const ReactionType &tag, int64 random_id) { - auto input_peer = dialog_id.is_valid() ? td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read) - : make_tl_object(); + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); CHECK(input_peer != nullptr); dialog_id_ = dialog_id; @@ -2019,6 +2018,57 @@ class SearchMessagesQuery final : public Td::ResultHandler { } }; +class SearchCallMessagesQuery final : public Td::ResultHandler { + Promise> promise_; + MessageId from_message_id_; + int32 limit_; + MessageSearchFilter filter_; + + public: + explicit SearchCallMessagesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(MessageId from_message_id, int32 limit, MessageSearchFilter filter) { + from_message_id_ = from_message_id; + limit_ = limit; + filter_ = filter; + + auto offset_id = from_message_id.get_server_message_id().get(); + send_query(G()->net_query_creator().create(telegram_api::messages_search( + 0, telegram_api::make_object(), string(), nullptr, nullptr, + vector>(), 0, get_input_messages_filter(filter), 0, + std::numeric_limits::max(), offset_id, 0, limit, std::numeric_limits::max(), 0, 0))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchCallMessagesQuery"); + td_->messages_manager_->get_channel_differences_if_needed( + std::move(info), + PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), from_message_id = from_message_id_, + limit = limit_, filter = filter_, + promise = std::move(promise_)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + auto info = result.move_as_ok(); + send_closure(actor_id, &MessagesManager::on_get_call_messages, from_message_id, limit, filter, + info.total_count, std::move(info.messages), std::move(promise)); + } + }), + "SearchCallMessagesQuery"); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class GetSearchResultPositionsQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; @@ -2835,7 +2885,7 @@ class SendMessageQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, - int64 effect_id, tl_object_ptr &&reply_markup, + MessageEffectId effect_id, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, bool is_copy, int64 random_id, NetQueryRef *send_query_ref) { random_id_ = random_id; @@ -2859,10 +2909,11 @@ class SendMessageQuery final : public Td::ResultHandler { } auto query = G()->net_query_creator().create( - telegram_api::messages_sendMessage( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), text, random_id, - std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer), nullptr, effect_id), + telegram_api::messages_sendMessage(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + std::move(input_peer), std::move(reply_to), text, random_id, + std::move(reply_markup), std::move(entities), schedule_date, + std::move(as_input_peer), nullptr, effect_id.get()), {{dialog_id, MessageContentType::Text}, {dialog_id, is_copy ? MessageContentType::Photo : MessageContentType::Text}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { @@ -3035,7 +3086,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, - int64 effect_id, vector &&file_ids, + MessageEffectId effect_id, vector &&file_ids, vector> &&input_single_media, bool is_copy) { for (auto &single_media : input_single_media) { random_ids_.push_back(single_media->random_id_); @@ -3065,7 +3116,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_single_media), - schedule_date, std::move(as_input_peer), nullptr, effect_id), + schedule_date, std::move(as_input_peer), nullptr, effect_id.get()), {{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo}, {dialog_id, MessageContentType::Photo}})); } @@ -3153,7 +3204,7 @@ class SendMediaQuery final : public Td::ResultHandler { public: void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, - MessageId top_thread_message_id, int32 schedule_date, int64 effect_id, + MessageId top_thread_message_id, int32 schedule_date, MessageEffectId effect_id, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, tl_object_ptr &&input_media, MessageContentType content_type, bool is_copy, @@ -3184,10 +3235,11 @@ class SendMediaQuery final : public Td::ResultHandler { } auto query = G()->net_query_creator().create( - telegram_api::messages_sendMedia( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_media), text, random_id, - std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer), nullptr, effect_id), + telegram_api::messages_sendMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), + std::move(reply_to), std::move(input_media), text, random_id, + std::move(reply_markup), std::move(entities), schedule_date, + std::move(as_input_peer), nullptr, effect_id.get()), {{dialog_id, content_type}, {dialog_id, is_copy ? MessageContentType::Text : content_type}}); if (td_->option_manager_->get_option_boolean("use_quick_ack") && was_uploaded_) { query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result result) { @@ -4191,7 +4243,7 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_initial_top_thread_message_id = !message_id.is_any_server() && initial_top_thread_message_id.is_valid(); bool has_sender_boost_count = sender_boost_count != 0; bool has_via_business_bot_user_id = via_business_bot_user_id.is_valid(); - bool has_effect_id = effect_id != 0; + bool has_effect_id = effect_id.is_valid(); bool has_fact_check = fact_check != nullptr; BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); @@ -8225,7 +8277,7 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr input_file, tl_object_ptr input_thumbnail) { CHECK(m != nullptr); @@ -8313,7 +8365,8 @@ void MessagesManager::do_send_media(DialogId dialog_id, Message *m, FileId file_ on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id); } -void MessagesManager::do_send_secret_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id, +void MessagesManager::do_send_secret_media(DialogId dialog_id, const Message *m, FileId file_id, + FileId thumbnail_file_id, tl_object_ptr input_encrypted_file, BufferSlice thumbnail) { CHECK(dialog_id.get_type() == DialogType::SecretChat); @@ -9092,90 +9145,88 @@ void MessagesManager::on_get_message_search_result_calendar( promise.set_value(td_api::make_object(total_count, std::move(days))); } -void MessagesManager::on_get_dialog_messages_search_result( - DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, const string &query, DialogId sender_dialog_id, - MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, - const ReactionType &tag, int64 random_id, int32 total_count, - vector> &&messages, Promise &&promise) { +void MessagesManager::on_get_call_messages(MessageId from_message_id, int32 limit, MessageSearchFilter filter, + int32 total_count, + vector> &&messages, + Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - LOG(INFO) << "Receive " << messages.size() << " found messages in " << dialog_id; - if (!dialog_id.is_valid()) { - CHECK(query.empty()); - CHECK(!sender_dialog_id.is_valid()); - CHECK(!top_thread_message_id.is_valid()); - CHECK(!saved_messages_topic_id.is_valid()); - CHECK(tag.is_empty()); - auto it = found_call_messages_.find(random_id); - CHECK(it != found_call_messages_.end()); - - MessageId first_added_message_id; - if (messages.empty()) { - // messages may be empty because there are no more messages or they can't be found due to global limit - // anyway pretend that there are no more messages - first_added_message_id = MessageId::min(); - } - - auto &result = it->second.message_full_ids; - CHECK(result.empty()); - int32 added_message_count = 0; - MessageId next_offset_message_id; - for (auto &message : messages) { - auto message_id = MessageId::get_message_id(message, false); - if (message_id.is_valid() && (!next_offset_message_id.is_valid() || message_id < next_offset_message_id)) { - next_offset_message_id = message_id; - } - auto new_message_full_id = on_get_message(std::move(message), false, false, false, "search call messages"); - if (new_message_full_id == MessageFullId()) { - continue; - } - - result.push_back(new_message_full_id); - added_message_count++; + LOG(INFO) << "Receive " << messages.size() << " found call messages"; + MessageId first_added_message_id; + if (messages.empty()) { + // messages may be empty because there are no more messages or they can't be found due to global limit + // anyway pretend that there are no more messages + first_added_message_id = MessageId::min(); + } - CHECK(message_id == new_message_full_id.get_message_id()); - CHECK(message_id.is_valid()); - if (message_id < first_added_message_id || !first_added_message_id.is_valid()) { - first_added_message_id = message_id; - } + FoundMessages found_messages; + auto &result = found_messages.message_full_ids; + int32 added_message_count = 0; + MessageId next_offset_message_id; + for (auto &message : messages) { + auto message_id = MessageId::get_message_id(message, false); + if (message_id.is_valid() && (!next_offset_message_id.is_valid() || message_id < next_offset_message_id)) { + next_offset_message_id = message_id; } - if (total_count < added_message_count) { - LOG(ERROR) << "Receive total_count = " << total_count << ", but added " << added_message_count - << " messages out of " << messages.size(); - total_count = added_message_count; + auto new_message_full_id = on_get_message(std::move(message), false, false, false, "on_get_call_messages"); + if (new_message_full_id == MessageFullId()) { + continue; } - if (G()->use_message_database()) { - bool update_state = false; - auto &old_message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; - if (old_message_count != total_count) { - LOG(INFO) << "Update calls database message count to " << total_count; - old_message_count = total_count; - update_state = true; - } + result.push_back(new_message_full_id); + added_message_count++; - auto &old_first_db_message_id = - calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)]; - bool from_the_end = !from_message_id.is_valid() || from_message_id >= MessageId::max(); - LOG(INFO) << "Have from_the_end = " << from_the_end << ", old_first_db_message_id = " << old_first_db_message_id - << ", first_added_message_id = " << first_added_message_id << ", from_message_id = " << from_message_id; - if ((from_the_end || (old_first_db_message_id.is_valid() && old_first_db_message_id <= from_message_id)) && - (!old_first_db_message_id.is_valid() || first_added_message_id < old_first_db_message_id)) { - LOG(INFO) << "Update calls database first message to " << first_added_message_id; - old_first_db_message_id = first_added_message_id; - update_state = true; - } - if (update_state) { - save_calls_db_state(); - } + CHECK(message_id == new_message_full_id.get_message_id()); + CHECK(message_id.is_valid()); + if (message_id < first_added_message_id || !first_added_message_id.is_valid()) { + first_added_message_id = message_id; } - it->second.total_count = total_count; - if (next_offset_message_id.is_valid()) { - it->second.next_offset = PSTRING() << next_offset_message_id.get_server_message_id().get(); + } + if (total_count < added_message_count) { + LOG(ERROR) << "Receive total_count = " << total_count << ", but added " << added_message_count + << " messages out of " << messages.size(); + total_count = added_message_count; + } + if (G()->use_message_database()) { + bool update_state = false; + + auto &old_message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; + if (old_message_count != total_count) { + LOG(INFO) << "Update calls database message count to " << total_count; + old_message_count = total_count; + update_state = true; } - promise.set_value(Unit()); - return; + + auto &old_first_db_message_id = + calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)]; + bool from_the_end = !from_message_id.is_valid() || from_message_id >= MessageId::max(); + LOG(INFO) << "Have from_the_end = " << from_the_end << ", old_first_db_message_id = " << old_first_db_message_id + << ", first_added_message_id = " << first_added_message_id << ", from_message_id = " << from_message_id; + if ((from_the_end || (old_first_db_message_id.is_valid() && old_first_db_message_id <= from_message_id)) && + (!old_first_db_message_id.is_valid() || first_added_message_id < old_first_db_message_id)) { + LOG(INFO) << "Update calls database first message to " << first_added_message_id; + old_first_db_message_id = first_added_message_id; + update_state = true; + } + if (update_state) { + save_calls_db_state(); + } + } + found_messages.total_count = total_count; + if (next_offset_message_id.is_valid()) { + found_messages.next_offset = PSTRING() << next_offset_message_id.get_server_message_id().get(); } + promise.set_value(get_found_messages_object(found_messages, "on_get_call_messages")); +} + +void MessagesManager::on_get_dialog_messages_search_result( + DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, const string &query, DialogId sender_dialog_id, + MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, + const ReactionType &tag, int64 random_id, int32 total_count, + vector> &&messages, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + LOG(INFO) << "Receive " << messages.size() << " found messages in " << dialog_id; auto it = found_dialog_messages_.find(random_id); CHECK(it != found_dialog_messages_.end()); @@ -9298,13 +9349,6 @@ void MessagesManager::on_get_dialog_messages_search_result( } void MessagesManager::on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id) { - if (!dialog_id.is_valid()) { - auto it = found_call_messages_.find(random_id); - CHECK(it != found_call_messages_.end()); - found_call_messages_.erase(it); - return; - } - auto it = found_dialog_messages_.find(random_id); CHECK(it != found_dialog_messages_.end()); found_dialog_messages_.erase(it); @@ -13199,7 +13243,7 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.has_mention = message->mentioned_; message_info.has_unread_content = message->media_unread_; message_info.invert_media = message->invert_media_; - message_info.effect_id = message->effect_; + message_info.effect_id = MessageEffectId(message->effect_); bool is_content_read = true; if (!td->auth_manager_->is_bot()) { @@ -20498,26 +20542,10 @@ MessagesManager::FoundDialogMessages MessagesManager::search_dialog_messages( return result; } -MessagesManager::FoundMessages MessagesManager::search_call_messages(const string &offset, int32 limit, - bool only_missed, int64 &random_id, bool use_db, - Promise &&promise) { - if (random_id != 0) { - // request has already been sent before - auto it = found_call_messages_.find(random_id); - if (it != found_call_messages_.end()) { - auto result = std::move(it->second); - found_call_messages_.erase(it); - promise.set_value(Unit()); - return result; - } - random_id = 0; - } - LOG(INFO) << "Search call messages from " << offset << " with limit " << limit; - - FoundMessages result; +void MessagesManager::search_call_messages(const string &offset, int32 limit, bool only_missed, + Promise> &&promise) { if (limit <= 0) { - promise.set_error(Status::Error(400, "Parameter limit must be positive")); - return result; + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; @@ -20527,21 +20555,14 @@ MessagesManager::FoundMessages MessagesManager::search_call_messages(const strin if (!offset.empty()) { auto r_offset_server_message_id = to_integer_safe(offset); if (r_offset_server_message_id.is_error()) { - promise.set_error(Status::Error(400, "Invalid offset specified")); - return result; + return promise.set_error(Status::Error(400, "Invalid offset specified")); } offset_message_id = MessageId(ServerMessageId(r_offset_server_message_id.ok())); } - do { - random_id = Random::secure_int64(); - } while (random_id == 0 || found_call_messages_.count(random_id) > 0); - found_call_messages_[random_id]; // reserve place for result - auto filter = only_missed ? MessageSearchFilter::MissedCall : MessageSearchFilter::Call; - - if (use_db && G()->use_message_database()) { + if (G()->use_message_database()) { // try to use database MessageId first_db_message_id = calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)]; @@ -20561,19 +20582,17 @@ MessagesManager::FoundMessages MessagesManager::search_call_messages(const strin db_query.from_unique_message_id = fixed_from_message_id.get_server_message_id().get(); db_query.limit = limit; G()->td_db()->get_message_db_async()->get_calls( - db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter, promise = std::move(promise)]( - Result calls_result) mutable { + db_query, + PromiseCreator::lambda([first_db_message_id, offset_message_id, limit, filter, + promise = std::move(promise)](Result calls_result) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_message_db_calls_result, std::move(calls_result), - random_id, first_db_message_id, filter, std::move(promise)); + first_db_message_id, offset_message_id, limit, filter, std::move(promise)); })); - return result; + return; } } - td_->create_handler(std::move(promise)) - ->send(DialogId(), SavedMessagesTopicId(), string(), DialogId(), offset_message_id, 0, limit, filter, MessageId(), - ReactionType(), random_id); - return result; + td_->create_handler(std::move(promise))->send(offset_message_id, limit, filter); } void MessagesManager::search_outgoing_document_messages(const string &query, int32 limit, @@ -21200,19 +21219,14 @@ void MessagesManager::on_message_db_fts_result(Result result promise.set_value(get_found_messages_object(found_messages, "on_message_db_fts_result")); } -void MessagesManager::on_message_db_calls_result(Result result, int64 random_id, - MessageId first_db_message_id, MessageSearchFilter filter, - Promise &&promise) { - G()->ignore_result_if_closing(result); - if (result.is_error()) { - found_call_messages_.erase(random_id); - return promise.set_error(result.move_as_error()); - } - auto calls_result = result.move_as_ok(); +void MessagesManager::on_message_db_calls_result(Result result, MessageId first_db_message_id, + MessageId offset_message_id, int32 limit, MessageSearchFilter filter, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + TRY_RESULT_PROMISE(promise, calls_result, std::move(result)); - auto it = found_call_messages_.find(random_id); - CHECK(it != found_call_messages_.end()); - auto &res = it->second.message_full_ids; + FoundMessages found_messages; + auto &res = found_messages.message_full_ids; CHECK(!first_db_message_id.is_scheduled()); res.reserve(calls_result.messages.size()); @@ -21226,17 +21240,17 @@ void MessagesManager::on_message_db_calls_result(Result re res.emplace_back(message.dialog_id, m->message_id); } } - it->second.total_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; + found_messages.total_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; if (next_offset_message_id.is_valid()) { - it->second.next_offset = PSTRING() << next_offset_message_id.get_server_message_id().get(); + found_messages.next_offset = PSTRING() << next_offset_message_id.get_server_message_id().get(); } if (res.empty() && first_db_message_id != MessageId::min()) { LOG(INFO) << "No messages found in database"; - found_call_messages_.erase(it); + return td_->create_handler(std::move(promise))->send(offset_message_id, limit, filter); } - promise.set_value(Unit()); + promise.set_value(get_found_messages_object(found_messages, "on_message_db_calls_result")); } void MessagesManager::search_messages(DialogListId dialog_list_id, bool ignore_folder_id, bool broadcasts_only, @@ -22737,8 +22751,8 @@ td_api::object_ptr MessagesManager::get_business_message_messag false, false, false, false, false, false, false, false, false, false, false, m->date, m->edit_date, std::move(forward_info), std::move(import_info), nullptr, Auto(), nullptr, std::move(reply_to), 0, 0, std::move(self_destruct_type), 0.0, 0.0, via_bot_user_id, via_business_bot_user_id, 0, string(), - m->media_album_id, m->effect_id, get_restriction_reason_description(m->restriction_reasons), std::move(content), - std::move(reply_markup)); + m->media_album_id, m->effect_id.get(), get_restriction_reason_description(m->restriction_reasons), + std::move(content), std::move(reply_markup)); } td_api::object_ptr MessagesManager::get_message_object(MessageFullId message_full_id, @@ -22856,7 +22870,7 @@ td_api::object_ptr MessagesManager::get_message_object(DialogId top_thread_message_id, td_->saved_messages_manager_->get_saved_messages_topic_id_object(m->saved_messages_topic_id), std::move(self_destruct_type), ttl_expires_in, auto_delete_in, via_bot_user_id, via_business_bot_user_id, - m->sender_boost_count, m->author_signature, m->media_album_id, m->effect_id, + m->sender_boost_count, m->author_signature, m->media_album_id, m->effect_id.get(), get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } @@ -23052,7 +23066,7 @@ unique_ptr MessagesManager::create_message_to_send( if (m->sender_user_id == my_id && dialog_type == DialogType::Channel) { m->sender_boost_count = td_->chat_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); } - m->effect_id = options.effect_id; + m->effect_id = MessageEffectId(options.effect_id); m->content = std::move(content); m->invert_media = invert_media; m->forward_info = std::move(forward_info); @@ -23205,43 +23219,48 @@ MessageInputReplyTo MessagesManager::create_message_input_reply_to( } return {}; } - auto *reply_d = d; - auto reply_dialog_id = DialogId(reply_to_message->chat_id_); - if (reply_dialog_id != DialogId()) { - reply_d = get_dialog_force(reply_dialog_id, "create_message_input_reply_to"); - if (reply_d == nullptr) { - return {}; - } - if (d->dialog_id.get_type() == DialogType::SecretChat) { - return {}; - } - } - message_id = get_persistent_message_id(reply_d, message_id); - if (message_id == MessageId(ServerMessageId(1)) && reply_d->dialog_id.get_type() == DialogType::Channel) { + message_id = get_persistent_message_id(d, message_id); + if (message_id == MessageId(ServerMessageId(1)) && d->dialog_id.get_type() == DialogType::Channel) { return {}; } - const Message *m = get_message_force(reply_d, message_id, "create_message_input_reply_to 2"); + const Message *m = get_message_force(d, message_id, "create_message_input_reply_to 2"); if (m == nullptr || m->message_id.is_yet_unsent() || - (m->message_id.is_local() && reply_d->dialog_id.get_type() != DialogType::SecretChat)) { - if (message_id.is_server() && reply_d->dialog_id.get_type() != DialogType::SecretChat && - reply_dialog_id == DialogId() && message_id > reply_d->last_new_message_id && - (reply_d->notification_info != nullptr && - message_id <= reply_d->notification_info->max_push_notification_message_id_)) { + (m->message_id.is_local() && d->dialog_id.get_type() != DialogType::SecretChat)) { + if (message_id.is_server() && d->dialog_id.get_type() != DialogType::SecretChat && + message_id > d->last_new_message_id && + (d->notification_info != nullptr && + message_id <= d->notification_info->max_push_notification_message_id_)) { // allow to reply yet unreceived server message in the same chat - return MessageInputReplyTo{message_id, reply_dialog_id, - MessageQuote{td_, std::move(reply_to_message->quote_)}}; + return MessageInputReplyTo{message_id, DialogId(), MessageQuote{td_, std::move(reply_to_message->quote_)}}; } if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { return MessageInputReplyTo{top_thread_message_id, DialogId(), MessageQuote()}; } - LOG(INFO) << "Can't find " << message_id << " in " << reply_d->dialog_id; + LOG(INFO) << "Can't find " << message_id << " in " << d->dialog_id; // TODO local replies to local messages can be allowed // TODO replies to yet unsent messages can be allowed with special handling of them on application restart return {}; } - if (reply_dialog_id != DialogId() && (!can_forward_message(reply_dialog_id, m) || !m->message_id.is_server())) { - LOG(INFO) << "Can't reply in another chat " << m->message_id << " in " << reply_d->dialog_id; + return MessageInputReplyTo{m->message_id, DialogId(), MessageQuote{td_, std::move(reply_to_message->quote_)}}; + } + case td_api::inputMessageReplyToExternalMessage::ID: { + auto reply_to_message = td_api::move_object_as(reply_to); + if (d->dialog_id.get_type() == DialogType::SecretChat) { + return {}; + } + auto reply_dialog_id = DialogId(reply_to_message->chat_id_); + auto *reply_d = get_dialog_force(reply_dialog_id, "create_message_input_reply_to"); + if (reply_d == nullptr) { + return {}; + } + auto message_id = get_persistent_message_id(reply_d, MessageId(reply_to_message->message_id_)); + if (message_id == MessageId(ServerMessageId(1)) && reply_d->dialog_id.get_type() == DialogType::Channel) { + return {}; + } + const Message *m = get_message_force(reply_d, message_id, "create_message_input_reply_to 2"); + if (!can_forward_message(reply_dialog_id, m) || !m->message_id.is_valid() || !m->message_id.is_server()) { + LOG(INFO) << "Can't reply in another chat " << message_id << " in " << reply_d->dialog_id; return {}; } return MessageInputReplyTo{m->message_id, reply_dialog_id, @@ -23768,7 +23787,7 @@ Result MessagesManager::process_message_sen if (!allow_effect) { return Status::Error(400, "Can't use message effects in the method"); } - result.effect_id = options->effect_id_; + result.effect_id = MessageEffectId(options->effect_id_); } return std::move(result); @@ -24356,7 +24375,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { MessageId top_thread_message_id; int32 flags = 0; int32 schedule_date = 0; - int64 effect_id = 0; + MessageEffectId effect_id; bool is_copy = false; for (size_t i = 0; i < request.message_ids.size(); i++) { auto *m = get_message(d, request.message_ids[i]); @@ -25792,7 +25811,7 @@ int32 MessagesManager::get_message_flags(const Message *m) { if (m->invert_media) { flags |= SEND_MESSAGE_FLAG_INVERT_MEDIA; } - if (m->effect_id != 0) { + if (m->effect_id.is_valid()) { flags |= SEND_MESSAGE_FLAG_EFFECT; } return flags; @@ -26529,7 +26548,7 @@ Result> MessagesManager::send_quick_reply_s auto *d = get_dialog(dialog_id); CHECK(d != nullptr); - MessageSendOptions message_send_options(false, false, false, false, false, 0, sending_id, 0); + MessageSendOptions message_send_options(false, false, false, false, false, 0, sending_id, MessageEffectId()); FlatHashMap original_message_id_to_new_message_id; vector> result; vector sent_messages; diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 15daebd5088d..0cbffcd4d271 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -29,6 +29,7 @@ #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageDb.h" +#include "td/telegram/MessageEffectId.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageInputReplyTo.h" @@ -182,6 +183,10 @@ class MessagesManager final : public Actor { vector> &&periods, Promise> &&promise); + void on_get_call_messages(MessageId from_message_id, int32 limit, MessageSearchFilter filter, int32 total_count, + vector> &&messages, + Promise> &&promise); + void on_get_dialog_messages_search_result(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, @@ -728,8 +733,8 @@ class MessagesManager final : public Actor { const string &offset_str, int32 limit, MessageSearchFilter filter, int32 min_date, int32 max_date, Promise> &&promise); - FoundMessages search_call_messages(const string &offset, int32 limit, bool only_missed, int64 &random_id, bool use_db, - Promise &&promise); + void search_call_messages(const string &offset, int32 limit, bool only_missed, + Promise> &&promise); void search_outgoing_document_messages(const string &query, int32 limit, Promise> &&promise); @@ -999,7 +1004,7 @@ class MessagesManager final : public Actor { vector restriction_reasons; string author_signature; int64 media_album_id = 0; - int64 effect_id = 0; + MessageEffectId effect_id; bool is_outgoing = false; bool is_silent = false; bool is_channel_post = false; @@ -1116,7 +1121,7 @@ class MessagesManager final : public Actor { double ttl_expires_at = 0; // only for TTL int64 media_album_id = 0; - int64 effect_id = 0; + MessageEffectId effect_id; unique_ptr content; @@ -1485,11 +1490,12 @@ class MessagesManager final : public Actor { bool only_preview = false; int32 schedule_date = 0; int32 sending_id = 0; - int64 effect_id = 0; + MessageEffectId effect_id; MessageSendOptions() = default; MessageSendOptions(bool disable_notification, bool from_background, bool update_stickersets_order, - bool protect_content, bool only_preview, int32 schedule_date, int32 sending_id, int64 effect_id) + bool protect_content, bool only_preview, int32 schedule_date, int32 sending_id, + MessageEffectId effect_id) : disable_notification(disable_notification) , from_background(from_background) , update_stickersets_order(update_stickersets_order) @@ -1837,11 +1843,11 @@ class MessagesManager final : public Actor { td_api::object_ptr &&options, bool in_game_share, vector &©_options); - void do_send_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id, + void do_send_media(DialogId dialog_id, const Message *m, FileId file_id, FileId thumbnail_file_id, tl_object_ptr input_file, tl_object_ptr input_thumbnail); - void do_send_secret_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id, + void do_send_secret_media(DialogId dialog_id, const Message *m, FileId file_id, FileId thumbnail_file_id, tl_object_ptr input_encrypted_file, BufferSlice thumbnail); @@ -2837,8 +2843,9 @@ class MessagesManager final : public Actor { void on_message_db_fts_result(Result result, string offset, int32 limit, Promise> &&promise); - void on_message_db_calls_result(Result result, int64 random_id, MessageId first_db_message_id, - MessageSearchFilter filter, Promise &&promise); + void on_message_db_calls_result(Result result, MessageId first_db_message_id, + MessageId offset_message_id, int32 limit, MessageSearchFilter filter, + Promise> &&promise); void on_load_active_live_location_message_full_ids_from_database(string value); @@ -3252,7 +3259,6 @@ class MessagesManager final : public Actor { FlatHashMap found_dialog_messages_; // random_id -> FoundDialogMessages FlatHashMap found_dialog_messages_dialog_id_; // random_id -> dialog_id - FlatHashMap found_call_messages_; // random_id -> FoundMessages struct MessageEmbeddingCodes { FlatHashMap embedding_codes_; diff --git a/td/telegram/OptionManager.cpp b/td/telegram/OptionManager.cpp index cf5dd0d4a530..b619ca7ed992 100644 --- a/td/telegram/OptionManager.cpp +++ b/td/telegram/OptionManager.cpp @@ -151,6 +151,8 @@ OptionManager::OptionManager(Td *td) set_default_integer_option("business_chat_link_count_max", is_test_dc ? 5 : 100); set_default_integer_option("pinned_story_count_max", 3); set_default_integer_option("fact_check_length_max", 1024); + set_default_integer_option("star_withdrawal_count_min", is_test_dc ? 10 : 1000); + set_default_integer_option("story_link_area_count_max", 3); if (options.isset("my_phone_number") || !options.isset("my_id")) { update_premium_options(); @@ -702,7 +704,7 @@ td_api::object_ptr OptionManager::get_option_synchronously( break; case 'v': if (name == "version") { - return td_api::make_object("1.8.30"); + return td_api::make_object("1.8.31"); } break; } diff --git a/td/telegram/Payments.cpp b/td/telegram/Payments.cpp index 870ef6c711e4..16db3ff49335 100644 --- a/td/telegram/Payments.cpp +++ b/td/telegram/Payments.cpp @@ -20,6 +20,7 @@ #include "td/telegram/Photo.h" #include "td/telegram/Premium.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/StarManager.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" @@ -508,7 +509,8 @@ class GetPaymentFormQuery final : public Td::ResultHandler { return on_error(Status::Error(500, "Receive invalid price")); } auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_form->photo_), dialog_id_); - auto type = td_api::make_object(payment_form->invoice_->prices_[0]->amount_); + auto type = td_api::make_object( + StarManager::get_star_count(payment_form->invoice_->prices_[0]->amount_)); promise_.set_value(td_api::make_object( payment_form->form_id_, std::move(type), td_->user_manager_->get_user_id_object(seller_bot_user_id, "paymentForm seller"), @@ -663,16 +665,16 @@ class SendStarPaymentFormQuery final : public Td::ResultHandler { switch (payment_result->get_id()) { case telegram_api::payments_paymentResult::ID: { - auto result = move_tl_object_as(payment_result); + auto result = telegram_api::move_object_as(payment_result); td_->updates_manager_->on_get_updates( std::move(result->updates_), PromiseCreator::lambda([promise = std::move(promise_)](Unit) mutable { - promise.set_value(make_tl_object(true, string())); + promise.set_value(td_api::make_object(true, string())); })); return; } case telegram_api::payments_paymentVerificationNeeded::ID: { - auto result = move_tl_object_as(payment_result); - promise_.set_value(make_tl_object(false, std::move(result->url_))); + auto result = telegram_api::move_object_as(payment_result); + promise_.set_value(td_api::make_object(false, std::move(result->url_))); return; } default: @@ -729,13 +731,12 @@ class GetPaymentReceiptQuery final : public Td::ResultHandler { LOG(ERROR) << "Receive invalid prices " << to_string(payment_receipt->invoice_->prices_); return on_error(Status::Error(500, "Receive invalid price")); } - auto type = td_api::make_object(); - promise_.set_value(make_tl_object( get_product_info_object(td_, payment_receipt->title_, payment_receipt->description_, photo), payment_receipt->date_, td_->user_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"), - td_api::make_object(payment_receipt->invoice_->prices_[0]->amount_, - payment_receipt->transaction_id_))); + td_api::make_object( + StarManager::get_star_count(payment_receipt->invoice_->prices_[0]->amount_), + payment_receipt->transaction_id_))); break; } case telegram_api::payments_paymentReceipt::ID: { @@ -868,34 +869,6 @@ class ExportInvoiceQuery final : public Td::ResultHandler { } }; -class RefundStarsChargeQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit RefundStarsChargeQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(telegram_api::object_ptr &&input_user, const string &telegram_payment_charge_id) { - send_query(G()->net_query_creator().create( - telegram_api::payments_refundStarsCharge(std::move(input_user), telegram_payment_charge_id))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for RefundStarsChargeQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - class GetBankCardInfoQuery final : public Td::ResultHandler { Promise> promise_; @@ -1158,12 +1131,6 @@ void export_invoice(Td *td, td_api::object_ptr &&in td->create_handler(std::move(promise))->send(std::move(input_media)); } -void refund_star_payment(Td *td, UserId user_id, const string &telegram_payment_charge_id, Promise &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td->user_manager_->get_input_user(user_id)); - td->create_handler(std::move(promise)) - ->send(std::move(input_user), telegram_payment_charge_id); -} - void get_bank_card_info(Td *td, const string &bank_card_number, Promise> &&promise) { td->create_handler(std::move(promise))->send(bank_card_number); diff --git a/td/telegram/Payments.h b/td/telegram/Payments.h index 82ad0bfb25d3..667fa9db85d2 100644 --- a/td/telegram/Payments.h +++ b/td/telegram/Payments.h @@ -8,7 +8,6 @@ #include "td/telegram/MessageFullId.h" #include "td/telegram/td_api.h" -#include "td/telegram/UserId.h" #include "td/utils/common.h" #include "td/utils/Promise.h" @@ -49,8 +48,6 @@ void delete_saved_credentials(Td *td, Promise &&promise); void export_invoice(Td *td, td_api::object_ptr &&invoice, Promise &&promise); -void refund_star_payment(Td *td, UserId user_id, const string &telegram_payment_charge_id, Promise &&promise); - void get_bank_card_info(Td *td, const string &bank_card_number, Promise> &&promise); diff --git a/td/telegram/Premium.cpp b/td/telegram/Premium.cpp index d4d3ade3479d..105312f3ca9f 100644 --- a/td/telegram/Premium.cpp +++ b/td/telegram/Premium.cpp @@ -17,13 +17,11 @@ #include "td/telegram/DocumentsManager.h" #include "td/telegram/GiveawayParameters.h" #include "td/telegram/Global.h" -#include "td/telegram/InputInvoice.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" -#include "td/telegram/Photo.h" #include "td/telegram/PremiumGiftOption.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/SuggestedAction.h" @@ -609,122 +607,6 @@ class GetGiveawayInfoQuery final : public Td::ResultHandler { } }; -class GetStarsTopupOptionsQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit GetStarsTopupOptionsQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTopupOptions())); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto results = result_ptr.move_as_ok(); - vector> options; - for (auto &result : results) { - options.push_back(td_api::make_object( - result->currency_, result->amount_, result->stars_, result->store_product_, result->extended_)); - } - - promise_.set_value(td_api::make_object(std::move(options))); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetStarsTransactionsQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit GetStarsTransactionsQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send(const string &offset, td_api::object_ptr &&direction) { - int32 flags = 0; - if (direction != nullptr) { - switch (direction->get_id()) { - case td_api::starTransactionDirectionIncoming::ID: - flags |= telegram_api::payments_getStarsTransactions::INBOUND_MASK; - break; - case td_api::starTransactionDirectionOutgoing::ID: - flags |= telegram_api::payments_getStarsTransactions::OUTBOUND_MASK; - break; - default: - UNREACHABLE(); - } - } - send_query(G()->net_query_creator().create( - telegram_api::payments_getStarsTransactions(flags, false /*ignored*/, false /*ignored*/, - telegram_api::make_object(), offset))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto result = result_ptr.move_as_ok(); - td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsTransactionsQuery"); - td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsTransactionsQuery"); - - vector> transactions; - for (auto &transaction : result->history_) { - td_api::object_ptr product_info; - if (!transaction->title_.empty() || !transaction->description_.empty() || transaction->photo_ != nullptr) { - auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(transaction->photo_), DialogId()); - product_info = get_product_info_object(td_, transaction->title_, transaction->description_, photo); - } - auto source = [&]() -> td_api::object_ptr { - switch (transaction->peer_->get_id()) { - case telegram_api::starsTransactionPeerUnsupported::ID: - return td_api::make_object(); - case telegram_api::starsTransactionPeerPremiumBot::ID: - return td_api::make_object(); - case telegram_api::starsTransactionPeerAppStore::ID: - return td_api::make_object(); - case telegram_api::starsTransactionPeerPlayMarket::ID: - return td_api::make_object(); - case telegram_api::starsTransactionPeerFragment::ID: - return td_api::make_object(); - case telegram_api::starsTransactionPeer::ID: { - DialogId dialog_id( - static_cast(transaction->peer_.get())->peer_); - if (dialog_id.get_type() == DialogType::User) { - return td_api::make_object( - td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), "starTransactionSourceUser"), - std::move(product_info)); - } - return td_api::make_object(); - } - default: - UNREACHABLE(); - } - }(); - transactions.push_back(td_api::make_object( - transaction->id_, transaction->stars_, transaction->refund_, transaction->date_, std::move(source))); - } - - promise_.set_value( - td_api::make_object(result->balance_, std::move(transactions), result->next_offset_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - class CanPurchasePremiumQuery final : public Td::ResultHandler { Promise promise_; @@ -1277,16 +1159,6 @@ void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, ->send(message_full_id.get_dialog_id(), server_message_id); } -void get_star_payment_options(Td *td, Promise> &&promise) { - td->create_handler(std::move(promise))->send(); -} - -void get_star_transactions(Td *td, const string &offset, - td_api::object_ptr &&direction, - Promise> &&promise) { - td->create_handler(std::move(promise))->send(offset, std::move(direction)); -} - void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise) { td->create_handler(std::move(promise))->send(std::move(purpose)); } diff --git a/td/telegram/Premium.h b/td/telegram/Premium.h index a4bb53d8370b..e3659b73fd77 100644 --- a/td/telegram/Premium.h +++ b/td/telegram/Premium.h @@ -54,12 +54,6 @@ void launch_prepaid_premium_giveaway(Td *td, int64 giveaway_id, void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, Promise> &&promise); -void get_star_payment_options(Td *td, Promise> &&promise); - -void get_star_transactions(Td *td, const string &offset, - td_api::object_ptr &&direction, - Promise> &&promise); - void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise); void assign_app_store_transaction(Td *td, const string &receipt, diff --git a/td/telegram/QuickReplyManager.cpp b/td/telegram/QuickReplyManager.cpp index 2671cae947e7..43f955709ce6 100644 --- a/td/telegram/QuickReplyManager.cpp +++ b/td/telegram/QuickReplyManager.cpp @@ -1330,6 +1330,7 @@ void QuickReplyManager::on_reload_quick_reply_shortcuts( changed_shortcut_ids.push_back(shortcut_id); changed_message_shortcut_ids.push_back(shortcut_id); change_message_files({shortcut_id, first_message_id}, shortcut->messages_[0].get(), {}); + register_message_content(shortcut->messages_[0].get(), "on_reload_quick_reply_shortcuts 1"); } else { bool is_shortcut_changed = false; bool are_messages_changed = false; @@ -1370,7 +1371,9 @@ void QuickReplyManager::on_reload_quick_reply_shortcuts( shortcut->local_total_count_ = static_cast(old_shortcut->messages_.size()); for (auto &message : old_shortcut->messages_) { CHECK(message->shortcut_id == shortcut_id); + unregister_message_content(message.get(), "on_reload_quick_reply_shortcuts 2"); message->shortcut_id = shortcut->shortcut_id_; + register_message_content(message.get(), "on_reload_quick_reply_shortcuts 3"); } append(shortcut->messages_, std::move(old_shortcut->messages_)); sort_quick_reply_messages(shortcut->messages_); @@ -1603,6 +1606,7 @@ void QuickReplyManager::on_get_quick_reply_message(Shortcut *s, unique_ptrmessage_id); if (it == s->messages_.end()) { change_message_files({s->shortcut_id_, message->message_id}, message.get(), {}); + register_message_content(message.get(), "on_get_quick_reply_message"); s->messages_.push_back(std::move(message)); s->server_total_count_++; sort_quick_reply_messages(s->messages_); @@ -1636,10 +1640,40 @@ void QuickReplyManager::update_quick_reply_message(QuickReplyShortcutId shortcut new_message->edited_invert_media = old_message->edited_invert_media; new_message->edited_disable_web_page_preview = old_message->edited_disable_web_page_preview; new_message->edit_generation = old_message->edit_generation; + unregister_message_content(old_message.get(), "update_quick_reply_message"); old_message = std::move(new_message); + register_message_content(old_message.get(), "update_quick_reply_message"); change_message_files({shortcut_id, old_message->message_id}, old_message.get(), old_file_ids); } +void QuickReplyManager::on_external_update_message_content(QuickReplyMessageFullId message_full_id, const char *source, + bool expect_no_message) { + const auto *s = get_shortcut(message_full_id.get_quick_reply_shortcut_id()); + auto message_id = message_full_id.get_message_id(); + const auto *m = get_message(s, message_id); + if (expect_no_message && m == nullptr) { + return; + } + CHECK(m != nullptr); + if (s->messages_[0]->message_id == message_id) { + send_update_quick_reply_shortcut(s, "on_external_update_message_content"); + } + send_update_quick_reply_shortcut_messages(s, "on_external_update_message_content"); + // must not call save_quick_reply_shortcuts, because the message itself wasn't changed +} + +void QuickReplyManager::delete_pending_message_web_page(QuickReplyMessageFullId message_full_id) { + auto *m = get_message_editable(message_full_id); + CHECK(has_message_content_web_page(m->content.get())); + unregister_message_content(m, "delete_pending_message_web_page"); + remove_message_content_web_page(m->content.get()); + register_message_content(m, "delete_pending_message_web_page"); + + // don't need to send updates, because the web page was pending + + save_quick_reply_shortcuts(); +} + void QuickReplyManager::delete_quick_reply_messages_from_updates(QuickReplyShortcutId shortcut_id, const vector &message_ids) { if (td_->auth_manager_->is_bot()) { @@ -1841,7 +1875,9 @@ void QuickReplyManager::process_send_quick_reply_updates(QuickReplyShortcutId sh for (auto &message : (*it)->messages_) { CHECK(message->shortcut_id == shortcut_id); + unregister_message_content(message.get(), "process_send_quick_reply_updates 1"); message->shortcut_id = new_shortcut_id; + register_message_content(message.get(), "process_send_quick_reply_updates 1"); } auto *s = get_shortcut(new_shortcut_id); @@ -1891,10 +1927,12 @@ void QuickReplyManager::process_send_quick_reply_updates(QuickReplyShortcutId sh "process_send_quick_reply_updates"); if (message != nullptr && message->shortcut_id == shortcut_id) { update_message_content(it->get(), message.get(), false); + unregister_message_content(it->get(), "process_send_quick_reply_updates 2"); auto old_message_it = get_message_it(s, message->message_id); if (old_message_it == s->messages_.end()) { change_message_files({shortcut_id, message->message_id}, message.get(), {}); *it = std::move(message); + register_message_content(it->get(), "process_send_quick_reply_updates 2"); s->server_total_count_++; } else { // the message has already been added @@ -1977,6 +2015,7 @@ void QuickReplyManager::on_failed_send_quick_reply_messages(QuickReplyShortcutId s->last_assigned_message_id_ = new_message_id; } CHECK(new_message_id.is_valid()); + unregister_message_content(it->get(), "on_failed_send_quick_reply_messages"); (*it)->message_id = new_message_id; (*it)->is_failed_to_send = true; (*it)->send_error_code = error.code(); @@ -1988,6 +2027,7 @@ void QuickReplyManager::on_failed_send_quick_reply_messages(QuickReplyShortcutId } CHECK((*it)->edited_content == nullptr); update_failed_to_send_message_content(td_, (*it)->content); + register_message_content(it->get(), "on_failed_send_quick_reply_messages"); break; } @@ -2212,7 +2252,7 @@ void QuickReplyManager::on_upload_media(FileId file_id, telegram_api::object_ptr being_uploaded_files_.erase(it); - auto *m = get_message(message_full_id); + const auto *m = get_message(message_full_id); if (m == nullptr || (m->message_id.is_server() && m->edit_generation != edit_generation)) { send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); return; @@ -2232,7 +2272,7 @@ void QuickReplyManager::on_upload_media(FileId file_id, telegram_api::object_ptr } } -void QuickReplyManager::do_send_media(QuickReplyMessage *m, FileId file_id, FileId thumbnail_file_id, +void QuickReplyManager::do_send_media(const QuickReplyMessage *m, FileId file_id, FileId thumbnail_file_id, telegram_api::object_ptr input_file, telegram_api::object_ptr input_thumbnail) { CHECK(m != nullptr); @@ -2267,7 +2307,7 @@ void QuickReplyManager::on_upload_media_error(FileId file_id, Status status) { being_uploaded_files_.erase(it); - auto *m = get_message(message_full_id); + const auto *m = get_message(message_full_id); if (m == nullptr) { return; } @@ -2294,7 +2334,7 @@ void QuickReplyManager::on_upload_thumbnail(FileId thumbnail_file_id, being_uploaded_thumbnails_.erase(it); - auto *m = get_message(message_full_id); + auto *m = get_message_editable(message_full_id); if (m == nullptr || (m->message_id.is_server() && m->edit_generation != edit_generation)) { send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); send_closure_later(G()->file_manager(), &FileManager::cancel_upload, thumbnail_file_id); @@ -2357,8 +2397,7 @@ void QuickReplyManager::on_message_media_uploaded(const QuickReplyMessage *m, void QuickReplyManager::on_upload_message_media_success(QuickReplyShortcutId shortcut_id, MessageId message_id, FileId file_id, telegram_api::object_ptr &&media) { - auto *s = get_shortcut(shortcut_id); - auto *m = get_message(s, message_id); + const auto *m = get_message({shortcut_id, message_id}); if (m == nullptr) { send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); return; @@ -2375,13 +2414,9 @@ void QuickReplyManager::on_upload_message_media_success(QuickReplyShortcutId sho update_message_content(m->content, content, true); - if (s->messages_[0]->message_id == message_id) { - // send_update_quick_reply_shortcut(s, "on_upload_message_media_success"); - } - // send_update_quick_reply_shortcut_messages(s, "on_upload_message_media_success"); save_quick_reply_shortcuts(); - auto input_media = get_input_media(m->content.get(), td_, {}, m->send_emoji, true); + auto input_media = get_input_media(content.get(), td_, {}, m->send_emoji, true); Status result; if (input_media == nullptr) { result = Status::Error(400, "Failed to upload file"); @@ -2393,7 +2428,7 @@ void QuickReplyManager::on_upload_message_media_success(QuickReplyShortcutId sho void QuickReplyManager::on_upload_message_media_fail(QuickReplyShortcutId shortcut_id, MessageId message_id, Status error) { - auto *m = get_message({shortcut_id, message_id}); + const auto *m = get_message({shortcut_id, message_id}); if (m == nullptr) { return; } @@ -2458,7 +2493,7 @@ void QuickReplyManager::do_send_message_group(QuickReplyShortcutId shortcut_id, Status error = Status::OK(); for (size_t i = 0; i < request.message_ids.size(); i++) { CHECK(request.is_finished[i]); - auto *m = get_message(s, request.message_ids[i]); + const auto *m = get_message(s, request.message_ids[i]); if (m == nullptr) { // skip deleted messages continue; @@ -2630,14 +2665,16 @@ Result> QuickReplyManager::resend continue; } - auto *m = get_message(s, message_ids[i]); + auto *m = get_message_editable(s, message_ids[i]); CHECK(m != nullptr); + unregister_message_content(m, "resend_message"); m->message_id = get_next_yet_unsent_message_id(s); m->media_album_id = new_media_album_ids[m->media_album_id].first; m->is_failed_to_send = false; m->send_error_code = 0; m->send_error_message = string(); m->try_resend_at = 0.0; + register_message_content(m, "resend_message"); do_send_message(m); @@ -2663,7 +2700,7 @@ void QuickReplyManager::edit_quick_reply_message( if (s == nullptr) { return promise.set_error(Status::Error(400, "Shortcut not found")); } - auto *m = get_message(s, message_id); + auto *m = get_message_editable(s, message_id); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } @@ -2740,7 +2777,7 @@ void QuickReplyManager::on_edit_quick_reply_message(QuickReplyShortcutId shortcu int64 edit_generation, FileId file_id, bool was_uploaded, telegram_api::object_ptr updates_ptr) { auto *s = get_shortcut(shortcut_id); - auto *m = get_message(s, message_id); + auto *m = get_message_editable(s, message_id); if (m == nullptr) { if (was_uploaded) { send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); @@ -2785,6 +2822,7 @@ void QuickReplyManager::on_edit_quick_reply_message(QuickReplyShortcutId shortcu } else { update_message_content(m, message.get(), true); auto old_message_it = get_message_it(s, message_id); + CHECK(old_message_it != s->messages_.end()); update_quick_reply_message(shortcut_id, *old_message_it, std::move(message)); m = old_message_it->get(); was_updated = true; @@ -2795,9 +2833,11 @@ void QuickReplyManager::on_edit_quick_reply_message(QuickReplyShortcutId shortcu auto old_file_ids = get_message_file_ids(m); CHECK(m->edited_content != nullptr); if (!was_updated) { + unregister_message_content(m, "on_edit_quick_reply_message"); m->content = std::move(m->edited_content); m->invert_media = m->edited_invert_media; m->disable_web_page_preview = m->edited_disable_web_page_preview; + register_message_content(m, "on_edit_quick_reply_message"); } m->edit_generation = 0; @@ -2817,7 +2857,7 @@ void QuickReplyManager::fail_edit_quick_reply_message(QuickReplyShortcutId short int64 edit_generation, FileId file_id, FileId thumbnail_file_id, string file_reference, bool was_uploaded, bool was_thumbnail_uploaded, Status status) { - auto *m = get_message({shortcut_id, message_id}); + auto *m = get_message_editable({shortcut_id, message_id}); if (m == nullptr) { if (was_uploaded) { send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); @@ -2974,6 +3014,7 @@ void QuickReplyManager::on_reload_quick_reply_messages( send_update_quick_reply_shortcut_messages(shortcut.get(), "on_reload_quick_reply_messages 2"); for (auto &message : shortcut->messages_) { change_message_files({shortcut_id, message->message_id}, message.get(), {}); + register_message_content(message.get(), "on_reload_quick_reply_messages 5"); } shortcuts_.shortcuts_.push_back(std::move(shortcut)); } else { @@ -3109,7 +3150,7 @@ Result> QuickReplyManager::g } vector result; - for (auto &message : shortcut->messages_) { + for (const auto &message : shortcut->messages_) { if (!message->message_id.is_server()) { continue; } @@ -3210,12 +3251,30 @@ vector>::iterator QuickReplyMan return s->messages_.end(); } -QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message(QuickReplyMessageFullId message_full_id) { - auto *s = get_shortcut(message_full_id.get_quick_reply_shortcut_id()); +const QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message( + QuickReplyMessageFullId message_full_id) const { + const auto *s = get_shortcut(message_full_id.get_quick_reply_shortcut_id()); return get_message(s, message_full_id.get_message_id()); } -QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message(Shortcut *s, MessageId message_id) { +const QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message(const Shortcut *s, + MessageId message_id) const { + if (s != nullptr) { + for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { + if ((*it)->message_id == message_id) { + return it->get(); + } + } + } + return nullptr; +} + +QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message_editable(QuickReplyMessageFullId message_full_id) { + auto *s = get_shortcut(message_full_id.get_quick_reply_shortcut_id()); + return get_message_editable(s, message_full_id.get_message_id()); +} + +QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message_editable(Shortcut *s, MessageId message_id) { if (s != nullptr) { for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { if ((*it)->message_id == message_id) { @@ -3272,7 +3331,7 @@ MessageId QuickReplyManager::get_input_reply_to_message_id(const Shortcut *s, Me if (s == nullptr || !reply_to_message_id.is_valid() || !reply_to_message_id.is_server()) { return MessageId(); } - for (auto &message : s->messages_) { + for (const auto &message : s->messages_) { CHECK(message != nullptr); if (message->message_id == reply_to_message_id) { return reply_to_message_id; @@ -3337,6 +3396,8 @@ QuickReplyManager::QuickReplyMessage *QuickReplyManager::add_local_message( m->random_id = Random::secure_int64(); } while (m->random_id == 0); + register_message_content(m, "add_local_message"); + s->messages_.push_back(std::move(message)); s->local_total_count_++; @@ -3407,6 +3468,7 @@ void QuickReplyManager::update_shortcut_from(Shortcut *new_shortcut, Shortcut *o } if (it == old_shortcut->messages_.end() || (*it)->message_id != new_first_message_id) { change_message_files({old_shortcut->shortcut_id_, new_first_message_id}, new_shortcut->messages_[0].get(), {}); + register_message_content(new_shortcut->messages_[0].get(), "update_shortcut_from"); old_shortcut->messages_.insert(it, std::move(new_shortcut->messages_[0])); } else { update_quick_reply_message(old_shortcut->shortcut_id_, *it, std::move(new_shortcut->messages_[0])); @@ -3514,6 +3576,7 @@ void QuickReplyManager::load_quick_reply_shortcuts() { message->shortcut_id = shortcut->shortcut_id_; } change_message_files({shortcut->shortcut_id_, message->message_id}, message.get(), {}); + register_message_content(message.get(), "load_quick_reply_shortcuts"); if (message->message_id.is_server()) { if (need_reget_message_content(message->content.get()) || @@ -3595,12 +3658,14 @@ vector QuickReplyManager::get_message_file_ids(const QuickReplyMessage * } } } + return file_ids; } return get_message_content_file_ids(m->content.get(), td_); } void QuickReplyManager::delete_message_files(QuickReplyShortcutId shortcut_id, const QuickReplyMessage *m) const { CHECK(m != nullptr); + unregister_message_content(m, "delete_message_files"); auto file_ids = get_message_file_ids(m); if (file_ids.empty()) { return; @@ -3635,6 +3700,14 @@ void QuickReplyManager::change_message_files(QuickReplyMessageFullId message_ful } } +void QuickReplyManager::register_message_content(const QuickReplyMessage *m, const char *source) const { + register_quick_reply_message_content(td_, m->content.get(), {m->shortcut_id, m->message_id}, source); +} + +void QuickReplyManager::unregister_message_content(const QuickReplyMessage *m, const char *source) const { + unregister_quick_reply_message_content(td_, m->content.get(), {m->shortcut_id, m->message_id}, source); +} + FileSourceId QuickReplyManager::get_quick_reply_message_file_source_id(QuickReplyMessageFullId message_full_id) { if (td_->auth_manager_->is_bot()) { return FileSourceId(); diff --git a/td/telegram/QuickReplyManager.h b/td/telegram/QuickReplyManager.h index d950aeb4f3bb..77f6c6d64617 100644 --- a/td/telegram/QuickReplyManager.h +++ b/td/telegram/QuickReplyManager.h @@ -53,6 +53,11 @@ class QuickReplyManager final : public Actor { void update_quick_reply_message(telegram_api::object_ptr &&message_ptr); + void delete_pending_message_web_page(QuickReplyMessageFullId message_full_id); + + void on_external_update_message_content(QuickReplyMessageFullId message_full_id, const char *source, + bool expect_no_message = false); + void delete_quick_reply_messages_from_updates(QuickReplyShortcutId shortcut_id, const vector &message_ids); void get_quick_reply_shortcut_messages(QuickReplyShortcutId shortcut_id, Promise &&promise); @@ -276,9 +281,13 @@ class QuickReplyManager final : public Actor { vector>::iterator get_message_it(Shortcut *s, MessageId message_id); - QuickReplyMessage *get_message(QuickReplyMessageFullId message_full_id); + const QuickReplyMessage *get_message(QuickReplyMessageFullId message_full_id) const; + + const QuickReplyMessage *get_message(const Shortcut *s, MessageId message_id) const; - QuickReplyMessage *get_message(Shortcut *s, MessageId message_id); + QuickReplyMessage *get_message_editable(QuickReplyMessageFullId message_full_id); + + QuickReplyMessage *get_message_editable(Shortcut *s, MessageId message_id); Result create_new_local_shortcut(const string &name, int32 new_message_count); @@ -373,7 +382,7 @@ class QuickReplyManager final : public Actor { void on_upload_media(FileId file_id, telegram_api::object_ptr input_file); - void do_send_media(QuickReplyMessage *m, FileId file_id, FileId thumbnail_file_id, + void do_send_media(const QuickReplyMessage *m, FileId file_id, FileId thumbnail_file_id, telegram_api::object_ptr input_file, telegram_api::object_ptr input_thumbnail); @@ -421,6 +430,10 @@ class QuickReplyManager final : public Actor { void change_message_files(QuickReplyMessageFullId message_full_id, const QuickReplyMessage *m, const vector &old_file_ids); + void register_message_content(const QuickReplyMessage *m, const char *source) const; + + void unregister_message_content(const QuickReplyMessage *m, const char *source) const; + Shortcuts shortcuts_; int32 next_local_shortcut_id_ = QuickReplyShortcutId::MAX_SERVER_SHORTCUT_ID + 1; diff --git a/td/telegram/ReactionManager.cpp b/td/telegram/ReactionManager.cpp index 36f0e07b8e78..0c9e31f378b2 100644 --- a/td/telegram/ReactionManager.cpp +++ b/td/telegram/ReactionManager.cpp @@ -1211,12 +1211,12 @@ td_api::object_ptr ReactionManager::get_message_effect_ob td_->stickers_manager_->get_sticker_object(effect.effect_sticker_id_), td_->stickers_manager_->get_sticker_object(effect.effect_animation_id_)); }(); - return td_api::make_object(effect.id_, + return td_api::make_object(effect.id_.get(), td_->stickers_manager_->get_sticker_object(effect.static_icon_id_), effect.emoji_, effect.is_premium_, std::move(type)); } -td_api::object_ptr ReactionManager::get_message_effect_object(int64 effect_id) const { +td_api::object_ptr ReactionManager::get_message_effect_object(MessageEffectId effect_id) const { for (auto &effect : message_effects_.effects_) { if (effect.id_ == effect_id) { return get_message_effect_object(effect); @@ -1227,9 +1227,12 @@ td_api::object_ptr ReactionManager::get_message_effect_ob td_api::object_ptr ReactionManager::get_update_available_message_effects_object() const { + auto get_raw_effect_ids = [](const vector &message_effect_ids) { + return transform(message_effect_ids, [](MessageEffectId effect_id) { return effect_id.get(); }); + }; return td_api::make_object( - vector(active_message_effects_.reaction_effects_), - vector(active_message_effects_.sticker_effects_)); + get_raw_effect_ids(active_message_effects_.reaction_effects_), + get_raw_effect_ids(active_message_effects_.sticker_effects_)); } void ReactionManager::reload_message_effects() { @@ -1324,7 +1327,7 @@ void ReactionManager::on_get_message_effects( bool have_invalid_order = false; for (const auto &available_effect : effects->effects_) { Effect effect; - effect.id_ = available_effect->id_; + effect.id_ = MessageEffectId(available_effect->id_); effect.emoji_ = std::move(available_effect->emoticon_); effect.is_premium_ = available_effect->premium_required_; if (available_effect->static_icon_id_ != 0) { @@ -1441,7 +1444,7 @@ void ReactionManager::update_active_message_effects() { send_closure(G()->td(), &Td::send_update, get_update_available_message_effects_object()); } -void ReactionManager::get_message_effect(int64 effect_id, +void ReactionManager::get_message_effect(MessageEffectId effect_id, Promise> &&promise) { load_message_effects(); if (message_effects_.effects_.empty() && message_effects_.are_being_reloaded_) { diff --git a/td/telegram/ReactionManager.h b/td/telegram/ReactionManager.h index 14a57e3fe2fa..b18b428995d3 100644 --- a/td/telegram/ReactionManager.h +++ b/td/telegram/ReactionManager.h @@ -8,6 +8,7 @@ #include "td/telegram/ChatReactions.h" #include "td/telegram/files/FileId.h" +#include "td/telegram/MessageEffectId.h" #include "td/telegram/ReactionListType.h" #include "td/telegram/ReactionType.h" #include "td/telegram/ReactionUnavailabilityReason.h" @@ -83,7 +84,7 @@ class ReactionManager final : public Actor { void reload_message_effects(); - void get_message_effect(int64 effect_id, Promise> &&promise); + void get_message_effect(MessageEffectId effect_id, Promise> &&promise); void get_current_state(vector> &updates) const; @@ -194,7 +195,7 @@ class ReactionManager final : public Actor { }; struct Effect { - int64 id_ = 0; + MessageEffectId id_; string emoji_; FileId static_icon_id_; FileId effect_sticker_id_; @@ -202,7 +203,7 @@ class ReactionManager final : public Actor { bool is_premium_ = false; bool is_valid() const { - return id_ != 0 && effect_sticker_id_.is_valid(); + return id_.is_valid() && effect_sticker_id_.is_valid(); } bool is_sticker() const { @@ -229,8 +230,8 @@ class ReactionManager final : public Actor { }; struct ActiveEffects { - vector reaction_effects_; - vector sticker_effects_; + vector reaction_effects_; + vector sticker_effects_; bool is_empty() const { return reaction_effects_.empty() && sticker_effects_.empty(); @@ -289,7 +290,7 @@ class ReactionManager final : public Actor { td_api::object_ptr get_message_effect_object(const Effect &effect) const; - td_api::object_ptr get_message_effect_object(int64 effect_id) const; + td_api::object_ptr get_message_effect_object(MessageEffectId effect_id) const; td_api::object_ptr get_update_available_message_effects_object() const; @@ -331,7 +332,8 @@ class ReactionManager final : public Actor { Effects message_effects_; ActiveEffects active_message_effects_; - vector>>> pending_get_message_effect_queries_; + vector>>> + pending_get_message_effect_queries_; }; } // namespace td diff --git a/td/telegram/StarManager.cpp b/td/telegram/StarManager.cpp new file mode 100644 index 000000000000..b27e9fbfe55b --- /dev/null +++ b/td/telegram/StarManager.cpp @@ -0,0 +1,427 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/StarManager.h" + +#include "td/telegram/AccessRights.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/ChatManager.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/InputInvoice.h" +#include "td/telegram/MessageSender.h" +#include "td/telegram/PasswordManager.h" +#include "td/telegram/Photo.h" +#include "td/telegram/StatisticsManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" + +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" + +namespace td { + +class GetStarsTopupOptionsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStarsTopupOptionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTopupOptions())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto results = result_ptr.move_as_ok(); + vector> options; + for (auto &result : results) { + options.push_back(td_api::make_object(result->currency_, result->amount_, + StarManager::get_star_count(result->stars_), + result->store_product_, result->extended_)); + } + + promise_.set_value(td_api::make_object(std::move(options))); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetStarsTransactionsQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetStarsTransactionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, const string &offset, int32 limit, + td_api::object_ptr &&direction) { + dialog_id_ = dialog_id; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Have no access to the chat")); + } + int32 flags = 0; + if (direction != nullptr) { + switch (direction->get_id()) { + case td_api::starTransactionDirectionIncoming::ID: + flags |= telegram_api::payments_getStarsTransactions::INBOUND_MASK; + break; + case td_api::starTransactionDirectionOutgoing::ID: + flags |= telegram_api::payments_getStarsTransactions::OUTBOUND_MASK; + break; + default: + UNREACHABLE(); + } + } + if (td_->auth_manager_->is_bot()) { + flags |= telegram_api::payments_getStarsTransactions::ASCENDING_MASK; + } + send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTransactions( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), offset, limit))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsTransactionsQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsTransactionsQuery"); + + vector> transactions; + for (auto &transaction : result->history_) { + td_api::object_ptr product_info; + if (!transaction->title_.empty() || !transaction->description_.empty() || transaction->photo_ != nullptr) { + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(transaction->photo_), DialogId()); + product_info = get_product_info_object(td_, transaction->title_, transaction->description_, photo); + } + auto partner = [&]() -> td_api::object_ptr { + switch (transaction->peer_->get_id()) { + case telegram_api::starsTransactionPeerUnsupported::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerPremiumBot::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerAppStore::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerPlayMarket::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerFragment::ID: { + auto state = [&]() -> td_api::object_ptr { + if (transaction->transaction_date_ > 0) { + return td_api::make_object(transaction->transaction_date_, + transaction->transaction_url_); + } + if (transaction->pending_) { + return td_api::make_object(); + } + if (transaction->failed_) { + return td_api::make_object(); + } + if (!transaction->refund_) { + LOG(ERROR) << "Receive " << to_string(transaction); + } + return nullptr; + }(); + return td_api::make_object(std::move(state)); + } + case telegram_api::starsTransactionPeer::ID: { + DialogId dialog_id( + static_cast(transaction->peer_.get())->peer_); + if (dialog_id.get_type() == DialogType::User) { + return td_api::make_object( + td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), "starTransactionPartnerUser"), + std::move(product_info)); + } + if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + return td_api::make_object( + td_->dialog_manager_->get_chat_id_object(dialog_id, "starTransactionPartnerChannel")); + } + return td_api::make_object(); + } + default: + UNREACHABLE(); + } + }(); + transactions.push_back(td_api::make_object( + transaction->id_, StarManager::get_star_count(transaction->stars_, true), transaction->refund_, + transaction->date_, std::move(partner))); + } + + promise_.set_value(td_api::make_object( + StarManager::get_star_count(result->balance_, true), std::move(transactions), result->next_offset_)); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsTransactionsQuery"); + promise_.set_error(std::move(status)); + } +}; + +class RefundStarsChargeQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit RefundStarsChargeQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(telegram_api::object_ptr &&input_user, const string &telegram_payment_charge_id) { + send_query(G()->net_query_creator().create( + telegram_api::payments_refundStarsCharge(std::move(input_user), telegram_payment_charge_id))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for RefundStarsChargeQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +static td_api::object_ptr convert_stars_revenue_status( + telegram_api::object_ptr obj) { + CHECK(obj != nullptr); + int32 next_withdrawal_in = 0; + if (obj->withdrawal_enabled_ && obj->next_withdrawal_at_ > 0) { + next_withdrawal_in = max(obj->next_withdrawal_at_ - G()->unix_time(), 1); + } + return td_api::make_object( + StarManager::get_star_count(obj->overall_revenue_), StarManager::get_star_count(obj->current_balance_), + StarManager::get_star_count(obj->available_balance_), obj->withdrawal_enabled_, next_withdrawal_in); +} + +class GetStarsRevenueStatsQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetStarsRevenueStatsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, bool is_dark) { + dialog_id_ = dialog_id; + + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Have no access to the chat")); + } + + int32 flags = 0; + if (is_dark) { + flags |= telegram_api::payments_getStarsRevenueStats::DARK_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::payments_getStarsRevenueStats(flags, false /*ignored*/, std::move(input_peer)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetStarsRevenueStatsQuery: " << to_string(ptr); + promise_.set_value(td_api::make_object( + StatisticsManager::convert_stats_graph(std::move(ptr->revenue_graph_)), + convert_stars_revenue_status(std::move(ptr->status_)), + ptr->usd_rate_ > 0 ? clamp(ptr->usd_rate_ * 1e2, 1e-18, 1e18) : 1.3)); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsRevenueStatsQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetStarsRevenueWithdrawalUrlQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit GetStarsRevenueWithdrawalUrlQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, int64 star_count, + telegram_api::object_ptr input_check_password) { + dialog_id_ = dialog_id; + + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Have no access to the chat")); + } + + send_query(G()->net_query_creator().create(telegram_api::payments_getStarsRevenueWithdrawalUrl( + std::move(input_peer), star_count, std::move(input_check_password)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + promise_.set_value(std::move(result_ptr.ok_ref()->url_)); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStarsRevenueWithdrawalUrlQuery"); + promise_.set_error(std::move(status)); + } +}; + +StarManager::StarManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void StarManager::tear_down() { + parent_.reset(); +} + +Status StarManager::can_manage_stars(DialogId dialog_id, bool allow_self) const { + switch (dialog_id.get_type()) { + case DialogType::User: { + auto user_id = dialog_id.get_user_id(); + if (allow_self && user_id == td_->user_manager_->get_my_id()) { + break; + } + TRY_RESULT(bot_data, td_->user_manager_->get_bot_data(user_id)); + if (!bot_data.can_be_edited) { + return Status::Error(400, "The bot isn't owned"); + } + break; + } + case DialogType::Channel: { + auto channel_id = dialog_id.get_channel_id(); + if (!td_->chat_manager_->is_broadcast_channel(channel_id)) { + return Status::Error(400, "Chat is not a channel"); + } + if (!td_->chat_manager_->get_channel_permissions(channel_id).is_creator()) { + return Status::Error(400, "Not enough rights to withdraw stars"); + } + break; + } + default: + return Status::Error(400, "Unallowed chat specified"); + } + return Status::OK(); +} + +void StarManager::get_star_payment_options(Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +void StarManager::get_star_transactions(td_api::object_ptr owner_id, const string &offset, + int32 limit, td_api::object_ptr &&direction, + Promise> &&promise) { + TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); + TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); + if (limit < 0) { + return promise.set_error(Status::Error(400, "Limit must be non-negative")); + } + td_->create_handler(std::move(promise)) + ->send(dialog_id, offset, limit, std::move(direction)); +} + +void StarManager::refund_star_payment(UserId user_id, const string &telegram_payment_charge_id, + Promise &&promise) { + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); + td_->create_handler(std::move(promise)) + ->send(std::move(input_user), telegram_payment_charge_id); +} + +void StarManager::get_star_revenue_statistics(const td_api::object_ptr &owner_id, bool is_dark, + Promise> &&promise) { + TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); + TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id)); + td_->create_handler(std::move(promise))->send(dialog_id, is_dark); +} + +void StarManager::get_star_withdrawal_url(const td_api::object_ptr &owner_id, int64 star_count, + const string &password, Promise &&promise) { + TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); + TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id)); + if (password.empty()) { + return promise.set_error(Status::Error(400, "PASSWORD_HASH_INVALID")); + } + send_closure( + td_->password_manager_, &PasswordManager::get_input_check_password_srp, password, + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, star_count, promise = std::move(promise)]( + Result> result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &StarManager::send_get_star_withdrawal_url_query, dialog_id, star_count, + result.move_as_ok(), std::move(promise)); + })); +} + +void StarManager::send_get_star_withdrawal_url_query( + DialogId dialog_id, int64 star_count, + telegram_api::object_ptr input_check_password, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + td_->create_handler(std::move(promise)) + ->send(dialog_id, star_count, std::move(input_check_password)); +} + +void StarManager::on_update_stars_revenue_status( + telegram_api::object_ptr &&update) { + DialogId dialog_id(update->peer_); + if (can_manage_stars(dialog_id).is_error()) { + LOG(ERROR) << "Receive " << to_string(update); + return; + } + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + get_message_sender_object(td_, dialog_id, "updateStarRevenueStatus"), + convert_stars_revenue_status(std::move(update->status_)))); +} + +int64 StarManager::get_star_count(int64 amount, bool allow_negative) { + auto max_amount = static_cast(1) << 51; + if (amount < 0) { + if (!allow_negative) { + LOG(ERROR) << "Receive star amount = " << amount; + return 0; + } + if (amount < -max_amount) { + LOG(ERROR) << "Receive star amount = " << amount; + return -max_amount; + } + } + if (amount > max_amount) { + LOG(ERROR) << "Receive star amount = " << amount; + return max_amount; + } + return amount; +} + +} // namespace td diff --git a/td/telegram/StarManager.h b/td/telegram/StarManager.h new file mode 100644 index 000000000000..78391273eb8f --- /dev/null +++ b/td/telegram/StarManager.h @@ -0,0 +1,59 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/DialogId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" + +namespace td { + +class Td; + +class StarManager final : public Actor { + public: + StarManager(Td *td, ActorShared<> parent); + + void get_star_payment_options(Promise> &&promise); + + void get_star_transactions(td_api::object_ptr owner_id, const string &offset, int32 limit, + td_api::object_ptr &&direction, + Promise> &&promise); + + void refund_star_payment(UserId user_id, const string &telegram_payment_charge_id, Promise &&promise); + + void get_star_revenue_statistics(const td_api::object_ptr &owner_id, bool is_dark, + Promise> &&promise); + + void get_star_withdrawal_url(const td_api::object_ptr &owner_id, int64 star_count, + const string &password, Promise &&promise); + + void on_update_stars_revenue_status(telegram_api::object_ptr &&update); + + static int64 get_star_count(int64 amount, bool allow_negative = false); + + private: + void tear_down() final; + + Status can_manage_stars(DialogId dialog_id, bool allow_self = false) const; + + void send_get_star_withdrawal_url_query( + DialogId dialog_id, int64 star_count, + telegram_api::object_ptr input_check_password, Promise &&promise); + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/td/telegram/StatisticsManager.cpp b/td/telegram/StatisticsManager.cpp index ec433e14417d..8a993e45f3f2 100644 --- a/td/telegram/StatisticsManager.cpp +++ b/td/telegram/StatisticsManager.cpp @@ -325,7 +325,7 @@ class GetBroadcastRevenueStatsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastStatsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastRevenueStatsQuery"); promise_.set_error(std::move(status)); } }; @@ -415,18 +415,18 @@ class GetBroadcastRevenueTransactionsQuery final : public Td::ResultHandler { auto transaction = telegram_api::move_object_as(transaction_ptr); amount = get_amount(transaction->amount_, true); - auto state = [&]() -> td_api::object_ptr { + auto state = [&]() -> td_api::object_ptr { if (transaction->transaction_date_ > 0) { - return td_api::make_object(transaction->transaction_date_, - transaction->transaction_url_); + return td_api::make_object(transaction->transaction_date_, + transaction->transaction_url_); } if (transaction->pending_) { - return td_api::make_object(); + return td_api::make_object(); } if (!transaction->failed_) { LOG(ERROR) << "Transaction has unknown state"; } - return td_api::make_object(); + return td_api::make_object(); }(); return td_api::make_object( transaction->date_, transaction->provider_, std::move(state)); @@ -999,4 +999,9 @@ void StatisticsManager::get_channel_differences_if_needed( source); } +td_api::object_ptr StatisticsManager::convert_stats_graph( + telegram_api::object_ptr obj) { + return ::td::convert_stats_graph(std::move(obj)); +} + } // namespace td diff --git a/td/telegram/StatisticsManager.h b/td/telegram/StatisticsManager.h index 5f2bc24afdd8..0e635f197fc2 100644 --- a/td/telegram/StatisticsManager.h +++ b/td/telegram/StatisticsManager.h @@ -63,6 +63,9 @@ class StatisticsManager final : public Actor { Promise> promise, const char *source); + static td_api::object_ptr convert_stats_graph( + telegram_api::object_ptr obj); + private: void tear_down() final; diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index 1e8e39de9141..bf3d9b48861f 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -30,6 +30,7 @@ #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PhotoSizeSource.h" +#include "td/telegram/QuickReplyManager.h" #include "td/telegram/secret_api.h" #include "td/telegram/SecretChatLayer.h" #include "td/telegram/StickersManager.hpp" @@ -1702,7 +1703,7 @@ StickersManager::~StickersManager() { found_stickers_[0], found_stickers_[1], found_stickers_[2], found_sticker_sets_[0], found_sticker_sets_[1], found_sticker_sets_[2], emoji_language_codes_, emoji_language_code_versions_, emoji_language_code_last_difference_times_, reloaded_emoji_keywords_, premium_gift_messages_, dice_messages_, - emoji_messages_, custom_emoji_messages_, custom_emoji_to_sticker_id_); + dice_quick_reply_messages_, emoji_messages_, custom_emoji_messages_, custom_emoji_to_sticker_id_); } void StickersManager::start_up() { @@ -1867,7 +1868,7 @@ void StickersManager::load_special_sticker_set(SpecialStickerSet &sticker_set) { return; } sticker_set.is_being_loaded_ = true; - LOG(INFO) << "Load " << sticker_set.type_.type_ << " " << sticker_set.id_; + LOG(INFO) << "Load " << sticker_set.type_.type_ << ' ' << sticker_set.id_; if (sticker_set.id_.is_valid()) { auto s = get_sticker_set(sticker_set.id_); CHECK(s != nullptr); @@ -2004,16 +2005,28 @@ void StickersManager::on_load_special_sticker_set(const SpecialStickerSetType &t auto emoji = type.get_dice_emoji(); CHECK(!emoji.empty()); - auto it = dice_messages_.find(emoji); - if (it == dice_messages_.end()) { - return; + { + auto it = dice_messages_.find(emoji); + if (it != dice_messages_.end()) { + vector message_full_ids; + it->second.foreach([&](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); + CHECK(!message_full_ids.empty()); + for (const auto &message_full_id : message_full_ids) { + td_->messages_manager_->on_external_update_message_content(message_full_id, "on_load_special_sticker_set"); + } + } } - - vector message_full_ids; - it->second.foreach([&](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); - CHECK(!message_full_ids.empty()); - for (const auto &message_full_id : message_full_ids) { - td_->messages_manager_->on_external_update_message_content(message_full_id, "on_load_special_sticker_set"); + { + auto it = dice_quick_reply_messages_.find(emoji); + if (it != dice_quick_reply_messages_.end()) { + vector message_full_ids; + it->second.foreach( + [&](const QuickReplyMessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); + CHECK(!message_full_ids.empty()); + for (const auto &message_full_id : message_full_ids) { + td_->quick_reply_manager_->on_external_update_message_content(message_full_id, "on_load_special_sticker_set"); + } + } } } @@ -5747,6 +5760,7 @@ void StickersManager::on_update_sticker_sets(StickerType sticker_type) { void StickersManager::try_update_animated_emoji_messages() { auto sticker_set = get_animated_emoji_sticker_set(); vector message_full_ids; + vector quick_reply_message_full_ids; for (auto &it : emoji_messages_) { auto new_animated_sticker = get_animated_emoji_sticker(sticker_set, it.first); auto new_sound_file_id = get_animated_emoji_sound_file_id(it.first); @@ -5756,11 +5770,18 @@ void StickersManager::try_update_animated_emoji_messages() { it.second->sound_file_id_ = new_sound_file_id; it.second->message_full_ids_.foreach( [&](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); + it.second->quick_reply_message_full_ids_.foreach([&](const QuickReplyMessageFullId &message_full_id) { + quick_reply_message_full_ids.push_back(message_full_id); + }); } } for (const auto &message_full_id : message_full_ids) { td_->messages_manager_->on_external_update_message_content(message_full_id, "try_update_animated_emoji_messages"); } + for (const auto &message_full_id : quick_reply_message_full_ids) { + td_->quick_reply_manager_->on_external_update_message_content(message_full_id, + "try_update_animated_emoji_messages"); + } } void StickersManager::try_update_custom_emoji_messages(CustomEmojiId custom_emoji_id) { @@ -5770,15 +5791,22 @@ void StickersManager::try_update_custom_emoji_messages(CustomEmojiId custom_emoj } vector message_full_ids; + vector quick_reply_message_full_ids; auto new_sticker_id = get_custom_animated_emoji_sticker_id(custom_emoji_id); if (new_sticker_id != it->second->sticker_id_) { it->second->sticker_id_ = new_sticker_id; it->second->message_full_ids_.foreach( [&](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); + it->second->quick_reply_message_full_ids_.foreach([&](const QuickReplyMessageFullId &message_full_id) { + quick_reply_message_full_ids.push_back(message_full_id); + }); } for (const auto &message_full_id : message_full_ids) { td_->messages_manager_->on_external_update_message_content(message_full_id, "try_update_custom_emoji_messages"); } + for (const auto &message_full_id : quick_reply_message_full_ids) { + td_->quick_reply_manager_->on_external_update_message_content(message_full_id, "try_update_custom_emoji_messages"); + } } void StickersManager::try_update_premium_gift_messages() { @@ -5815,7 +5843,7 @@ void StickersManager::register_premium_gift(int32 months, MessageFullId message_ } bool is_inserted = premium_gift_messages.message_full_ids_.insert(message_full_id).second; - LOG_CHECK(is_inserted) << source << " " << months << " " << message_full_id; + LOG_CHECK(is_inserted) << source << ' ' << months << ' ' << message_full_id; } void StickersManager::unregister_premium_gift(int32 months, MessageFullId message_full_id, const char *source) { @@ -5828,7 +5856,7 @@ void StickersManager::unregister_premium_gift(int32 months, MessageFullId messag CHECK(it != premium_gift_messages_.end()); auto &message_ids = it->second->message_full_ids_; auto is_deleted = message_ids.erase(message_full_id) > 0; - LOG_CHECK(is_deleted) << source << " " << months << " " << message_full_id; + LOG_CHECK(is_deleted) << source << ' ' << months << ' ' << message_full_id; if (message_ids.empty()) { premium_gift_messages_.erase(it); @@ -5836,19 +5864,25 @@ void StickersManager::unregister_premium_gift(int32 months, MessageFullId messag } void StickersManager::register_dice(const string &emoji, int32 value, MessageFullId message_full_id, - const char *source) { + QuickReplyMessageFullId quick_reply_message_full_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } - LOG(INFO) << "Register dice " << emoji << " with value " << value << " from " << message_full_id << " from " - << source; - dice_messages_[emoji].insert(message_full_id); + LOG(INFO) << "Register dice " << emoji << " with value " << value << " from " << message_full_id << '/' + << quick_reply_message_full_id << " from " << source; + if (quick_reply_message_full_id.is_valid()) { + dice_quick_reply_messages_[emoji].insert(quick_reply_message_full_id); + } else { + CHECK(message_full_id.get_dialog_id().is_valid()); + dice_messages_[emoji].insert(message_full_id); + } if (!td::contains(dice_emojis_, emoji)) { - if (message_full_id.get_message_id().is_any_server() && - message_full_id.get_dialog_id().get_type() != DialogType::SecretChat) { + if (quick_reply_message_full_id.is_valid() || + (message_full_id.get_message_id().is_any_server() && + message_full_id.get_dialog_id().get_type() != DialogType::SecretChat)) { send_closure(G()->config_manager(), &ConfigManager::reget_app_config, Promise()); } return; @@ -5866,7 +5900,7 @@ void StickersManager::register_dice(const string &emoji, int32 value, MessageFul } if (need_load) { - LOG(INFO) << "Waiting for a dice sticker set needed in " << message_full_id; + LOG(INFO) << "Waiting for a dice sticker set needed in " << message_full_id << '/' << quick_reply_message_full_id; load_special_sticker_set(special_sticker_set); } else { // TODO reload once in a while @@ -5875,39 +5909,49 @@ void StickersManager::register_dice(const string &emoji, int32 value, MessageFul } void StickersManager::unregister_dice(const string &emoji, int32 value, MessageFullId message_full_id, - const char *source) { + QuickReplyMessageFullId quick_reply_message_full_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } - LOG(INFO) << "Unregister dice " << emoji << " with value " << value << " from " << message_full_id << " from " - << source; - auto &message_ids = dice_messages_[emoji]; - auto is_deleted = message_ids.erase(message_full_id) > 0; - LOG_CHECK(is_deleted) << source << " " << emoji << " " << value << " " << message_full_id; + LOG(INFO) << "Unregister dice " << emoji << " with value " << value << " from " << message_full_id << '/' + << quick_reply_message_full_id << " from " << source; + if (quick_reply_message_full_id.is_valid()) { + auto &message_ids = dice_quick_reply_messages_[emoji]; + auto is_deleted = message_ids.erase(quick_reply_message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << emoji << ' ' << value << ' ' << quick_reply_message_full_id; - if (message_ids.empty()) { - dice_messages_.erase(emoji); + if (message_ids.empty()) { + dice_quick_reply_messages_.erase(emoji); + } + } else { + auto &message_ids = dice_messages_[emoji]; + auto is_deleted = message_ids.erase(message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << emoji << ' ' << value << ' ' << message_full_id; + + if (message_ids.empty()) { + dice_messages_.erase(emoji); + } } } void StickersManager::register_emoji(const string &emoji, CustomEmojiId custom_emoji_id, MessageFullId message_full_id, - const char *source) { + QuickReplyMessageFullId quick_reply_message_full_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } - LOG(INFO) << "Register emoji " << emoji << " with " << custom_emoji_id << " from " << message_full_id << " from " - << source; + LOG(INFO) << "Register emoji " << emoji << " with " << custom_emoji_id << " from " << message_full_id << '/' + << quick_reply_message_full_id << " from " << source; if (custom_emoji_id.is_valid()) { auto &emoji_messages_ptr = custom_emoji_messages_[custom_emoji_id]; if (emoji_messages_ptr == nullptr) { emoji_messages_ptr = make_unique(); } auto &emoji_messages = *emoji_messages_ptr; - if (emoji_messages.message_full_ids_.empty()) { + if (emoji_messages.message_full_ids_.empty() && emoji_messages.quick_reply_message_full_ids_.empty()) { if (!disable_animated_emojis_ && custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { load_custom_emoji_sticker_from_database_force(custom_emoji_id); if (custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { @@ -5916,7 +5960,12 @@ void StickersManager::register_emoji(const string &emoji, CustomEmojiId custom_e } emoji_messages.sticker_id_ = get_custom_animated_emoji_sticker_id(custom_emoji_id); } - emoji_messages.message_full_ids_.insert(message_full_id); + if (quick_reply_message_full_id.is_valid()) { + emoji_messages.quick_reply_message_full_ids_.insert(quick_reply_message_full_id); + } else { + CHECK(message_full_id.get_dialog_id().is_valid()); + emoji_messages.message_full_ids_.insert(message_full_id); + } return; } @@ -5925,30 +5974,39 @@ void StickersManager::register_emoji(const string &emoji, CustomEmojiId custom_e emoji_messages_ptr = make_unique(); } auto &emoji_messages = *emoji_messages_ptr; - if (emoji_messages.message_full_ids_.empty()) { + if (emoji_messages.message_full_ids_.empty() && emoji_messages.quick_reply_message_full_ids_.empty()) { emoji_messages.animated_emoji_sticker_ = get_animated_emoji_sticker(emoji); emoji_messages.sound_file_id_ = get_animated_emoji_sound_file_id(emoji); } - emoji_messages.message_full_ids_.insert(message_full_id); + if (quick_reply_message_full_id.is_valid()) { + emoji_messages.quick_reply_message_full_ids_.insert(quick_reply_message_full_id); + } else { + CHECK(message_full_id.get_dialog_id().is_valid()); + emoji_messages.message_full_ids_.insert(message_full_id); + } } void StickersManager::unregister_emoji(const string &emoji, CustomEmojiId custom_emoji_id, - MessageFullId message_full_id, const char *source) { + MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source) { CHECK(!emoji.empty()); if (td_->auth_manager_->is_bot()) { return; } - LOG(INFO) << "Unregister emoji " << emoji << " with " << custom_emoji_id << " from " << message_full_id << " from " - << source; + LOG(INFO) << "Unregister emoji " << emoji << " with " << custom_emoji_id << " from " << message_full_id << '/' + << quick_reply_message_full_id << " from " << source; if (custom_emoji_id.is_valid()) { auto it = custom_emoji_messages_.find(custom_emoji_id); CHECK(it != custom_emoji_messages_.end()); - auto &message_full_ids = it->second->message_full_ids_; - auto is_deleted = message_full_ids.erase(message_full_id) > 0; - LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << message_full_id; - - if (message_full_ids.empty()) { + if (quick_reply_message_full_id.is_valid()) { + auto is_deleted = it->second->quick_reply_message_full_ids_.erase(quick_reply_message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << quick_reply_message_full_id; + } else { + auto is_deleted = it->second->message_full_ids_.erase(message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << message_full_id; + } + if (it->second->message_full_ids_.empty() && it->second->quick_reply_message_full_ids_.empty()) { custom_emoji_messages_.erase(it); } return; @@ -5956,11 +6014,14 @@ void StickersManager::unregister_emoji(const string &emoji, CustomEmojiId custom auto it = emoji_messages_.find(emoji); CHECK(it != emoji_messages_.end()); - auto &message_full_ids = it->second->message_full_ids_; - auto is_deleted = message_full_ids.erase(message_full_id) > 0; - LOG_CHECK(is_deleted) << source << ' ' << emoji << ' ' << message_full_id; - - if (message_full_ids.empty()) { + if (quick_reply_message_full_id.is_valid()) { + auto is_deleted = it->second->quick_reply_message_full_ids_.erase(quick_reply_message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << quick_reply_message_full_id; + } else { + auto is_deleted = it->second->message_full_ids_.erase(message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << message_full_id; + } + if (it->second->message_full_ids_.empty() && it->second->quick_reply_message_full_ids_.empty()) { emoji_messages_.erase(it); } } diff --git a/td/telegram/StickersManager.h b/td/telegram/StickersManager.h index 464ea95c4e85..961c6d918ac0 100644 --- a/td/telegram/StickersManager.h +++ b/td/telegram/StickersManager.h @@ -16,6 +16,7 @@ #include "td/telegram/MessageFullId.h" #include "td/telegram/PhotoFormat.h" #include "td/telegram/PhotoSize.h" +#include "td/telegram/QuickReplyMessageFullId.h" #include "td/telegram/SecretInputMedia.h" #include "td/telegram/SpecialStickerSetType.h" #include "td/telegram/StickerFormat.h" @@ -110,15 +111,17 @@ class StickersManager final : public Actor { void unregister_premium_gift(int32 months, MessageFullId message_full_id, const char *source); - void register_dice(const string &emoji, int32 value, MessageFullId message_full_id, const char *source); + void register_dice(const string &emoji, int32 value, MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source); - void unregister_dice(const string &emoji, int32 value, MessageFullId message_full_id, const char *source); + void unregister_dice(const string &emoji, int32 value, MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source); void register_emoji(const string &emoji, CustomEmojiId custom_emoji_id, MessageFullId message_full_id, - const char *source); + QuickReplyMessageFullId quick_reply_message_full_id, const char *source); void unregister_emoji(const string &emoji, CustomEmojiId custom_emoji_id, MessageFullId message_full_id, - const char *source); + QuickReplyMessageFullId quick_reply_message_full_id, const char *source); void get_animated_emoji(string emoji, bool is_recursive, Promise> &&promise); @@ -1132,9 +1135,11 @@ class StickersManager final : public Actor { FlatHashMap> premium_gift_messages_; FlatHashMap> dice_messages_; + FlatHashMap> dice_quick_reply_messages_; struct EmojiMessages { WaitFreeHashSet message_full_ids_; + WaitFreeHashSet quick_reply_message_full_ids_; std::pair animated_emoji_sticker_; FileId sound_file_id_; }; @@ -1142,6 +1147,7 @@ class StickersManager final : public Actor { struct CustomEmojiMessages { WaitFreeHashSet message_full_ids_; + WaitFreeHashSet quick_reply_message_full_ids_; FileId sticker_id_; }; FlatHashMap, CustomEmojiIdHash> custom_emoji_messages_; diff --git a/td/telegram/StoryManager.cpp b/td/telegram/StoryManager.cpp index e378bbb28ba5..b73365b32dd2 100644 --- a/td/telegram/StoryManager.cpp +++ b/td/telegram/StoryManager.cpp @@ -15,6 +15,7 @@ #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/Global.h" +#include "td/telegram/HashtagHints.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MediaArea.hpp" @@ -24,6 +25,7 @@ #include "td/telegram/NotificationId.h" #include "td/telegram/NotificationManager.h" #include "td/telegram/OptionManager.h" +#include "td/telegram/QuickReplyManager.h" #include "td/telegram/ReactionManager.h" #include "td/telegram/ReactionType.hpp" #include "td/telegram/ReportReason.h" @@ -703,6 +705,95 @@ class DeleteStoriesQuery final : public Td::ResultHandler { } }; +class SearchStoriesQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit SearchStoriesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(string hashtag, const string &offset, int32 limit) { + int32 flags = telegram_api::stories_searchPosts::HASHTAG_MASK; + send_query( + G()->net_query_creator().create(telegram_api::stories_searchPosts(flags, hashtag, nullptr, offset, limit))); + } + + void send(td_api::object_ptr &&address, const string &offset, int32 limit) { + int32 flags = telegram_api::stories_searchPosts::AREA_MASK; + + int32 address_flags = 0; + if (!address->state_.empty()) { + address_flags |= telegram_api::geoPointAddress::STATE_MASK; + } + if (!address->city_.empty()) { + address_flags |= telegram_api::geoPointAddress::CITY_MASK; + } + if (!address->street_.empty()) { + address_flags |= telegram_api::geoPointAddress::STREET_MASK; + } + + auto area = telegram_api::make_object( + telegram_api::mediaAreaGeoPoint::ADDRESS_MASK, + telegram_api::make_object(0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + telegram_api::make_object(0, 0.0, 0.0, 0, 0), + telegram_api::make_object(address_flags, address->country_code_, address->state_, + address->city_, address->street_)); + send_query(G()->net_query_creator().create( + telegram_api::stories_searchPosts(flags, string(), std::move(area), offset, limit))); + } + + void send(const string &venue_provider, const string &venue_id, const string &offset, int32 limit) { + int32 flags = telegram_api::stories_searchPosts::AREA_MASK; + auto area = telegram_api::make_object( + telegram_api::make_object(0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + telegram_api::make_object(0, 0.0, 0.0, 0, 0), string(), string(), venue_provider, + venue_id, string()); + send_query(G()->net_query_creator().create( + telegram_api::stories_searchPosts(flags, string(), std::move(area), offset, limit))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for SearchStoriesQuery: " << to_string(ptr); + td_->user_manager_->on_get_users(std::move(ptr->users_), "SearchStoriesQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "SearchStoriesQuery"); + + auto total_count = ptr->count_; + if (total_count < static_cast(ptr->stories_.size())) { + LOG(ERROR) << "Receive total count = " << total_count << " and " << ptr->stories_.size() << " stories"; + total_count = static_cast(ptr->stories_.size()); + } + vector> stories; + for (auto &found_story : ptr->stories_) { + DialogId owner_dialog_id(found_story->peer_); + auto story_id = td_->story_manager_->on_get_story(owner_dialog_id, std::move(found_story->story_)); + if (story_id.is_valid()) { + auto story_object = td_->story_manager_->get_story_object({owner_dialog_id, story_id}); + if (story_object == nullptr) { + LOG(ERROR) << "Receive deleted " << story_id << " from " << owner_dialog_id; + } else { + stories.push_back(std::move(story_object)); + } + } + } + + promise_.set_value(td_api::make_object(total_count, std::move(stories), ptr->next_offset_)); + } + + void on_error(Status status) final { + if (status.message() == "SEARCH_QUERY_EMPTY") { + return promise_.set_value(td_api::make_object()); + } + promise_.set_error(std::move(status)); + } +}; + class TogglePinnedStoriesToTopQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -1439,10 +1530,11 @@ StoryManager::StoryManager(Td *td, ActorShared<> parent) : td_(td), parent_(std: } StoryManager::~StoryManager() { - Scheduler::instance()->destroy_on_scheduler( - G()->get_gc_scheduler_id(), story_full_id_to_file_source_id_, stories_, stories_by_global_id_, - inaccessible_story_full_ids_, deleted_story_full_ids_, failed_to_load_story_full_ids_, story_messages_, - active_stories_, updated_active_stories_, max_read_story_ids_, failed_to_load_active_stories_); + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), story_full_id_to_file_source_id_, stories_, + stories_by_global_id_, inaccessible_story_full_ids_, + deleted_story_full_ids_, failed_to_load_story_full_ids_, story_messages_, + story_quick_reply_messages_, active_stories_, updated_active_stories_, + max_read_story_ids_, failed_to_load_active_stories_); } void StoryManager::start_up() { @@ -2539,6 +2631,54 @@ void StoryManager::on_get_dialog_expiring_stories(DialogId owner_dialog_id, } } +void StoryManager::search_hashtag_posts(string hashtag, string offset, int32 limit, + Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + if (limit > MAX_SEARCH_STORIES) { + limit = MAX_SEARCH_STORIES; + } + + bool is_cashtag = false; + if (hashtag[0] == '#' || hashtag[0] == '$') { + is_cashtag = (hashtag[0] == '$'); + hashtag = hashtag.substr(1); + } + if (hashtag.empty()) { + return promise.set_value(td_api::make_object()); + } + send_closure(is_cashtag ? td_->cashtag_search_hints_ : td_->hashtag_search_hints_, &HashtagHints::hashtag_used, + hashtag); + + td_->create_handler(std::move(promise)) + ->send(PSTRING() << (is_cashtag ? '$' : '#') << hashtag, offset, limit); +} + +void StoryManager::search_location_posts(td_api::object_ptr &&address, string offset, + int32 limit, Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + if (limit > MAX_SEARCH_STORIES) { + limit = MAX_SEARCH_STORIES; + } + + td_->create_handler(std::move(promise))->send(std::move(address), offset, limit); +} + +void StoryManager::search_venue_posts(string venue_provider, string venue_id, string offset, int32 limit, + Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + if (limit > MAX_SEARCH_STORIES) { + limit = MAX_SEARCH_STORIES; + } + + td_->create_handler(std::move(promise))->send(venue_provider, venue_id, offset, limit); +} + void StoryManager::set_pinned_stories(DialogId owner_dialog_id, vector story_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Write, "set_pinned_stories")); @@ -3157,27 +3297,45 @@ int32 StoryManager::get_story_duration(StoryFullId story_full_id) const { return get_story_content_duration(td_, content); } -void StoryManager::register_story(StoryFullId story_full_id, MessageFullId message_full_id, const char *source) { +void StoryManager::register_story(StoryFullId story_full_id, MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(story_full_id.is_server()); - LOG(INFO) << "Register " << story_full_id << " from " << message_full_id << " from " << source; - story_messages_[story_full_id].insert(message_full_id); + LOG(INFO) << "Register " << story_full_id << " from " << message_full_id << '/' << quick_reply_message_full_id + << " from " << source; + if (quick_reply_message_full_id.is_valid()) { + story_quick_reply_messages_[story_full_id].insert(quick_reply_message_full_id); + } else { + CHECK(message_full_id.get_dialog_id().is_valid()); + story_messages_[story_full_id].insert(message_full_id); + } } -void StoryManager::unregister_story(StoryFullId story_full_id, MessageFullId message_full_id, const char *source) { +void StoryManager::unregister_story(StoryFullId story_full_id, MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source) { if (td_->auth_manager_->is_bot()) { return; } CHECK(story_full_id.is_server()); - LOG(INFO) << "Unregister " << story_full_id << " from " << message_full_id << " from " << source; - auto &message_ids = story_messages_[story_full_id]; - auto is_deleted = message_ids.erase(message_full_id) > 0; - LOG_CHECK(is_deleted) << source << ' ' << story_full_id << ' ' << message_full_id; - if (message_ids.empty()) { - story_messages_.erase(story_full_id); + LOG(INFO) << "Unregister " << story_full_id << " from " << message_full_id << '/' << quick_reply_message_full_id + << " from " << source; + if (quick_reply_message_full_id.is_valid()) { + auto &message_ids = story_quick_reply_messages_[story_full_id]; + auto is_deleted = message_ids.erase(quick_reply_message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << story_full_id << ' ' << quick_reply_message_full_id; + if (message_ids.empty()) { + story_quick_reply_messages_.erase(story_full_id); + } + } else { + auto &message_ids = story_messages_[story_full_id]; + auto is_deleted = message_ids.erase(message_full_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << story_full_id << ' ' << message_full_id; + if (message_ids.empty()) { + story_messages_.erase(story_full_id); + } } } @@ -3813,6 +3971,19 @@ void StoryManager::on_story_changed(StoryFullId story_full_id, const Story *stor message_full_id, "on_story_changed", true); } } + + if (story_quick_reply_messages_.count(story_full_id) != 0) { + vector message_full_ids; + story_quick_reply_messages_[story_full_id].foreach( + [&message_full_ids](const QuickReplyMessageFullId &message_full_id) { + message_full_ids.push_back(message_full_id); + }); + CHECK(!message_full_ids.empty()); + for (const auto &message_full_id : message_full_ids) { + send_closure_later(G()->quick_reply_manager(), &QuickReplyManager::on_external_update_message_content, + message_full_id, "on_story_changed", true); + } + } } } diff --git a/td/telegram/StoryManager.h b/td/telegram/StoryManager.h index 57ac879d263a..75d543f20977 100644 --- a/td/telegram/StoryManager.h +++ b/td/telegram/StoryManager.h @@ -14,6 +14,7 @@ #include "td/telegram/MediaArea.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageFullId.h" +#include "td/telegram/QuickReplyMessageFullId.h" #include "td/telegram/ReactionType.h" #include "td/telegram/StoryDb.h" #include "td/telegram/StoryFullId.h" @@ -253,6 +254,15 @@ class StoryManager final : public Actor { void reload_dialog_expiring_stories(DialogId dialog_id); + void search_hashtag_posts(string hashtag, string offset, int32 limit, + Promise> &&promise); + + void search_location_posts(td_api::object_ptr &&address, string offset, int32 limit, + Promise> &&promise); + + void search_venue_posts(string venue_provider, string venue_id, string offset, int32 limit, + Promise> &&promise); + void set_pinned_stories(DialogId owner_dialog_id, vector story_ids, Promise &&promise); void open_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise); @@ -333,9 +343,11 @@ class StoryManager final : public Actor { int32 get_story_duration(StoryFullId story_full_id) const; - void register_story(StoryFullId story_full_id, MessageFullId message_full_id, const char *source); + void register_story(StoryFullId story_full_id, MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source); - void unregister_story(StoryFullId story_full_id, MessageFullId message_full_id, const char *source); + void unregister_story(StoryFullId story_full_id, MessageFullId message_full_id, + QuickReplyMessageFullId quick_reply_message_full_id, const char *source); td_api::object_ptr get_story_object(StoryFullId story_full_id) const; @@ -366,6 +378,8 @@ class StoryManager final : public Actor { class SendStoryLogEvent; class EditStoryLogEvent; + static constexpr int32 MAX_SEARCH_STORIES = 100; // server-side limit + static constexpr int32 OPENED_STORY_POLL_PERIOD = 60; static constexpr int32 VIEWED_STORY_POLL_PERIOD = 300; @@ -652,6 +666,9 @@ class StoryManager final : public Actor { WaitFreeHashMap, StoryFullIdHash> story_messages_; + WaitFreeHashMap, StoryFullIdHash> + story_quick_reply_messages_; + WaitFreeHashMap, DialogIdHash> active_stories_; WaitFreeHashSet updated_active_stories_; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index cd6a0dc5dfcf..e0fbd59b5bef 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -87,6 +87,7 @@ #include "td/telegram/Location.h" #include "td/telegram/Logging.h" #include "td/telegram/MessageCopyOptions.h" +#include "td/telegram/MessageEffectId.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" @@ -142,6 +143,7 @@ #include "td/telegram/SecureValue.h" #include "td/telegram/SentEmailCode.h" #include "td/telegram/SponsoredMessageManager.h" +#include "td/telegram/StarManager.h" #include "td/telegram/StateManager.h" #include "td/telegram/StatisticsManager.h" #include "td/telegram/StickerFormat.h" @@ -1437,34 +1439,6 @@ class SearchChatMessagesRequest final : public RequestActor<> { } }; -class SearchCallMessagesRequest final : public RequestActor<> { - string offset_; - int32 limit_; - bool only_missed_; - int64 random_id_; - - MessagesManager::FoundMessages messages_; - - void do_run(Promise &&promise) final { - messages_ = td_->messages_manager_->search_call_messages(offset_, limit_, only_missed_, random_id_, - get_tries() == 3, std::move(promise)); - } - - void do_send_result() final { - send_result(td_->messages_manager_->get_found_messages_object(messages_, "SearchCallMessagesRequest")); - } - - public: - SearchCallMessagesRequest(ActorShared td, uint64 request_id, string offset, int32 limit, bool only_missed) - : RequestActor(std::move(td), request_id) - , offset_(std::move(offset)) - , limit_(limit) - , only_missed_(only_missed) - , random_id_(0) { - set_tries(3); - } -}; - class GetActiveLiveLocationMessagesRequest final : public RequestActor<> { vector message_full_ids_; @@ -3126,6 +3100,7 @@ void Td::dec_actor_refcnt() { reset_manager(reaction_manager_, "ReactionManager"); reset_manager(saved_messages_manager_, "SavedMessagesManager"); reset_manager(sponsored_message_manager_, "SponsoredMessageManager"); + reset_manager(star_manager_, "StarManager"); reset_manager(statistics_manager_, "StatisticsManager"); reset_manager(stickers_manager_, "StickersManager"); reset_manager(story_manager_, "StoryManager"); @@ -3303,6 +3278,7 @@ void Td::clear() { reset_actor(ActorOwn(std::move(reaction_manager_actor_))); reset_actor(ActorOwn(std::move(saved_messages_manager_actor_))); reset_actor(ActorOwn(std::move(sponsored_message_manager_actor_))); + reset_actor(ActorOwn(std::move(star_manager_actor_))); reset_actor(ActorOwn(std::move(statistics_manager_actor_))); reset_actor(ActorOwn(std::move(stickers_manager_actor_))); reset_actor(ActorOwn(std::move(story_manager_actor_))); @@ -3853,6 +3829,8 @@ void Td::init_managers() { sponsored_message_manager_ = make_unique(this, create_reference()); sponsored_message_manager_actor_ = register_actor("SponsoredMessageManager", sponsored_message_manager_.get()); G()->set_sponsored_message_manager(sponsored_message_manager_actor_.get()); + star_manager_ = make_unique(this, create_reference()); + star_manager_actor_ = register_actor("StarManager", star_manager_.get()); statistics_manager_ = make_unique(this, create_reference()); statistics_manager_actor_ = register_actor("StatisticsManager", statistics_manager_.get()); stickers_manager_ = make_unique(this, create_reference()); @@ -5323,7 +5301,8 @@ void Td::on_request(uint64 id, td_api::searchSavedMessages &request) { void Td::on_request(uint64 id, const td_api::searchCallMessages &request) { CHECK_IS_USER(); - CREATE_REQUEST(SearchCallMessagesRequest, std::move(request.offset_), request.limit_, request.only_missed_); + CREATE_REQUEST_PROMISE(); + messages_manager_->search_call_messages(request.offset_, request.limit_, request.only_missed_, std::move(promise)); } void Td::on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request) { @@ -5333,18 +5312,52 @@ void Td::on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request) messages_manager_->search_outgoing_document_messages(request.query_, request.limit_, std::move(promise)); } -void Td::on_request(uint64 id, td_api::searchPublicHashtagMessages &request) { +void Td::on_request(uint64 id, td_api::searchPublicMessagesByTag &request) { CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.hashtag_); + CLEAN_INPUT_STRING(request.tag_); CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); - messages_manager_->search_hashtag_posts(std::move(request.hashtag_), std::move(request.offset_), request.limit_, + messages_manager_->search_hashtag_posts(std::move(request.tag_), std::move(request.offset_), request.limit_, std::move(promise)); } -void Td::on_request(uint64 id, td_api::getSearchedForHashtags &request) { +void Td::on_request(uint64 id, td_api::searchPublicStoriesByTag &request) { CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.prefix_); + CLEAN_INPUT_STRING(request.tag_); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + story_manager_->search_hashtag_posts(std::move(request.tag_), std::move(request.offset_), request.limit_, + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::searchPublicStoriesByLocation &request) { + CHECK_IS_USER(); + if (request.address_ == nullptr) { + return send_error_raw(id, 400, "Address must be non-empty"); + } + CLEAN_INPUT_STRING(request.address_->country_code_); + CLEAN_INPUT_STRING(request.address_->state_); + CLEAN_INPUT_STRING(request.address_->city_); + CLEAN_INPUT_STRING(request.address_->street_); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + story_manager_->search_location_posts(std::move(request.address_), std::move(request.offset_), request.limit_, + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::searchPublicStoriesByVenue &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.venue_provider_); + CLEAN_INPUT_STRING(request.venue_id_); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + story_manager_->search_venue_posts(std::move(request.venue_provider_), std::move(request.venue_id_), + std::move(request.offset_), request.limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getSearchedForTags &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.tag_prefix_); CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result> result) mutable { if (result.is_error()) { @@ -5353,22 +5366,23 @@ void Td::on_request(uint64 id, td_api::getSearchedForHashtags &request) { promise.set_value(td_api::make_object(result.move_as_ok())); } }); - send_closure(request.prefix_[0] == '$' ? cashtag_search_hints_ : hashtag_search_hints_, &HashtagHints::query, - std::move(request.prefix_), request.limit_, std::move(query_promise)); + send_closure(request.tag_prefix_[0] == '$' ? cashtag_search_hints_ : hashtag_search_hints_, &HashtagHints::query, + std::move(request.tag_prefix_), request.limit_, std::move(query_promise)); } -void Td::on_request(uint64 id, td_api::removeSearchedForHashtag &request) { +void Td::on_request(uint64 id, td_api::removeSearchedForTag &request) { CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.hashtag_); + CLEAN_INPUT_STRING(request.tag_); CREATE_OK_REQUEST_PROMISE(); - send_closure(request.hashtag_[0] == '$' ? cashtag_search_hints_ : hashtag_search_hints_, - &HashtagHints::remove_hashtag, std::move(request.hashtag_), std::move(promise)); + send_closure(request.tag_[0] == '$' ? cashtag_search_hints_ : hashtag_search_hints_, &HashtagHints::remove_hashtag, + std::move(request.tag_), std::move(promise)); } -void Td::on_request(uint64 id, td_api::clearSearchedForHashtags &request) { +void Td::on_request(uint64 id, td_api::clearSearchedForTags &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - send_closure(hashtag_search_hints_, &HashtagHints::clear, std::move(promise)); + send_closure(request.clear_cashtags_ ? cashtag_search_hints_ : hashtag_search_hints_, &HashtagHints::clear, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::deleteAllCallMessages &request) { @@ -5524,7 +5538,7 @@ void Td::on_request(uint64 id, td_api::setSavedMessagesTagLabel &request) { void Td::on_request(uint64 id, const td_api::getMessageEffect &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - reaction_manager_->get_message_effect(request.effect_id_, std::move(promise)); + reaction_manager_->get_message_effect(MessageEffectId(request.effect_id_), std::move(promise)); } void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) { @@ -5780,10 +5794,11 @@ void Td::on_request(uint64 id, td_api::setMessageFactCheck &request) { void Td::on_request(uint64 id, td_api::sendBusinessMessage &request) { CHECK_IS_BOT(); CREATE_REQUEST_PROMISE(); - business_connection_manager_->send_message( - BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), - std::move(request.reply_to_), request.disable_notification_, request.protect_content_, request.effect_id_, - std::move(request.reply_markup_), std::move(request.input_message_content_), std::move(promise)); + business_connection_manager_->send_message(BusinessConnectionId(std::move(request.business_connection_id_)), + DialogId(request.chat_id_), std::move(request.reply_to_), + request.disable_notification_, request.protect_content_, + MessageEffectId(request.effect_id_), std::move(request.reply_markup_), + std::move(request.input_message_content_), std::move(promise)); } void Td::on_request(uint64 id, td_api::sendBusinessMessageAlbum &request) { @@ -5791,8 +5806,60 @@ void Td::on_request(uint64 id, td_api::sendBusinessMessageAlbum &request) { CREATE_REQUEST_PROMISE(); business_connection_manager_->send_message_album( BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), - std::move(request.reply_to_), request.disable_notification_, request.protect_content_, request.effect_id_, - std::move(request.input_message_contents_), std::move(promise)); + std::move(request.reply_to_), request.disable_notification_, request.protect_content_, + MessageEffectId(request.effect_id_), std::move(request.input_message_contents_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editBusinessMessageText &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->edit_business_message_text( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + MessageId(request.message_id_), std::move(request.reply_markup_), std::move(request.input_message_content_), + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editBusinessMessageLiveLocation &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->edit_business_message_live_location( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + MessageId(request.message_id_), std::move(request.reply_markup_), std::move(request.location_), + request.live_period_, request.heading_, request.proximity_alert_radius_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editBusinessMessageMedia &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->edit_business_message_media( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + MessageId(request.message_id_), std::move(request.reply_markup_), std::move(request.input_message_content_), + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editBusinessMessageCaption &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->edit_business_message_caption( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + MessageId(request.message_id_), std::move(request.reply_markup_), std::move(request.caption_), + request.show_caption_above_media_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editBusinessMessageReplyMarkup &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->edit_business_message_reply_markup( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + MessageId(request.message_id_), std::move(request.reply_markup_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::stopBusinessPoll &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->stop_poll(BusinessConnectionId(std::move(request.business_connection_id_)), + DialogId(request.chat_id_), MessageId(request.message_id_), + std::move(request.reply_markup_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request) { @@ -8667,6 +8734,26 @@ void Td::on_request(uint64 id, const td_api::getChatRevenueTransactions &request std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getStarRevenueStatistics &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + star_manager_->get_star_revenue_statistics(request.owner_id_, request.is_dark_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getStarWithdrawalUrl &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(td_api::make_object(result.move_as_ok())); + } + }); + star_manager_->get_star_withdrawal_url(request.owner_id_, request.star_count_, request.password_, + std::move(query_promise)); +} + void Td::on_request(uint64 id, const td_api::getMessageStatistics &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -9089,7 +9176,7 @@ void Td::on_request(uint64 id, td_api::refundStarPayment &request) { CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.telegram_payment_charge_id_); CREATE_OK_REQUEST_PROMISE(); - refund_star_payment(this, UserId(request.user_id_), request.telegram_payment_charge_id_, std::move(promise)); + star_manager_->refund_star_payment(UserId(request.user_id_), request.telegram_payment_charge_id_, std::move(promise)); } void Td::on_request(uint64 id, td_api::getPassportElement &request) { @@ -9413,14 +9500,14 @@ void Td::on_request(uint64 id, const td_api::getPremiumGiveawayInfo &request) { void Td::on_request(uint64 id, const td_api::getStarPaymentOptions &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - get_star_payment_options(this, std::move(promise)); + star_manager_->get_star_payment_options(std::move(promise)); } void Td::on_request(uint64 id, td_api::getStarTransactions &request) { - CHECK_IS_USER(); CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); - get_star_transactions(this, request.offset_, std::move(request.direction_), std::move(promise)); + star_manager_->get_star_transactions(std::move(request.owner_id_), request.offset_, request.limit_, + std::move(request.direction_), std::move(promise)); } void Td::on_request(uint64 id, td_api::canPurchaseFromStore &request) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 23823c863a9d..41cf6252be68 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -90,6 +90,7 @@ class SavedMessagesManager; class SecureManager; class SecretChatsManager; class SponsoredMessageManager; +class StarManager; class StateManager; class StatisticsManager; class StickersManager; @@ -247,6 +248,8 @@ class Td final : public Actor { ActorOwn saved_messages_manager_actor_; unique_ptr sponsored_message_manager_; ActorOwn sponsored_message_manager_actor_; + unique_ptr star_manager_; + ActorOwn star_manager_actor_; unique_ptr statistics_manager_; ActorOwn statistics_manager_actor_; unique_ptr stickers_manager_; @@ -798,13 +801,19 @@ class Td final : public Actor { void on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request); - void on_request(uint64 id, td_api::searchPublicHashtagMessages &request); + void on_request(uint64 id, td_api::searchPublicMessagesByTag &request); - void on_request(uint64 id, td_api::getSearchedForHashtags &request); + void on_request(uint64 id, td_api::searchPublicStoriesByTag &request); - void on_request(uint64 id, td_api::removeSearchedForHashtag &request); + void on_request(uint64 id, td_api::searchPublicStoriesByLocation &request); - void on_request(uint64 id, td_api::clearSearchedForHashtags &request); + void on_request(uint64 id, td_api::searchPublicStoriesByVenue &request); + + void on_request(uint64 id, td_api::getSearchedForTags &request); + + void on_request(uint64 id, td_api::removeSearchedForTag &request); + + void on_request(uint64 id, td_api::clearSearchedForTags &request); void on_request(uint64 id, const td_api::deleteAllCallMessages &request); @@ -910,6 +919,18 @@ class Td final : public Actor { void on_request(uint64 id, td_api::sendBusinessMessageAlbum &request); + void on_request(uint64 id, td_api::editBusinessMessageText &request); + + void on_request(uint64 id, td_api::editBusinessMessageLiveLocation &request); + + void on_request(uint64 id, td_api::editBusinessMessageMedia &request); + + void on_request(uint64 id, td_api::editBusinessMessageCaption &request); + + void on_request(uint64 id, td_api::editBusinessMessageReplyMarkup &request); + + void on_request(uint64 id, td_api::stopBusinessPoll &request); + void on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request); void on_request(uint64 id, const td_api::setQuickReplyShortcutName &request); @@ -1676,6 +1697,10 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatRevenueTransactions &request); + void on_request(uint64 id, const td_api::getStarRevenueStatistics &request); + + void on_request(uint64 id, const td_api::getStarWithdrawalUrl &request); + void on_request(uint64 id, const td_api::getMessageStatistics &request); void on_request(uint64 id, const td_api::getStoryStatistics &request); diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 455dc2177181..4e633e6f0006 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -66,6 +66,7 @@ #include "td/telegram/SecretChatsManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/SpecialStickerSetType.h" +#include "td/telegram/StarManager.h" #include "td/telegram/StateManager.h" #include "td/telegram/StatisticsManager.h" #include "td/telegram/StickerListType.h" @@ -4148,6 +4149,14 @@ void UpdatesManager::on_update(tl_object_ptr update, + Promise &&promise) { + td_->callback_queries_manager_->on_new_business_query( + update->query_id_, UserId(update->user_id_), std::move(update->connection_id_), std::move(update->message_), + std::move(update->reply_to_message_), std::move(update->data_), update->chat_instance_); + promise.set_value(Unit()); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->stickers_manager_->reload_favorite_stickers(true); promise.set_value(Unit()); @@ -4561,11 +4570,13 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - if (update->balance_ < 0) { - LOG(ERROR) << "Receive " << update->balance_ << " stars"; - update->balance_ = 0; - } - send_closure(G()->td(), &Td::send_update, td_api::make_object(update->balance_)); + send_closure(G()->td(), &Td::send_update, + td_api::make_object(StarManager::get_star_count(update->balance_, true))); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->star_manager_->on_update_stars_revenue_status(std::move(update)); promise.set_value(Unit()); } diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index a98801110edd..832753936332 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -565,6 +565,7 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); @@ -673,6 +674,8 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + // unsupported updates void on_update(tl_object_ptr update, Promise &&promise); diff --git a/td/telegram/Version.h b/td/telegram/Version.h index 062d93028970..dfd6c270911c 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -10,7 +10,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 181; +constexpr int32 MTPROTO_LAYER = 182; enum class Version : int32 { Initial, // 0 diff --git a/td/telegram/WebPagesManager.cpp b/td/telegram/WebPagesManager.cpp index 6c9b3d788fb5..7130a0925e2a 100644 --- a/td/telegram/WebPagesManager.cpp +++ b/td/telegram/WebPagesManager.cpp @@ -26,6 +26,7 @@ #include "td/telegram/MessagesManager.h" #include "td/telegram/Photo.h" #include "td/telegram/PhotoFormat.h" +#include "td/telegram/QuickReplyManager.h" #include "td/telegram/StickerFormat.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StickersManager.hpp" @@ -468,7 +469,8 @@ void WebPagesManager::tear_down() { WebPagesManager::~WebPagesManager() { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), web_pages_, web_page_messages_, - url_to_web_page_id_, url_to_file_source_id_); + web_page_quick_reply_messages_, url_to_web_page_id_, + url_to_file_source_id_); } string WebPagesManager::get_web_page_url(const tl_object_ptr &web_page_ptr) { @@ -879,11 +881,38 @@ void WebPagesManager::unregister_web_page(WebPageId web_page_id, MessageFullId m if (message_ids.empty()) { web_page_messages_.erase(web_page_id); - if (pending_get_web_pages_.count(web_page_id) == 0) { - pending_web_pages_timeout_.cancel_timeout(web_page_id.get()); - } else { - LOG(INFO) << "Still waiting for " << web_page_id; - } + } +} + +void WebPagesManager::register_quick_reply_web_page(WebPageId web_page_id, QuickReplyMessageFullId message_full_id, + const char *source) { + if (!web_page_id.is_valid()) { + return; + } + + LOG(INFO) << "Register " << web_page_id << " from " << message_full_id << " from " << source; + bool is_inserted = web_page_quick_reply_messages_[web_page_id].insert(message_full_id).second; + LOG_CHECK(is_inserted) << source << " " << web_page_id << " " << message_full_id; + + if (!have_web_page_force(web_page_id)) { + LOG(INFO) << "Waiting for " << web_page_id << " needed in " << message_full_id; + pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), 1.0); + } +} + +void WebPagesManager::unregister_quick_reply_web_page(WebPageId web_page_id, QuickReplyMessageFullId message_full_id, + const char *source) { + if (!web_page_id.is_valid()) { + return; + } + + LOG(INFO) << "Unregister " << web_page_id << " from " << message_full_id << " from " << source; + auto &message_ids = web_page_quick_reply_messages_[web_page_id]; + auto is_deleted = message_ids.erase(message_full_id) > 0; + LOG_CHECK(is_deleted) << source << " " << web_page_id << " " << message_full_id; + + if (message_ids.empty()) { + web_page_quick_reply_messages_.erase(web_page_id); } } @@ -1499,37 +1528,67 @@ tl_object_ptr WebPagesManager::get_web_page_instant_ void WebPagesManager::on_web_page_changed(WebPageId web_page_id, bool have_web_page) { LOG(INFO) << "Updated " << web_page_id; - auto it = web_page_messages_.find(web_page_id); - if (it != web_page_messages_.end()) { - vector message_full_ids; - for (const auto &message_full_id : it->second) { - message_full_ids.push_back(message_full_id); - } - CHECK(!message_full_ids.empty()); - for (const auto &message_full_id : message_full_ids) { - if (!have_web_page) { - td_->messages_manager_->delete_pending_message_web_page(message_full_id); - } else { - td_->messages_manager_->on_external_update_message_content(message_full_id, "on_web_page_changed"); + { + auto it = web_page_messages_.find(web_page_id); + if (it != web_page_messages_.end()) { + vector message_full_ids; + for (const auto &message_full_id : it->second) { + message_full_ids.push_back(message_full_id); + } + CHECK(!message_full_ids.empty()); + for (const auto &message_full_id : message_full_ids) { + if (!have_web_page) { + td_->messages_manager_->delete_pending_message_web_page(message_full_id); + } else { + td_->messages_manager_->on_external_update_message_content(message_full_id, "on_web_page_changed"); + } + } + + // don't check that on_external_update_message_content doesn't load new messages + if (!have_web_page && web_page_messages_.count(web_page_id) != 0) { + vector new_message_full_ids; + for (const auto &message_full_id : web_page_messages_[web_page_id]) { + new_message_full_ids.push_back(message_full_id); + } + LOG(FATAL) << message_full_ids << ' ' << new_message_full_ids; } } + } + { + auto it = web_page_quick_reply_messages_.find(web_page_id); + if (it != web_page_quick_reply_messages_.end()) { + vector message_full_ids; + for (const auto &message_full_id : it->second) { + message_full_ids.push_back(message_full_id); + } + CHECK(!message_full_ids.empty()); + for (const auto &message_full_id : message_full_ids) { + if (!have_web_page) { + td_->quick_reply_manager_->delete_pending_message_web_page(message_full_id); + } else { + td_->quick_reply_manager_->on_external_update_message_content(message_full_id, "on_web_page_changed"); + } + } - // don't check that on_external_update_message_content doesn't load new messages - if (!have_web_page && web_page_messages_.count(web_page_id) != 0) { - vector new_message_full_ids; - for (const auto &message_full_id : web_page_messages_[web_page_id]) { - new_message_full_ids.push_back(message_full_id); + // don't check that on_external_update_message_content doesn't load new messages + if (!have_web_page && web_page_quick_reply_messages_.count(web_page_id) != 0) { + vector new_message_full_ids; + for (const auto &message_full_id : web_page_quick_reply_messages_[web_page_id]) { + new_message_full_ids.push_back(message_full_id); + } + LOG(FATAL) << message_full_ids << ' ' << new_message_full_ids; } - LOG(FATAL) << message_full_ids << ' ' << new_message_full_ids; } } - auto get_it = pending_get_web_pages_.find(web_page_id); - if (get_it != pending_get_web_pages_.end()) { - auto requests = std::move(get_it->second); - pending_get_web_pages_.erase(get_it); - for (auto &request : requests) { - on_get_web_page_preview_success(std::move(request.first), have_web_page ? web_page_id : WebPageId(), - std::move(request.second)); + { + auto it = pending_get_web_pages_.find(web_page_id); + if (it != pending_get_web_pages_.end()) { + auto requests = std::move(it->second); + pending_get_web_pages_.erase(it); + for (auto &request : requests) { + on_get_web_page_preview_success(std::move(request.first), have_web_page ? web_page_id : WebPageId(), + std::move(request.second)); + } } } pending_web_pages_timeout_.cancel_timeout(web_page_id.get()); @@ -1578,27 +1637,42 @@ void WebPagesManager::on_pending_web_page_timeout(WebPageId web_page_id) { LOG(INFO) << "Process timeout for " << web_page_id; int32 count = 0; - auto it = web_page_messages_.find(web_page_id); - if (it != web_page_messages_.end()) { - vector message_full_ids; - for (const auto &message_full_id : it->second) { - if (message_full_id.get_dialog_id().get_type() != DialogType::SecretChat) { - message_full_ids.push_back(message_full_id); + { + auto it = web_page_messages_.find(web_page_id); + if (it != web_page_messages_.end()) { + vector message_full_ids; + for (const auto &message_full_id : it->second) { + if (message_full_id.get_dialog_id().get_type() != DialogType::SecretChat) { + message_full_ids.push_back(message_full_id); + } + count++; + } + if (!message_full_ids.empty()) { + send_closure_later(G()->messages_manager(), &MessagesManager::get_messages_from_server, + std::move(message_full_ids), Promise(), "on_pending_web_page_timeout", nullptr); } - count++; } - if (!message_full_ids.empty()) { - send_closure_later(G()->messages_manager(), &MessagesManager::get_messages_from_server, - std::move(message_full_ids), Promise(), "on_pending_web_page_timeout", nullptr); + } + { + auto it = web_page_quick_reply_messages_.find(web_page_id); + if (it != web_page_quick_reply_messages_.end()) { + for (const auto &message_full_id : it->second) { + send_closure_later(G()->quick_reply_manager(), &QuickReplyManager::reload_quick_reply_message, + message_full_id.get_quick_reply_shortcut_id(), message_full_id.get_message_id(), + Promise()); + count++; + } } } - auto get_it = pending_get_web_pages_.find(web_page_id); - if (get_it != pending_get_web_pages_.end()) { - auto requests = std::move(get_it->second); - pending_get_web_pages_.erase(get_it); - for (auto &request : requests) { - request.second.set_error(Status::Error(500, "Request timeout exceeded")); - count++; + { + auto it = pending_get_web_pages_.find(web_page_id); + if (it != pending_get_web_pages_.end()) { + auto requests = std::move(it->second); + pending_get_web_pages_.erase(it); + for (auto &request : requests) { + request.second.set_error(Status::Error(500, "Request timeout exceeded")); + count++; + } } } if (count == 0) { diff --git a/td/telegram/WebPagesManager.h b/td/telegram/WebPagesManager.h index 6ed1a6176dd9..250fcc6d7449 100644 --- a/td/telegram/WebPagesManager.h +++ b/td/telegram/WebPagesManager.h @@ -11,6 +11,7 @@ #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/MessageFullId.h" +#include "td/telegram/QuickReplyMessageFullId.h" #include "td/telegram/StoryFullId.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -64,6 +65,12 @@ class WebPagesManager final : public Actor { void unregister_web_page(WebPageId web_page_id, MessageFullId message_full_id, const char *source); + void register_quick_reply_web_page(WebPageId web_page_id, QuickReplyMessageFullId message_full_id, + const char *source); + + void unregister_quick_reply_web_page(WebPageId web_page_id, QuickReplyMessageFullId message_full_id, + const char *source); + bool have_web_page(WebPageId web_page_id) const; bool have_web_page_force(WebPageId web_page_id); @@ -198,6 +205,8 @@ class WebPagesManager final : public Actor { FlatHashMap load_web_page_instant_view_queries_; FlatHashMap, WebPageIdHash> web_page_messages_; + FlatHashMap, WebPageIdHash> + web_page_quick_reply_messages_; FlatHashMap, Promise>>>, diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 265aeeef9436..ee5003680387 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -955,8 +955,11 @@ class CliClient final : public Actor { if (!reply_quote_.empty()) { quote = td_api::make_object(as_formatted_text(reply_quote_), reply_quote_position_); } - return td_api::make_object(reply_chat_id_, reply_message_id_, - std::move(quote)); + if (reply_chat_id_ == 0) { + return td_api::make_object(reply_message_id_, std::move(quote)); + } + return td_api::make_object(reply_chat_id_, reply_message_id_, + std::move(quote)); } if (reply_story_chat_id_ != 0 || reply_story_id_ != 0) { return td_api::make_object(reply_story_chat_id_, reply_story_id_); @@ -1355,11 +1358,12 @@ class CliClient final : public Actor { } auto position = td_api::make_object( Random::fast(1, 99) * 0.01, Random::fast(1, 99) * 0.01, Random::fast(1, 99) * 0.01, - Random::fast(1, 99) * 0.01, Random::fast(0, 360)); + Random::fast(1, 99) * 0.01, Random::fast(0, 360), Random::fast(1, 19) * 0.01); td_api::object_ptr type; if (area == "l") { type = td_api::make_object( - td_api::make_object(Random::fast(-50, 50), Random::fast(-50, 50), 0.0)); + td_api::make_object(Random::fast(-50, 50), Random::fast(-50, 50), 0.0), + td_api::make_object("US", "ZZ", "Deniles", "Road")); } else if (area[0] == 'v') { string query_id; string result_id; @@ -1379,6 +1383,8 @@ class CliClient final : public Actor { std::tie(chat_id, message_id) = split(area.substr(1), ':'); type = td_api::make_object(to_integer(chat_id), as_message_id(message_id)); + } else if (area[0] == 'u') { + type = td_api::make_object(area.substr(1)); } result->areas_.push_back(td_api::make_object(std::move(position), std::move(type))); } @@ -3058,23 +3064,48 @@ class CliClient final : public Actor { SearchQuery query; get_args(args, query); send_request(td_api::make_object(query.query, query.limit)); - } else if (op == "sphm") { - string hashtag; + } else if (op == "spmbt") { + string tag; string limit; string offset; - get_args(args, hashtag, limit, offset); - send_request(td_api::make_object(hashtag, offset, as_limit(limit))); + get_args(args, tag, limit, offset); + send_request(td_api::make_object(tag, offset, as_limit(limit))); + } else if (op == "spsbt") { + string tag; + string limit; + string offset; + get_args(args, tag, limit, offset); + send_request(td_api::make_object(tag, offset, as_limit(limit))); + } else if (op == "spsbl") { + string country_code; + string state; + string city; + string street; + string venue_id; + string limit; + string offset; + get_args(args, country_code, state, city, street, limit, offset); + send_request(td_api::make_object( + td_api::make_object(country_code, state, city, street), offset, as_limit(limit))); + } else if (op == "spsbv") { + string venue_provider; + string venue_id; + string limit; + string offset; + get_args(args, venue_provider, venue_id, limit, offset); + send_request( + td_api::make_object(venue_provider, venue_id, offset, as_limit(limit))); } else if (op == "gsfh") { - string hashtag; + string tag_prefix; string limit; - get_args(args, hashtag, limit); - send_request(td_api::make_object(hashtag, as_limit(limit))); + get_args(args, tag_prefix, limit); + send_request(td_api::make_object(tag_prefix, as_limit(limit))); } else if (op == "rsfh") { - string hashtag; - get_args(args, hashtag); - send_request(td_api::make_object(hashtag)); - } else if (op == "csfh") { - send_request(td_api::make_object()); + string tag; + get_args(args, tag); + send_request(td_api::make_object(tag)); + } else if (op == "csfh" || op == "csfc") { + send_request(td_api::make_object(op == "csfc")); } else if (op == "DeleteAllCallMessages") { bool revoke = as_bool(args); send_request(td_api::make_object(revoke)); @@ -3405,13 +3436,18 @@ class CliClient final : public Actor { } else if (op == "gspo") { send_request(td_api::make_object()); } else if (op == "gsta" || op == "gsti" || op == "gsto") { + string owner_id; + string offset; + string limit; + get_args(args, owner_id, offset, limit); td_api::object_ptr direction; if (op == "gsti") { direction = td_api::make_object(); } else if (op == "gsto") { direction = td_api::make_object(); } - send_request(td_api::make_object(args, std::move(direction))); + send_request(td_api::make_object(as_message_sender(owner_id), std::move(direction), + offset, as_limit(limit))); } else if (op == "cpfs" || op == "cpfsb") { UserId user_id; string currency; @@ -4474,7 +4510,8 @@ class CliClient final : public Actor { draft_message = td_api::make_object( std::move(reply_to), 0, td_api::make_object(as_formatted_text(message, std::move(entities)), - get_link_preview_options(), false)); + get_link_preview_options(), false), + message_effect_id_); } send_request( td_api::make_object(chat_id, message_thread_id_, std::move(draft_message))); @@ -4487,18 +4524,19 @@ class CliClient final : public Actor { td_api::make_object( nullptr, 0, td_api::make_object(as_input_file(video_path), nullptr, 10, 5, - get_message_self_destruct_type())))); + get_message_self_destruct_type()), + message_effect_id_))); } else if (op == "scdmvoice") { ChatId chat_id; string voice_path; get_args(args, chat_id, voice_path); send_request(td_api::make_object( chat_id, message_thread_id_, - td_api::make_object( - nullptr, 0, - td_api::make_object(as_input_file(voice_path), 0, "abacaba", - as_caption("voice caption"), - get_message_self_destruct_type())))); + td_api::make_object(nullptr, 0, + td_api::make_object( + as_input_file(voice_path), 0, "abacaba", + as_caption("voice caption"), get_message_self_destruct_type()), + message_effect_id_))); } else if (op == "cadm") { send_request(td_api::make_object()); } else if (op == "tchpc") { @@ -4589,7 +4627,7 @@ class CliClient final : public Actor { double duration; string sticker_file_ids; bool protect_content; - get_args(args, chat_id, video, caption, areas, rules, active_period, duration, sticker_file_ids, protect_content); + get_args(args, chat_id, video, caption, rules, areas, active_period, duration, sticker_file_ids, protect_content); send_request(td_api::make_object( chat_id, td_api::make_object(as_input_file(video), @@ -5003,9 +5041,14 @@ class CliClient final : public Actor { MessageId message_id; string message; get_args(args, chat_id, message_id, message); - send_request(td_api::make_object( - chat_id, message_id, nullptr, - td_api::make_object(as_formatted_text(message), get_link_preview_options(), true))); + auto input_text = + td_api::make_object(as_formatted_text(message), get_link_preview_options(), true); + if (!business_connection_id_.empty()) { + send_request(td_api::make_object(business_connection_id_, chat_id, message_id, + nullptr, std::move(input_text))); + } else { + send_request(td_api::make_object(chat_id, message_id, nullptr, std::move(input_text))); + } } else if (op == "eqrm") { ShortcutId shortcut_id; MessageId message_id; @@ -5036,9 +5079,15 @@ class CliClient final : public Actor { MessageId message_id; string document; get_args(args, chat_id, message_id, document); - send_request(td_api::make_object( - chat_id, message_id, nullptr, - td_api::make_object(as_input_file(document), nullptr, false, as_caption("")))); + auto input_document = + td_api::make_object(as_input_file(document), nullptr, false, as_caption("")); + if (!business_connection_id_.empty()) { + send_request(td_api::make_object(business_connection_id_, chat_id, message_id, + nullptr, std::move(input_document))); + } else { + send_request( + td_api::make_object(chat_id, message_id, nullptr, std::move(input_document))); + } } else if (op == "eqrmd") { ShortcutId shortcut_id; MessageId message_id; @@ -5052,11 +5101,16 @@ class CliClient final : public Actor { MessageId message_id; string photo; get_args(args, chat_id, message_id, photo); - send_request(td_api::make_object( - chat_id, message_id, nullptr, - td_api::make_object(as_input_file(photo), as_input_thumbnail(photo), Auto(), 0, 0, - as_caption(""), show_caption_above_media_, - get_message_self_destruct_type(), has_spoiler_))); + auto input_photo = td_api::make_object( + as_input_file(photo), as_input_thumbnail(photo), Auto(), 0, 0, as_caption(""), show_caption_above_media_, + get_message_self_destruct_type(), has_spoiler_); + if (!business_connection_id_.empty()) { + send_request(td_api::make_object(business_connection_id_, chat_id, message_id, + nullptr, std::move(input_photo))); + } else { + send_request( + td_api::make_object(chat_id, message_id, nullptr, std::move(input_photo))); + } } else if (op == "eqrmp") { ShortcutId shortcut_id; MessageId message_id; @@ -5073,11 +5127,16 @@ class CliClient final : public Actor { string video; string thumbnail; get_args(args, chat_id, message_id, video, thumbnail); - send_request(td_api::make_object( - chat_id, message_id, nullptr, - td_api::make_object(as_input_file(video), as_input_thumbnail(thumbnail), Auto(), 1, - 2, 3, true, as_caption(""), show_caption_above_media_, - get_message_self_destruct_type(), has_spoiler_))); + auto input_video = td_api::make_object( + as_input_file(video), as_input_thumbnail(thumbnail), Auto(), 1, 2, 3, true, as_caption(""), + show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_); + if (!business_connection_id_.empty()) { + send_request(td_api::make_object(business_connection_id_, chat_id, message_id, + nullptr, std::move(input_video))); + } else { + send_request( + td_api::make_object(chat_id, message_id, nullptr, std::move(input_video))); + } } else if (op == "emll") { ChatId chat_id; MessageId message_id; @@ -5260,6 +5319,12 @@ class CliClient final : public Actor { get_args(args, chat_id, message_id); send_request(td_api::make_object( chat_id, message_id, td_api::make_object(""))); + } else if (op == "acq" || op == "acqa") { + int64 callback_query_id; + string text; + get_args(args, callback_query_id, text); + send_request( + td_api::make_object(callback_query_id, text, op == "acqa", string(), 0)); } else { op_not_found_count++; } @@ -5906,7 +5971,12 @@ class CliClient final : public Actor { ChatId chat_id; MessageId message_id; get_args(args, chat_id, message_id); - send_request(td_api::make_object(chat_id, message_id, nullptr)); + if (!business_connection_id_.empty()) { + send_request( + td_api::make_object(business_connection_id_, chat_id, message_id, nullptr)); + } else { + send_request(td_api::make_object(chat_id, message_id, nullptr)); + } } else { op_not_found_count++; } @@ -6710,6 +6780,18 @@ class CliClient final : public Actor { string limit; get_args(args, chat_id, offset, limit); send_request(td_api::make_object(chat_id, offset, as_limit(limit))); + } else if (op == "gsrs") { + string owner_id; + bool is_dark; + get_args(args, owner_id, is_dark); + send_request(td_api::make_object(as_message_sender(owner_id), is_dark)); + } else if (op == "gswu") { + string owner_id; + int32 star_count; + string password; + get_args(args, owner_id, star_count, password); + send_request( + td_api::make_object(as_message_sender(owner_id), star_count, password)); } else { op_not_found_count++; }