diff --git a/CHANGELOG.md b/CHANGELOG.md index c8dfc4772..6a0210500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Changes in 1.6.0: +Changes in 1.6.0 (31 Jan 2020): * Added support for multiple chat lists. Currently, only two chat lists Main and Archive are supported: - Added the class `ChatList`, which represents a chat list and could be `chatListMain` or `chatListArchive`. @@ -61,7 +61,7 @@ Changes in 1.6.0: - Added the field `type` to the classes `poll` and `inputMessagePoll`. - Added support for non-anonymous polls with visible votes by adding the field `is_anonymous` to the classes `poll` and `inputMessagePoll`. - - Added the method `getPollVoters`, returning users that voted for the specified option in a non-anonymous poll. + - Added the method `getPollVoters` returning users that voted for the specified option in a non-anonymous poll. - Added the new reply markup keyboard button `keyboardButtonTypeRequestPoll`. - Added the field `is_regular` to the class `pushMessageContentPoll`. - Added the update `updatePollAnswer` for bots only. @@ -172,7 +172,7 @@ Changes in 1.6.0: ----------------------------------------------------------------------------------------------------------------------- -Changes in 1.5.0: +Changes in 1.5.0 (9 Sep 2019): * Changed authorization workflow: - Added the state `authorizationStateWaitRegistration`, which will be received after `authorizationStateWaitCode` for @@ -250,7 +250,7 @@ Changes in 1.5.0: - Added the field `audio_cover_minithumbnail` to the class `audio`. * Added support for resending messages which failed to send: - Added the fields `error_code`, `error_message`, `can_retry` and `retry_after` to - the `messageSendingStateFailed` object. + the class `messageSendingStateFailed`. - Added the method `resendMessages`. * Added the field `is_animated` to the `sticker`, `stickerSet` and `stickerSetInfo` classes. Animated stickers can be received anywhere where non-animated stickers can appear. @@ -278,7 +278,7 @@ Changes in 1.5.0: ----------------------------------------------------------------------------------------------------------------------- -Changes in 1.4.0: +Changes in 1.4.0 (1 May 2019): * Added a [TDLib build instructions generator](https://tdlib.github.io/td/build.html), covering in details TDLib building on the most popular operating systems. @@ -532,7 +532,7 @@ Changes in 1.4.0: ----------------------------------------------------------------------------------------------------------------------- -Changes in 1.3.0: +Changes in 1.3.0 (5 Sep 2018): * Added a review of existing TDLib based [frameworks](https://github.com/tdlib/td/blob/master/example/README.md) in different programming languages. @@ -678,7 +678,7 @@ Changes in 1.3.0: ----------------------------------------------------------------------------------------------------------------------- -Changes in 1.2.0: +Changes in 1.2.0 (20 Mar 2018): * Added support for native .NET bindings through `C++/CLI` and `C++/CX`. See [using in .NET projects](README.md#using-dotnet) for more details. @@ -725,13 +725,13 @@ Changes in 1.2.0: ----------------------------------------------------------------------------------------------------------------------- -Changes in 1.1.1: +Changes in 1.1.1 (4 Feb 2018): * Fixed C JSON bindings compilation error. * Fixed locale-dependent JSON generation. ----------------------------------------------------------------------------------------------------------------------- -Changes in 1.1.0: +Changes in 1.1.0 (31 Jan 2018): * Methods `td::Log::set_file_path` and `td_set_log_file_path` now return whether they succeeded. * Added methods `td::Log::set_max_file_size` and `td_set_log_max_file_size` for restricting maximum TDLib log size. diff --git a/CMakeLists.txt b/CMakeLists.txt index 799787125..52a7c8528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -399,6 +399,7 @@ set(TDLIB_SOURCE td/telegram/ConfigShared.cpp td/telegram/Contact.cpp td/telegram/ContactsManager.cpp + td/telegram/CountryInfoManager.cpp td/telegram/DelayDispatcher.cpp td/telegram/Dependencies.cpp td/telegram/DeviceTokenManager.cpp @@ -447,7 +448,9 @@ set(TDLIB_SOURCE td/telegram/MessageContentType.cpp td/telegram/MessageEntity.cpp td/telegram/MessageId.cpp + td/telegram/MessageReplyInfo.cpp td/telegram/MessagesDb.cpp + td/telegram/MessageSearchFilter.cpp td/telegram/MessagesManager.cpp td/telegram/misc.cpp td/telegram/net/AuthDataShared.cpp @@ -553,6 +556,7 @@ set(TDLIB_SOURCE td/telegram/ConfigShared.h td/telegram/Contact.h td/telegram/ContactsManager.h + td/telegram/CountryInfoManager.h td/telegram/DelayDispatcher.h td/telegram/Dependencies.h td/telegram/DeviceTokenManager.h @@ -618,7 +622,9 @@ set(TDLIB_SOURCE td/telegram/MessageCopyOptions.h td/telegram/MessageEntity.h td/telegram/MessageId.h + td/telegram/MessageReplyInfo.h td/telegram/MessagesDb.h + td/telegram/MessageSearchFilter.h td/telegram/MessagesManager.h td/telegram/misc.h td/telegram/net/AuthDataShared.h diff --git a/benchmark/bench_crypto.cpp b/benchmark/bench_crypto.cpp index c7d34adb4..eaca9c8e2 100644 --- a/benchmark/bench_crypto.cpp +++ b/benchmark/bench_crypto.cpp @@ -14,6 +14,7 @@ #include "td/utils/UInt.h" #include +#include #include #include @@ -164,6 +165,47 @@ class AesCtrBench : public td::Benchmark { } }; +class AesCtrOpenSSLBench : public td::Benchmark { + public: + alignas(64) unsigned char data[DATA_SIZE]; + alignas(64) unsigned char dest[DATA_SIZE]; + td::UInt256 key; + td::UInt128 iv; + + std::string get_description() const override { + return PSTRING() << "AES CTR RAW OpenSSL [" << (DATA_SIZE >> 10) << "KB]"; + } + + void start_up() override { + for (int i = 0; i < DATA_SIZE; i++) { + data[i] = 123; + } + td::Random::secure_bytes(key.raw, sizeof(key)); + td::Random::secure_bytes(iv.raw, sizeof(iv)); + } + + void run(int n) override { + EVP_CIPHER_CTX *ctx; + int len; + ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key.raw, iv.raw); + + td::MutableSlice data_slice(data, DATA_SIZE); + td::MutableSlice dest_slice(dest, DATA_SIZE); + td::AesCtrState state; + state.init(as_slice(key), as_slice(iv)); + for (int i = 0; i < n; i++) { + //state.encrypt(data_slice, data_slice); + len = (int)data_slice.size(); + EVP_EncryptUpdate(ctx, dest_slice.ubegin(), &len, data_slice.ubegin(), len); + } + + //EVP_EncryptFinal_ex(ctx, ciphertext + len, &len); + + EVP_CIPHER_CTX_free(ctx); + } +}; + class AesCbcBench : public td::Benchmark { public: alignas(64) unsigned char data[DATA_SIZE]; @@ -346,13 +388,14 @@ class Crc64Bench : public td::Benchmark { int main() { td::init_openssl_threads(); + td::bench(AesCtrBench()); + td::bench(AesCtrOpenSSLBench()); td::bench(AesIgeShortBench()); td::bench(AesIgeShortBench()); td::bench(AesIgeEncryptBench()); td::bench(AesIgeDecryptBench()); td::bench(AesEcbBench()); - td::bench(AesCtrBench()); td::bench(Pbkdf2Bench()); td::bench(RandBench()); diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 94dcb3fbe..54d3a9167 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -178,8 +178,10 @@ inputFileLocal path:string = InputFile; inputFileGenerated original_path:string conversion:string expected_size:int32 = InputFile; -//@description Photo description @type Thumbnail type (see https://core.telegram.org/constructor/photoSize) @photo Information about the photo file @width Photo width @height Photo height -photoSize type:string photo:file width:int32 height:int32 = PhotoSize; +//@description Describes an image in JPEG format @type Image type (see https://core.telegram.org/constructor/photoSize) +//@photo Information about the image file @width Image width @height Image height +//@progressive_sizes Sizes of progressive JPEG file prefixes, which can be used to preliminarily show the image +photoSize type:string photo:file width:int32 height:int32 progressive_sizes:vector = PhotoSize; //@description Thumbnail image of a very poor quality and low resolution @width Thumbnail width, usually doesn't exceed 40 @height Thumbnail height, usually doesn't exceed 40 @data The thumbnail in JPEG format minithumbnail width:int32 height:int32 data:bytes = Minithumbnail; @@ -293,7 +295,7 @@ contact phone_number:string first_name:string last_name:string vcard:string user //@description Describes a location on planet Earth @latitude Latitude of the location in degrees; as defined by the sender @longitude Longitude of the location, in degrees; as defined by the sender location latitude:double longitude:double = Location; -//@description Describes a venue @location Venue location; as defined by the sender @title Venue name; as defined by the sender @address Venue address; as defined by the sender @provider Provider of the venue database; as defined by the sender. Currently only "foursquare" and "gplaces" (Google Places) needs to be supported +//@description Describes a venue @location Venue location; as defined by the sender @title Venue name; as defined by the sender @address Venue address; as defined by the sender @provider Provider of the venue database; as defined by the sender. Currently only "foursquare" and "gplaces" (Google Places) need to be supported //@id Identifier of the venue in the provider database; as defined by the sender @type Type of the venue in the provider database; as defined by the sender venue location:location title:string address:string provider:string id:string type:string = Venue; @@ -352,7 +354,7 @@ chatLocation location:location address:string = ChatLocation; //@description Animated variant of a chat photo in MPEG4 format //@length Animation width and height //@file Information about the animation file -//@main_frame_timestamp Timestamp of the frame, used as static chat photo +//@main_frame_timestamp Timestamp of the frame, used as a static chat photo animatedChatPhoto length:int32 file:file main_frame_timestamp:double = AnimatedChatPhoto; @@ -428,7 +430,7 @@ chatAdministrators administrators:vector = ChatAdministrators //@can_send_messages True, if the user can send text messages, contacts, locations, and venues //@can_send_media_messages True, if the user can send audio files, documents, photos, videos, video notes, and voice notes. Implies can_send_messages permissions //@can_send_polls True, if the user can send polls. Implies can_send_messages permissions -//@can_send_other_messages True, if the user can send animations, games, stickers and dice and use inline bots. Implies can_send_messages permissions +//@can_send_other_messages True, if the user can send animations, games, stickers, and dice and use inline bots. Implies can_send_messages permissions //@can_add_web_page_previews True, if the user may add a web page preview to their messages. Implies can_send_messages permissions //@can_change_info True, if the user can change the chat title, photo, and other settings //@can_invite_users True, if the user can invite new users to the chat @@ -475,9 +477,9 @@ chatMemberStatusBanned banned_until_date:int32 = ChatMemberStatus; //@description A user with information about joining/leaving a chat @user_id User identifier of the chat member //@inviter_user_id Identifier of a user that invited/promoted/banned this member in the chat; 0 if unknown -//@joined_chat_date Point in time (Unix timestamp) when the user joined a chat +//@joined_chat_date Point in time (Unix timestamp) when the user joined the chat //@status Status of the member in the chat -//@bot_info If the user is a bot, information about the bot; may be null. Can be null even for a bot if the bot is not a chat member +//@bot_info If the user is a bot, information about the bot; may be null. Can be null even for a bot if the bot is not the chat member chatMember user_id:int32 inviter_user_id:int32 joined_chat_date:int32 status:ChatMemberStatus bot_info:botInfo = ChatMember; //@description Contains a list of chat members @total_count Approximate total count of chat members found @members A list of chat members @@ -576,14 +578,14 @@ supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_co //@can_set_username True, if the chat username can be changed //@can_set_sticker_set True, if the supergroup sticker set can be changed //@can_set_location True, if the supergroup location can be changed -//@can_view_statistics True, if the channel statistics is available +//@can_get_statistics True, if the supergroup or channel statistics are available //@is_all_history_available True, if new chat members will have access to old messages. In public or discussion groups and both public and private channels, old messages are always available, so this option affects only private supergroups without a linked chat. The value of this field is only available for chat administrators //@sticker_set_id Identifier of the supergroup sticker set; 0 if none //@location Location to which the supergroup is connected; may be null //@invite_link Invite link for this chat //@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none //@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none -supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_view_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:string upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo; +supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:string upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo; //@class SecretChatState @description Describes the current secret chat state @@ -633,6 +635,13 @@ messageForwardOriginChannel chat_id:int53 message_id:int53 author_signature:stri //@from_message_id For messages forwarded to the chat with the current user (Saved Messages) or to the channel's discussion group, the identifier of the original message from which the new message was forwarded last time; 0 if unknown messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announcement_type:string from_chat_id:int53 from_message_id:int53 = MessageForwardInfo; +//@description Contains information about interactions with a message +//@view_count Number of times the message was viewed +//@forward_count Number of times the message was forwarded +//@reply_count Number of times the message was directly or indirectly replied; supergroups and linked channels only +//@recent_replier_user_ids User identifiers of the recent repliers to the message +messageInteractionInfo view_count:int32 forward_count:int32 reply_count:int32 recent_replier_user_ids:vector = MessageInteractionInfo; + //@class MessageSendingState @description Contains information about the sending state of the message @@ -655,28 +664,30 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r //@can_be_forwarded True, if the message can be forwarded //@can_be_deleted_only_for_self True, if the message can be deleted only for the current user while other users will continue to see it //@can_be_deleted_for_all_users True, if the message can be deleted for all users +//@can_get_statistics True, if the message statistics are available +//@can_get_replies True, if the message replies are available //@is_channel_post True, if the message is a channel post. All messages to channels are channel posts, all other messages are not channel posts //@contains_unread_mention True, if the message contains an unread mention for the current user //@date Point in time (Unix timestamp) when the message was sent //@edit_date Point in time (Unix timestamp) when the message was last edited //@forward_info Information about the initial message sender; may be null +//@interaction_info Information about interactions with the message; may be null //@reply_to_message_id If non-zero, the identifier of the message this message is replying to; can be the identifier of a deleted message //@ttl For self-destructing messages, the message's TTL (Time To Live), in seconds; 0 if none. TDLib will send updateDeleteMessages or updateMessageContent once the TTL expires //@ttl_expires_in Time left before the message expires, in seconds //@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent //@author_signature For channel posts, optional author signature -//@views Number of times this message was viewed //@media_album_id Unique identifier of an album this message belongs to. Only photos and videos can be grouped together in albums //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted //@content Content of the message //@reply_markup Reply markup for the message; may be null -message id:int53 sender_user_id:int32 chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo reply_to_message_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int32 author_signature:string views:int32 media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message; +message id:int53 sender_user_id:int32 chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_statistics:Bool can_get_replies:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo reply_to_message_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int32 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message; //@description Contains a list of messages @total_count Approximate total count of messages found @messages List of messages; messages may be null messages total_count:int32 messages:vector = Messages; -//@description Contains a list of messages found by a search @messages List of messages @next_from_search_id Value to pass as from_search_id to get more results -foundMessages messages:vector next_from_search_id:int64 = FoundMessages; +//@description Contains a list of messages found by a search @total_count Approximate total count of messages found; -1 if unknown @messages List of messages @next_offset The offset for the next request. If empty, there are no more results +foundMessages total_count:int32 messages:vector next_offset:string = FoundMessages; //@class NotificationSettingsScope @description Describes the types of chats to which notification settings are applied @@ -833,7 +844,7 @@ chatsNearby users_nearby:vector supergroups_nearby:vector view_count:int32 version:int32 webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote instant_view_version:int32 = WebPage; +//@description Contains information about a country +//@country_code A two-letter ISO 3166-1 alpha-2 country code +//@name Native name of the country +//@english_name English name of the country +//@is_hidden True, if the country should be hidden from the list of all countries +//@calling_codes List of country calling codes +countryInfo country_code:string name:string english_name:string is_hidden:Bool calling_codes:vector = CountryInfo; + +//@description Contains information about countries @countries The list of countries +countries countries:vector = Countries; + +//@description Contains information about a phone number +//@country Information about the country to which the phone number belongs; may be null +//@country_calling_code The part of the phone number denoting country calling code or its part +//@formatted_phone_number The phone number without country calling code formatted accordingly to local rules +phoneNumberInfo country:countryInfo country_calling_code:string formatted_phone_number:string = PhoneNumberInfo; + + //@description Describes an action associated with a bank card number @text Action text @url The URL to be opened bankCardActionOpenUrl text:string url:string = BankCardActionOpenUrl; @@ -1684,7 +1716,7 @@ messageSchedulingStateSendWhenOnline = MessageSchedulingState; //@description Options to be used when a message is sent -//@disable_notification Pass true to disable notification for the message. Must be false if the message is sent to a secret chat +//@disable_notification Pass true to disable notification for the message //@from_background Pass true if the message is sent from the background //@scheduling_state Message scheduling state. Messages sent to a secret chat, live location messages and self-destructing messages can't be scheduled messageSendOptions disable_notification:Bool from_background:Bool scheduling_state:MessageSchedulingState = MessageSendOptions; @@ -1980,6 +2012,12 @@ callProblemSilentRemote = CallProblem; //@description The call ended unexpectedly callProblemDropped = CallProblem; +//@description The video was distorted +callProblemDistortedVideo = CallProblem; + +//@description The video was pixelated +callProblemPixelatedVideo = CallProblem; + //@description Describes a call @id Call identifier, not persistent @user_id Peer user identifier @is_outgoing True, if the call is outgoing @is_video True, if the call is a video call @state Call state call id:int32 user_id:int32 is_outgoing:Bool is_video:Bool state:CallState = Call; @@ -1988,7 +2026,7 @@ call id:int32 user_id:int32 is_outgoing:Bool is_video:Bool state:CallState = Cal //@description Contains settings for the authentication of the user's phone number //@allow_flash_call Pass true if the authentication code may be sent via flash call to the specified phone number //@is_current_phone_number Pass true if the authenticated phone number is used on the current device -//@allow_sms_retriever_api For official applications only. True, if the app can use Android SMS Retriever API (requires Google Play Services >= 10.2) to automatically receive the authentication code from the SMS. See https://developers.google.com/identity/sms-retriever/ for more details +//@allow_sms_retriever_api For official applications only. True, if the application can use Android SMS Retriever API (requires Google Play Services >= 10.2) to automatically receive the authentication code from the SMS. See https://developers.google.com/identity/sms-retriever/ for more details phoneNumberAuthenticationSettings allow_flash_call:Bool is_current_phone_number:Bool allow_sms_retriever_api:Bool = PhoneNumberAuthenticationSettings; @@ -2125,10 +2163,13 @@ inlineQueryResults inline_query_id:int64 next_offset:string results:vector = LocalizationTargetInfo; -//@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. To use specific push notification service, you must specify the correct application platform and upload valid server authentication data at https://my.telegram.org +//@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org //@description A token for Firebase Cloud Messaging @token Device registration token; may be empty to de-register a device @encrypt True, if push notifications should be additionally encrypted deviceTokenFirebaseCloudMessaging token:string encrypt:Bool = DeviceToken; @@ -2791,7 +2832,7 @@ networkStatisticsEntryFile file_type:FileType network_type:NetworkType sent_byte //@sent_bytes Total number of bytes sent @received_bytes Total number of bytes received @duration Total call duration, in seconds networkStatisticsEntryCall network_type:NetworkType sent_bytes:int53 received_bytes:int53 duration:double = NetworkStatisticsEntry; -//@description A full list of available network statistic entries @since_date Point in time (Unix timestamp) when the app began collecting statistics @entries Network statistics entries +//@description A full list of available network statistic entries @since_date Point in time (Unix timestamp) from which the statistics are collected @entries Network statistics entries networkStatistics since_date:int32 entries:vector = NetworkStatistics; @@ -2801,7 +2842,7 @@ networkStatistics since_date:int32 entries:vector = Netw //@max_video_file_size The maximum size of a video file to be auto-downloaded //@max_other_file_size The maximum size of other file types to be auto-downloaded //@video_upload_bitrate The maximum suggested bitrate for uploaded videos -//@preload_large_videos True, if the beginning of videos needs to be preloaded for instant playback +//@preload_large_videos True, if the beginning of video files needs to be preloaded for instant playback //@preload_next_audio True, if the next audio track needs to be preloaded while the user is listening to an audio file //@use_less_data_for_calls True, if "use less data for calls" option needs to be enabled autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int32 max_other_file_size:int32 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool use_less_data_for_calls:Bool = AutoDownloadSettings; @@ -2955,7 +2996,7 @@ statisticsValue value:double previous_value:double growth_rate_percentage:double //@description A graph data @json_data Graph data in JSON format @zoom_token If non-empty, a token which can be used to receive a zoomed in graph statisticsGraphData json_data:string zoom_token:string = StatisticsGraph; -//@description The graph data to be asynchronously loaded through getChatStatisticsGraph @token The token to use for data loading +//@description The graph data to be asynchronously loaded through getStatisticsGraph @token The token to use for data loading statisticsGraphAsync token:string = StatisticsGraph; //@description An error message to be shown to the user instead of the graph @error_message The error message @@ -3027,6 +3068,10 @@ chatStatisticsSupergroup period:dateRange member_count:statisticsValue message_c chatStatisticsChannel period:dateRange member_count:statisticsValue mean_view_count:statisticsValue mean_share_count:statisticsValue enabled_notifications_percentage:double member_count_graph:StatisticsGraph join_graph:StatisticsGraph mute_graph:StatisticsGraph view_count_by_hour_graph:StatisticsGraph view_count_by_source_graph:StatisticsGraph join_by_source_graph:StatisticsGraph language_graph:StatisticsGraph message_interaction_graph:StatisticsGraph instant_view_interaction_graph:StatisticsGraph recent_message_interactions:vector = ChatStatistics; +//@description A detailed statistics about a message @message_interaction_graph A graph containing number of message views and shares +messageStatistics message_interaction_graph:StatisticsGraph = MessageStatistics; + + //@class Update @description Contains notifications about data changes //@description The user authorization state has changed @authorization_state New authorization state @@ -3052,8 +3097,8 @@ updateMessageContent chat_id:int53 message_id:int53 new_content:MessageContent = //@description A message was edited. Changes in the message content will come in a separate updateMessageContent @chat_id Chat identifier @message_id Message identifier @edit_date Point in time (Unix timestamp) when the message was edited @reply_markup New message reply markup; may be null updateMessageEdited chat_id:int53 message_id:int53 edit_date:int32 reply_markup:ReplyMarkup = Update; -//@description The view count of the message has changed @chat_id Chat identifier @message_id Message identifier @views New value of the view count -updateMessageViews chat_id:int53 message_id:int53 views:int32 = Update; +//@description The information about interactions with a message has changed @chat_id Chat identifier @message_id Message identifier @interaction_info New information about interactions with the message +updateMessageInteractionInfo chat_id:int53 message_id:int53 interaction_info:messageInteractionInfo = Update; //@description The message content was opened. Updates voice note messages to "listened", video note messages to "viewed" and starts the TTL timer for self-destructing messages @chat_id Chat identifier @message_id Message identifier updateMessageContentOpened chat_id:int53 message_id:int53 = Update; @@ -3574,15 +3619,17 @@ searchChatMessages chat_id:int53 query:string sender_user_id:int32 from_message_ //@offset_date The date of the message starting from which the results should be fetched. Use 0 or any date in the future to get results from the last message //@offset_chat_id The chat identifier of the last found message, or 0 for the first request //@offset_message_id The message identifier of the last found message, or 0 for the first request -//@limit The maximum number of messages to be returned, up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached -searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 = Messages; +//@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached +//@filter Filter for message content in the search results; searchMessagesFilterCall, searchMessagesFilterMissedCall, searchMessagesFilterMention, searchMessagesFilterUnreadMention and searchMessagesFilterFailedToSend are unsupported in this function +searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 filter:SearchMessagesFilter = Messages; //@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance the number of returned messages is chosen by the library -//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats @query Query to search for. If empty, searchChatMessages should be used instead -//@from_search_id The identifier from the result of a previous request, use 0 to get results from the last message +//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats +//@query Query to search for. If empty, searchChatMessages should be used instead +//@offset Offset of the first entry to return as received from the previous request; use empty string to get first chunk of results //@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached -//@filter A filter for the content of messages in the search results -searchSecretMessages chat_id:int53 query:string from_search_id:int64 limit:int32 filter:SearchMessagesFilter = FoundMessages; +//@filter A filter for message content in the search results +searchSecretMessages chat_id:int53 query:string offset:string limit:int32 filter:SearchMessagesFilter = FoundMessages; //@description Searches for call messages. Returns the results in reverse chronological order (i. e., in order of decreasing message_id). For optimal performance the number of returned messages is chosen by the library //@from_message_id Identifier of the message from which to search; use 0 to get results from the last message @@ -3604,6 +3651,13 @@ getChatMessageCount chat_id:int53 filter:SearchMessagesFilter return_local:Bool //@description Returns all scheduled messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id) @chat_id Chat identifier getChatScheduledMessages chat_id:int53 = Messages; +//@description Returns forwarded copies of a channel message to another public channels. For optimal performance the number of returned messages is chosen by the library +//@chat_id Chat identifier of the message +//@message_id Message identifier +//@offset Offset of the first entry to return as received from the previous request; use empty string to get first chunk of results +//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. Fewer messages may be returned than specified by the limit, even if the end of the list has not been reached +getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages; + //@description Removes an active notification from notification list. Needs to be called only if the notification is removed by the current user @notification_group_id Identifier of notification group to which the notification belongs @notification_id Identifier of removed notification removeNotification notification_group_id:int32 notification_id:int32 = Ok; @@ -3655,7 +3709,7 @@ sendInlineQueryResultMessage chat_id:int53 reply_to_message_id:int53 options:mes //@options Options to be used to send the messages //@as_album True, if the messages should be grouped into an album after forwarding. For this to work, no more than 10 messages may be forwarded, and all of them must be photo or video messages //@send_copy True, if content of the messages needs to be copied without links to the original messages. Always true if the messages are forwarded to a secret chat -//@remove_caption True, if media captions of message copies needs to be removed. Ignored if send_copy is false +//@remove_caption True, if media caption of message copies needs to be removed. Ignored if send_copy is false forwardMessages chat_id:int53 from_chat_id:int53 message_ids:vector options:messageSendOptions as_album:Bool send_copy:Bool remove_caption:Bool = Messages; //@description Resends messages which failed to send. Can be called only for messages for which messageSendingStateFailed.can_retry is true and after specified in messageSendingStateFailed.retry_after time passed. @@ -3910,7 +3964,7 @@ getChatFilterDefaultIconName filter:chatFilter = Text; setChatTitle chat_id:int53 title:string = Ok; //@description Changes the photo of a chat. Supported only for basic groups, supergroups and channels. Requires can_change_info rights -//@chat_id Chat identifier @photo New chat photo. You can pass null to delete the chat photo +//@chat_id Chat identifier @photo New chat photo. Pass null to delete the chat photo setChatPhoto chat_id:int53 photo:InputChatPhoto = Ok; //@description Changes the chat members permissions. Supported only for basic groups and supergroups. Requires can_restrict_members administrator right @@ -3937,7 +3991,7 @@ setChatClientData chat_id:int53 client_data:string = Ok; setChatDescription chat_id:int53 description:string = Ok; //@description Changes the discussion group of a channel chat; requires can_change_info rights in the channel if it is specified @chat_id Identifier of the channel chat. Pass 0 to remove a link from the supergroup passed in the second argument to a linked channel chat (requires can_pin_messages rights in the supergroup) @discussion_chat_id Identifier of a new channel's discussion group. Use 0 to remove the discussion group. -//-Use the method getSuitableDiscussionChats to find all suitable groups. Basic group chats needs to be first upgraded to supergroup chats. If new chat members don't have access to old messages in the supergroup, then toggleSupergroupIsAllHistoryAvailable needs to be used first to change that +//-Use the method getSuitableDiscussionChats to find all suitable groups. Basic group chats need to be first upgraded to supergroup chats. If new chat members don't have access to old messages in the supergroup, then toggleSupergroupIsAllHistoryAvailable needs to be used first to change that setChatDiscussionGroup chat_id:int53 discussion_chat_id:int53 = Ok; //@description Changes the location of a chat. Available only for some location-based supergroups, use supergroupFullInfo.can_set_location to check whether the method is allowed to use @chat_id Chat identifier @location New location for the chat; must be valid and not null @@ -4005,7 +4059,7 @@ setScopeNotificationSettings scope:NotificationSettingsScope notification_settin resetAllNotificationSettings = Ok; -//@description Changes the pinned state of a chat. You can pin up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") non-secret chats and the same number of secret chats in the main/arhive chat list +//@description Changes the pinned state of a chat. There can be up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") pinned non-secret chats and the same number of secret chats in the main/arhive chat list //@chat_list Chat list in which to change the pinned state of the chat @chat_id Chat identifier @is_pinned True, if the chat is pinned toggleChatIsPinned chat_list:ChatList chat_id:int53 is_pinned:Bool = Ok; @@ -4351,7 +4405,7 @@ deleteSavedCredentials = Ok; getSupportUser = User; -//@description Returns backgrounds installed by the user @for_dark_theme True, if the backgrounds needs to be ordered for dark theme +//@description Returns backgrounds installed by the user @for_dark_theme True, if the backgrounds need to be ordered for dark theme getBackgrounds for_dark_theme:Bool = Backgrounds; //@description Constructs a persistent HTTP URL for a background @name Background name @type Background type @@ -4449,14 +4503,17 @@ removeChatActionBar chat_id:int53 = Ok; reportChat chat_id:int53 reason:ChatReportReason message_ids:vector = Ok; -//@description Returns an HTTP URL with the chat statistics. Currently this method of getting the statistics is disabled and can be deleted in the future @chat_id Chat identifier @parameters Parameters from "tg://statsrefresh?params=******" link @is_dark Pass true if a URL with the dark theme must be returned +//@description Returns an HTTP URL with the chat statistics. Currently this method of getting the statistics are disabled and can be deleted in the future @chat_id Chat identifier @parameters Parameters from "tg://statsrefresh?params=******" link @is_dark Pass true if a URL with the dark theme must be returned getChatStatisticsUrl chat_id:int53 parameters:string is_dark:Bool = HttpUrl; -//@description Returns detailed statistics about a chat. Currently this method can be used only for supergroups and channels. Requires administrator rights in the channel @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the app +//@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; -//@description Loads asynchronous or zoomed in chat statistics graph @chat_id Chat identifier @token The token for graph loading @x X-value for zoomed in graph or 0 otherwise -getChatStatisticsGraph chat_id:int53 token:string x:int53 = StatisticsGraph; +//@description Returns detailed statistics about a message. Can be used only if Message.can_get_statistics == true @chat_id Chat identifier @message_id Message identifier @is_dark Pass true if a dark theme is used by the application +getMessageStatistics chat_id:int53 message_id:int53 is_dark:Bool = MessageStatistics; + +//@description Loads asynchronous or zoomed in chat or message statistics graph @chat_id Chat identifier @token The token for graph loading @x X-value for zoomed in graph or 0 otherwise +getStatisticsGraph chat_id:int53 token:string x:int53 = StatisticsGraph; //@description Returns storage usage statistics. Can be called before authorization @chat_limit The maximum number of chats with the largest storage usage for which separate statistics should be returned. All other chats will be grouped in entries with chat_id == 0. If the chat info database is not used, the chat_limit is ignored and is always set to 0 @@ -4476,7 +4533,7 @@ getDatabaseStatistics = DatabaseStatistics; //@file_types If not empty, only files with the given type(s) are considered. By default, all types except thumbnails, profile photos, stickers and wallpapers are deleted //@chat_ids If not empty, only files from the given chats are considered. Use 0 as chat identifier to delete files not belonging to any chat (e.g., profile photos) //@exclude_chat_ids If not empty, files from the given chats are excluded. Use 0 as chat identifier to exclude all files not belonging to any chat (e.g., profile photos) -//@return_deleted_file_statistics Pass true if deleted file statistics needs to be returned instead of the whole storage usage statistics. Affects only returned statistics +//@return_deleted_file_statistics Pass true if deleted file statistics need to be returned instead of the whole storage usage statistics. Affects only returned statistics //@chat_limit Same as in getStorageStatistics. Affects only returned statistics optimizeStorage size:int53 ttl:int32 count:int32 immunity_delay:int32 file_types:vector chat_ids:vector exclude_chat_ids:vector return_deleted_file_statistics:Bool chat_limit:int32 = StorageStatistics; @@ -4590,7 +4647,7 @@ addStickerToSet user_id:int32 name:string sticker:InputSticker = StickerSet; //@description Sets a sticker set thumbnail; for bots only. Returns the sticker set //@user_id Sticker set owner @name Sticker set name -//@thumbnail Thumbnail to set in PNG or TGS format. Animated thumbnail must be set for animated sticker sets and only for them. You can use a zero InputFileId to delete the thumbnail +//@thumbnail Thumbnail to set in PNG or TGS format. Animated thumbnail must be set for animated sticker sets and only for them. Pass a zero InputFileId to delete the thumbnail setStickerSetThumbnail user_id:int32 name:string thumbnail:InputFile = StickerSet; //@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot @@ -4620,9 +4677,15 @@ answerCustomQuery custom_query_id:int64 data:string = Ok; setAlarm seconds:double = Ok; +//@description Returns information about existing countries. Can be called before authorization +getCountries = Countries; + //@description Uses current user IP address to find their country. Returns two-letter ISO 3166-1 alpha-2 country code. Can be called before authorization getCountryCode = Text; +//@description Returns information about a phone number by its prefix. Can be called before authorization @phone_number_prefix The phone number prefix +getPhoneNumberInfo phone_number_prefix:string = PhoneNumberInfo; + //@description Returns the default text for invitation messages to be used as a placeholder when the current user invites friends to Telegram getInviteText = Text; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index fcd50d6d5..dbfff2331 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index 348026e44..4e57dd8fc 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -128,7 +128,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#83e5de54 id:int = Message; -message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; +message#8a7e027d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int reply_to_top_id:flags.24?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -179,6 +179,7 @@ photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; +photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint; @@ -345,6 +346,8 @@ updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; +updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Update; +updateReadDiscussion#119fb587 peer:Peer msg_id:int read_max_id:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -534,7 +537,7 @@ botInfo#98e81d3a user_id:int description:string commands:Vector = Bo keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; -keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; +keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; @@ -1137,6 +1140,23 @@ stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAnd globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings; +help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; + +help.country#c3878e23 flags:# hidden:flags.0?true iso2:string default_name:string name:flags.1?string country_codes:Vector = help.Country; + +help.countriesListNotModified#93cc1f32 = help.CountriesList; +help.countriesList#87d0759e countries:Vector hash:int = help.CountriesList; + +messageReplies#82e3c815 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?int = MessageReplies; + +messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:flags.2?MessageReplies = MessageViews; + +messages.messageViews#2c3f2ae2 views:Vector users:Vector = messages.MessageViews; + +stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; + +messages.discussionMessage#d25fad90 message:Message read_max_id:int chats:Vector users:Vector = messages.DiscussionMessage; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1261,7 +1281,7 @@ contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoP messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; -messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.search#4e17810b flags:# peer:InputPeer q:string from_id:flags.0?InputUser top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; @@ -1286,8 +1306,8 @@ messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerp messages.discardEncryption#edd923c5 chat_id:int = Bool; messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool; messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool; -messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; -messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; +messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; +messages.sendEncryptedFile#5559481d flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; @@ -1302,10 +1322,10 @@ messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; -messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; +messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool = messages.MessageViews; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; -messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; @@ -1316,7 +1336,7 @@ messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:fla messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; -messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer; +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#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; @@ -1381,6 +1401,9 @@ messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers; +messages.getReplies#fda52fdc peer:InputPeer msg_id:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; +messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1421,6 +1444,7 @@ help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = messages.AffectedMessages; @@ -1498,3 +1522,5 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; +stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; diff --git a/td/generate/scheme/telegram_api.tlo b/td/generate/scheme/telegram_api.tlo index 4ca7e201b..5fd375dff 100644 Binary files a/td/generate/scheme/telegram_api.tlo and b/td/generate/scheme/telegram_api.tlo differ diff --git a/td/mtproto/SessionConnection.cpp b/td/mtproto/SessionConnection.cpp index a1f6f84d6..8c94fb8d7 100644 --- a/td/mtproto/SessionConnection.cpp +++ b/td/mtproto/SessionConnection.cpp @@ -824,6 +824,11 @@ void SessionConnection::send_ack(uint64 message_id) { // an easiest way to eliminate duplicated acks for gzipped packets if (to_ack_.empty() || to_ack_.back() != ack) { to_ack_.push_back(ack); + + constexpr size_t MAX_UNACKED_PACKETS = 100; + if (to_ack_.size() >= MAX_UNACKED_PACKETS) { + send_before(Time::now_cached()); + } } } @@ -970,8 +975,8 @@ void SessionConnection::flush_packet() { } } - to_ack_.clear(); - if (to_send_.empty()) { + if (to_send_.empty() && to_ack_.empty() && to_get_state_info_.empty() && to_resend_answer_.empty() && + to_cancel_answer_.empty()) { force_send_at_ = 0; } } diff --git a/td/telegram/AccessRights.h b/td/telegram/AccessRights.h index bc7b49371..029cea6d0 100644 --- a/td/telegram/AccessRights.h +++ b/td/telegram/AccessRights.h @@ -6,6 +6,8 @@ // #pragma once +#include "td/utils/common.h" + namespace td { enum class AccessRights : int32 { Read, Edit, Write }; diff --git a/td/telegram/CallActor.cpp b/td/telegram/CallActor.cpp index e622d922e..838aba9a4 100644 --- a/td/telegram/CallActor.cpp +++ b/td/telegram/CallActor.cpp @@ -277,6 +277,10 @@ void CallActor::rate_call(int32 rating, string comment, vector &payload, - int64 result_id) { + tl_object_ptr &&password, int64 result_id) { result_id_ = result_id; dialog_id_ = dialog_id; message_id_ = message_id; @@ -56,6 +57,12 @@ class GetBotCallbackAnswerQuery : public Td::ResultHandler { flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK; data = BufferSlice(static_cast(payload.get())->data_); break; + case td_api::callbackQueryPayloadDataWithPassword::ID: + CHECK(password != nullptr); + flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK | + telegram_api::messages_getBotCallbackAnswer::PASSWORD_MASK; + data = BufferSlice(static_cast(payload.get())->data_); + break; case td_api::callbackQueryPayloadGame::ID: flags = telegram_api::messages_getBotCallbackAnswer::GAME_MASK; break; @@ -64,7 +71,8 @@ class GetBotCallbackAnswerQuery : public Td::ResultHandler { } auto net_query = G()->net_query_creator().create(telegram_api::messages_getBotCallbackAnswer( - flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), std::move(data))); + flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), std::move(data), + std::move(password))); net_query->need_resend_on_503_ = false; send_query(std::move(net_query)); } @@ -222,7 +230,7 @@ void CallbackQueriesManager::on_new_inline_query( } int64 CallbackQueriesManager::send_callback_query(FullMessageId full_message_id, - const tl_object_ptr &payload, + tl_object_ptr &&payload, Promise &&promise) { if (td_->auth_manager_->is_bot()) { promise.set_error(Status::Error(5, "Bot can't send callback queries to other bot")); @@ -260,12 +268,44 @@ int64 CallbackQueriesManager::send_callback_query(FullMessageId full_message_id, } while (callback_query_answers_.find(result_id) != callback_query_answers_.end()); callback_query_answers_[result_id]; // reserve place for result - td_->create_handler(std::move(promise)) - ->send(dialog_id, full_message_id.get_message_id(), payload, result_id); + if (payload->get_id() == td_api::callbackQueryPayloadDataWithPassword::ID) { + auto password = static_cast(payload.get())->password_; + send_closure(td_->password_manager_, &PasswordManager::get_input_check_password_srp, std::move(password), + PromiseCreator::lambda( + [this, full_message_id, payload = std::move(payload), result_id, promise = std::move(promise)]( + Result> result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } + + send_get_callback_answer_query(full_message_id, std::move(payload), result.move_as_ok(), + result_id, std::move(promise)); + })); + } else { + send_get_callback_answer_query(full_message_id, std::move(payload), nullptr, result_id, std::move(promise)); + } return result_id; } +void CallbackQueriesManager::send_get_callback_answer_query( + FullMessageId full_message_id, tl_object_ptr &&payload, + tl_object_ptr &&password, int64 result_id, Promise &&promise) { + auto dialog_id = full_message_id.get_dialog_id(); + if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the chat")); + } + if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) { + return promise.set_error(Status::Error(5, "Message not found")); + } + + td_->create_handler(std::move(promise)) + ->send(dialog_id, full_message_id.get_message_id(), payload, std::move(password), result_id); +} + void CallbackQueriesManager::on_get_callback_query_answer( int64 result_id, tl_object_ptr &&answer) { LOG(INFO) << "Receive answer for callback query " << result_id; diff --git a/td/telegram/CallbackQueriesManager.h b/td/telegram/CallbackQueriesManager.h index c05bd2215..c859cbf6f 100644 --- a/td/telegram/CallbackQueriesManager.h +++ b/td/telegram/CallbackQueriesManager.h @@ -39,7 +39,7 @@ class CallbackQueriesManager { tl_object_ptr &&inline_message_id, BufferSlice &&data, int64 chat_instance, string &&game_short_name); - int64 send_callback_query(FullMessageId full_message_id, const tl_object_ptr &payload, + int64 send_callback_query(FullMessageId full_message_id, tl_object_ptr &&payload, Promise &&promise); void on_get_callback_query_answer(int64 result_id, tl_object_ptr &&answer); @@ -60,6 +60,11 @@ class CallbackQueriesManager { tl_object_ptr get_query_payload(int32 flags, BufferSlice &&data, string &&game_short_name); + void send_get_callback_answer_query(FullMessageId full_message_id, + tl_object_ptr &&payload, + tl_object_ptr &&password, int64 result_id, + Promise &&promise); + std::unordered_map callback_query_answers_; Td *td_; diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index 23bd09036..6bf45802f 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -875,7 +875,7 @@ class ConfigRecoverer : public Actor { }; ConfigManager::ConfigManager(ActorShared<> parent) : parent_(std::move(parent)) { - lazy_request_flood_countrol_.add_limit(20, 1); + lazy_request_flood_control_.add_limit(20, 1); } void ConfigManager::start_up() { @@ -930,7 +930,7 @@ void ConfigManager::request_config() { return; } - lazy_request_flood_countrol_.add_event(static_cast(Timestamp::now().at())); + lazy_request_flood_control_.add_event(static_cast(Timestamp::now().at())); request_config_from_dc_impl(DcId::main()); } @@ -943,7 +943,7 @@ void ConfigManager::lazy_request_config() { return; } - expire_time_.relax(Timestamp::at(lazy_request_flood_countrol_.get_wakeup_at())); + expire_time_.relax(Timestamp::at(lazy_request_flood_control_.get_wakeup_at())); set_timeout_at(expire_time_.at()); } diff --git a/td/telegram/ConfigManager.h b/td/telegram/ConfigManager.h index 3a0190f54..10e76c7f0 100644 --- a/td/telegram/ConfigManager.h +++ b/td/telegram/ConfigManager.h @@ -114,7 +114,7 @@ class ConfigManager : public NetQueryCallback { int ref_cnt_{1}; Timestamp expire_time_; - FloodControlStrict lazy_request_flood_countrol_; + FloodControlStrict lazy_request_flood_control_; vector>> get_app_config_queries_; diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 1ea55b6e3..5ebe1ed27 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -2742,7 +2742,7 @@ tl_object_ptr ContactsManager::convert_stats_absolute_v get_percentage_value(obj->current_ - obj->previous_, obj->previous_)); } -tl_object_ptr ContactsManager::convert_megagroup_stats( +tl_object_ptr ContactsManager::convert_megagroup_stats( tl_object_ptr obj) { CHECK(obj != nullptr); @@ -2785,7 +2785,7 @@ tl_object_ptr ContactsManager::convert_megagroup_stats( std::move(top_inviters)); } -tl_object_ptr ContactsManager::convert_broadcast_stats( +tl_object_ptr ContactsManager::convert_broadcast_stats( tl_object_ptr obj) { CHECK(obj != nullptr); @@ -2835,8 +2835,7 @@ class GetMegagroupStatsQuery : public Td::ResultHandler { return on_error(id, result_ptr.move_as_error()); } - auto result = result_ptr.move_as_ok(); - promise_.set_value(td->contacts_manager_->convert_megagroup_stats(std::move(result))); + promise_.set_value(td->contacts_manager_->convert_megagroup_stats(result_ptr.move_as_ok())); } void on_error(uint64 id, Status status) override { @@ -2874,8 +2873,13 @@ class GetBroadcastStatsQuery : public Td::ResultHandler { return on_error(id, result_ptr.move_as_error()); } - auto result = result_ptr.move_as_ok(); - promise_.set_value(ContactsManager::convert_broadcast_stats(std::move(result))); + auto result = ContactsManager::convert_broadcast_stats(result_ptr.move_as_ok()); + for (auto &info : result->recent_message_interactions_) { + td->messages_manager_->on_update_message_interaction_info({DialogId(channel_id_), MessageId(info->message_id_)}, + info->view_count_, info->forward_count_, false, + nullptr); + } + promise_.set_value(std::move(result)); } void on_error(uint64 id, Status status) override { @@ -2884,6 +2888,51 @@ class GetBroadcastStatsQuery : public Td::ResultHandler { } }; +tl_object_ptr ContactsManager::convert_message_stats( + tl_object_ptr obj) { + return make_tl_object(convert_stats_graph(std::move(obj->views_graph_))); +} + +class GetMessageStatsQuery : public Td::ResultHandler { + Promise> promise_; + ChannelId channel_id_; + + public: + explicit GetMessageStatsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, MessageId message_id, bool is_dark, DcId dc_id) { + channel_id_ = channel_id; + + auto input_channel = td->contacts_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + + int32 flags = 0; + if (is_dark) { + flags |= telegram_api::stats_getMessageStats::DARK_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::stats_getMessageStats(flags, false /*ignored*/, std::move(input_channel), + message_id.get_server_message_id().get()), + dc_id)); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + promise_.set_value(td->contacts_manager_->convert_message_stats(result_ptr.move_as_ok())); + } + + void on_error(uint64 id, Status status) override { + td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetMessageStatsQuery"); + promise_.set_error(std::move(status)); + } +}; + class LoadAsyncGraphQuery : public Td::ResultHandler { Promise> promise_; @@ -6068,6 +6117,56 @@ void ContactsManager::send_get_channel_stats_query(DcId dc_id, ChannelId channel } } +bool ContactsManager::can_get_channel_message_statistics(DialogId dialog_id) { + if (dialog_id.get_type() != DialogType::Channel) { + return false; + } + + auto channel_id = dialog_id.get_channel_id(); + const Channel *c = get_channel(channel_id); + if (c == nullptr || c->is_megagroup) { + return false; + } + + auto channel_full = get_channel_full_force(channel_id, "can_get_channel_message_statistics"); + if (channel_full == nullptr) { + return c->status.is_administrator(); + } + return channel_full->stats_dc_id.is_exact(); +} + +void ContactsManager::get_channel_message_statistics(FullMessageId full_message_id, bool is_dark, + Promise> &&promise) { + auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, is_dark, + promise = std::move(promise)](Result r_dc_id) mutable { + if (r_dc_id.is_error()) { + return promise.set_error(r_dc_id.move_as_error()); + } + send_closure(actor_id, &ContactsManager::send_get_channel_message_stats_query, r_dc_id.move_as_ok(), + full_message_id, is_dark, std::move(promise)); + }); + get_channel_statistics_dc_id(full_message_id.get_dialog_id(), false, std::move(dc_id_promise)); +} + +void ContactsManager::send_get_channel_message_stats_query( + DcId dc_id, FullMessageId full_message_id, bool is_dark, + Promise> &&promise) { + if (G()->close_flag()) { + return promise.set_error(Status::Error(500, "Request aborted")); + } + + auto dialog_id = full_message_id.get_dialog_id(); + if (!td_->messages_manager_->have_message_force(full_message_id, "send_get_channel_message_stats_query")) { + return promise.set_error(Status::Error(400, "Message not found")); + } + if (!td_->messages_manager_->can_get_message_statistics(full_message_id)) { + return promise.set_error(Status::Error(400, "Message statistics is inaccessible")); + } + CHECK(dialog_id.get_type() == DialogType::Channel); + td_->create_handler(std::move(promise)) + ->send(dialog_id.get_channel_id(), full_message_id.get_message_id(), is_dark, dc_id); +} + void ContactsManager::load_statistics_graph(DialogId dialog_id, const string &token, int64 x, Promise> &&promise) { auto dc_id_promise = PromiseCreator::lambda( @@ -12926,6 +13025,18 @@ bool ContactsManager::get_channel_sign_messages(const Channel *c) { return c->sign_messages; } +bool ContactsManager::get_channel_has_linked_channel(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return get_channel_has_linked_channel(c); +} + +bool ContactsManager::get_channel_has_linked_channel(const Channel *c) { + return c->has_linked_channel; +} + int32 ContactsManager::get_channel_slow_mode_delay(ChannelId channel_id) { auto channel_full = get_channel_full_force(channel_id, "get_channel_slow_mode_delay"); if (channel_full == nullptr) { diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index 0e62a28ba..704730539 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -20,6 +20,7 @@ #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/FolderId.h" +#include "td/telegram/FullMessageId.h" #include "td/telegram/Location.h" #include "td/telegram/MessageId.h" #include "td/telegram/net/DcId.h" @@ -376,6 +377,11 @@ class ContactsManager : public Actor { void get_channel_statistics(DialogId dialog_id, bool is_dark, Promise> &&promise); + bool can_get_channel_message_statistics(DialogId dialog_id); + + void get_channel_message_statistics(FullMessageId full_message_id, bool is_dark, + Promise> &&promise); + void load_statistics_graph(DialogId dialog_id, const string &token, int64 x, Promise> &&promise); @@ -491,6 +497,7 @@ class ContactsManager : public Actor { DialogParticipantStatus get_channel_permissions(ChannelId channel_id) const; int32 get_channel_participant_count(ChannelId channel_id) const; bool get_channel_sign_messages(ChannelId channel_id) const; + bool get_channel_has_linked_channel(ChannelId channel_id) const; int32 get_channel_slow_mode_delay(ChannelId channel_id); std::pair> search_among_users(const vector &user_ids, const string &query, int32 limit); @@ -581,11 +588,15 @@ class ContactsManager : public Actor { static tl_object_ptr convert_stats_absolute_value( const tl_object_ptr &obj); - tl_object_ptr convert_megagroup_stats(tl_object_ptr obj); + tl_object_ptr convert_megagroup_stats( + tl_object_ptr obj); - static tl_object_ptr convert_broadcast_stats( + static tl_object_ptr convert_broadcast_stats( tl_object_ptr obj); + static tl_object_ptr convert_message_stats( + tl_object_ptr obj); + private: struct User { string first_name; @@ -1133,6 +1144,7 @@ class ContactsManager : public Actor { static DialogParticipantStatus get_channel_status(const Channel *c); DialogParticipantStatus get_channel_permissions(const Channel *c) const; static bool get_channel_sign_messages(const Channel *c); + static bool get_channel_has_linked_channel(const Channel *c); void set_my_id(UserId my_id); @@ -1444,6 +1456,9 @@ class ContactsManager : public Actor { void send_get_channel_stats_query(DcId dc_id, ChannelId channel_id, bool is_dark, Promise> &&promise); + void send_get_channel_message_stats_query(DcId dc_id, FullMessageId full_message_id, bool is_dark, + Promise> &&promise); + void send_load_async_graph_query(DcId dc_id, string token, int64 x, Promise> &&promise); diff --git a/td/telegram/CountryInfoManager.cpp b/td/telegram/CountryInfoManager.cpp new file mode 100644 index 000000000..f303160d0 --- /dev/null +++ b/td/telegram/CountryInfoManager.cpp @@ -0,0 +1,372 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// 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/CountryInfoManager.h" + +#include "td/telegram/Global.h" +#include "td/telegram/LanguagePackManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/buffer.h" +#include "td/utils/misc.h" +#include "td/utils/Random.h" +#include "td/utils/Status.h" + +namespace td { + +class GetNearestDcQuery : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetNearestDcQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create_unauth(telegram_api::help_getNearestDc())); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + promise_.set_value(std::move(result->country_)); + } + + void on_error(uint64 id, Status status) override { + if (!G()->is_expected_error(status) && status.message() != "BOT_METHOD_INVALID") { + LOG(ERROR) << "GetNearestDc returned " << status; + } + promise_.set_error(std::move(status)); + } +}; + +class GetCountriesListQuery : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetCountriesListQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &language_code, int32 hash) { + send_query(G()->net_query_creator().create_unauth(telegram_api::help_getCountriesList(language_code, hash))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + promise_.set_value(result_ptr.move_as_ok()); + } + + void on_error(uint64 id, Status status) override { + if (!G()->is_expected_error(status)) { + LOG(ERROR) << "GetCountriesList returned " << status; + } + promise_.set_error(std::move(status)); + } +}; + +struct CountryInfoManager::CallingCodeInfo { + string calling_code; + vector prefixes; + vector patterns; +}; + +struct CountryInfoManager::CountryInfo { + string country_code; + string default_name; + string name; + vector calling_codes; + bool is_hidden = false; + + td_api::object_ptr get_country_info_object() const { + return td_api::make_object( + country_code, name.empty() ? default_name : name, default_name, is_hidden, + transform(calling_codes, [](const CallingCodeInfo &info) { return info.calling_code; })); + } +}; + +struct CountryInfoManager::CountryList { + vector countries_; + int32 hash = 0; + double next_reload_time = 0.0; + + td_api::object_ptr get_countries_object() const { + return td_api::make_object( + transform(countries_, [](const CountryInfo &info) { return info.get_country_info_object(); })); + } +}; + +CountryInfoManager::CountryInfoManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void CountryInfoManager::tear_down() { + parent_.reset(); +} + +string CountryInfoManager::get_main_language_code() { + return to_lower(td_->language_pack_manager_->get_actor_unsafe()->get_main_language_code()); +} + +void CountryInfoManager::get_countries(Promise> &&promise) { + do_get_countries(get_main_language_code(), false, std::move(promise)); +} + +void CountryInfoManager::do_get_countries(string language_code, bool is_recursive, + Promise> &&promise) { + if (is_recursive) { + auto main_language_code = get_main_language_code(); + if (language_code != main_language_code) { + language_code = std::move(main_language_code); + is_recursive = false; + } + } + auto list = get_country_list(language_code); + if (list == nullptr) { + if (is_recursive) { + return promise.set_error(Status::Error(500, "Requested data is inaccessible")); + } + return load_country_list(language_code, 0, + PromiseCreator::lambda([actor_id = actor_id(this), language_code, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &CountryInfoManager::do_get_countries, std::move(language_code), + true, std::move(promise)); + })); + } + + promise.set_value(list->get_countries_object()); +} + +void CountryInfoManager::get_phone_number_info(string phone_number_prefix, + Promise> &&promise) { + td::remove_if(phone_number_prefix, [](char c) { return c < '0' || c > '9'; }); + if (phone_number_prefix.empty()) { + return promise.set_value(td_api::make_object(nullptr, string(), string())); + } + do_get_phone_number_info(std::move(phone_number_prefix), get_main_language_code(), false, std::move(promise)); +} + +void CountryInfoManager::do_get_phone_number_info(string phone_number_prefix, string language_code, bool is_recursive, + Promise> &&promise) { + if (is_recursive) { + auto main_language_code = get_main_language_code(); + if (language_code != main_language_code) { + language_code = std::move(main_language_code); + is_recursive = false; + } + } + auto list = get_country_list(language_code); + if (list == nullptr) { + if (is_recursive) { + return promise.set_error(Status::Error(500, "Requested data is inaccessible")); + } + return load_country_list(language_code, 0, + PromiseCreator::lambda([actor_id = actor_id(this), phone_number_prefix, language_code, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &CountryInfoManager::do_get_phone_number_info, + std::move(phone_number_prefix), std::move(language_code), true, + std::move(promise)); + })); + } + + Slice phone_number = phone_number_prefix; + const CountryInfo *best_country = nullptr; + const CallingCodeInfo *best_calling_code = nullptr; + size_t best_length = 0; + bool is_prefix = false; + for (auto &country : list->countries_) { + for (auto &calling_code : country.calling_codes) { + if (begins_with(phone_number, calling_code.calling_code)) { + auto calling_code_size = calling_code.calling_code.size(); + for (auto &prefix : calling_code.prefixes) { + if (begins_with(prefix, phone_number.substr(calling_code_size))) { + is_prefix = true; + } + if (calling_code_size + prefix.size() > best_length && + begins_with(phone_number.substr(calling_code_size), prefix)) { + best_country = &country; + best_calling_code = &calling_code; + best_length = calling_code_size + prefix.size(); + } + } + } + if (begins_with(calling_code.calling_code, phone_number)) { + is_prefix = true; + } + } + } + if (best_country == nullptr) { + return promise.set_value(td_api::make_object( + nullptr, is_prefix ? phone_number_prefix : string(), is_prefix ? string() : phone_number_prefix)); + } + + string formatted_part = phone_number_prefix.substr(best_calling_code->calling_code.size()); + string formatted_phone_number = formatted_part; + size_t max_matched_digits = 0; + for (auto &pattern : best_calling_code->patterns) { + string result; + size_t current_pattern_pos = 0; + bool is_failed_match = false; + size_t matched_digits = 0; + for (auto &c : formatted_part) { + while (current_pattern_pos < pattern.size() && pattern[current_pattern_pos] != 'X' && + !is_digit(pattern[current_pattern_pos])) { + result += pattern[current_pattern_pos++]; + } + if (current_pattern_pos == pattern.size()) { + result += c; + } else if (pattern[current_pattern_pos] == 'X') { + result += c; + current_pattern_pos++; + } else { + CHECK(is_digit(pattern[current_pattern_pos])); + if (c == pattern[current_pattern_pos]) { + matched_digits++; + result += c; + current_pattern_pos++; + } else { + is_failed_match = true; + break; + } + } + } + if (!is_failed_match && matched_digits >= max_matched_digits) { + max_matched_digits = matched_digits; + formatted_phone_number = std::move(result); + } + } + + promise.set_value(td_api::make_object( + best_country->get_country_info_object(), best_calling_code->calling_code, + formatted_phone_number.empty() ? formatted_part : formatted_phone_number)); +} + +void CountryInfoManager::get_current_country_code(Promise &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +void CountryInfoManager::load_country_list(string language_code, int32 hash, Promise &&promise) { + auto &queries = pending_load_country_queries_[language_code]; + if (!promise && !queries.empty()) { + return; + } + queries.push_back(std::move(promise)); + if (queries.size() == 1) { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), language_code](Result> &&result) { + send_closure(actor_id, &CountryInfoManager::on_get_country_list, language_code, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(language_code, hash); + } +} + +void CountryInfoManager::on_get_country_list(const string &language_code, + Result> r_country_list) { + auto query_it = pending_load_country_queries_.find(language_code); + CHECK(query_it != pending_load_country_queries_.end()); + auto promises = std::move(query_it->second); + CHECK(!promises.empty()); + pending_load_country_queries_.erase(query_it); + + auto &countries = countries_[language_code]; + if (r_country_list.is_error()) { + if (countries != nullptr) { + countries->next_reload_time = Time::now() + Random::fast(60, 120); + } + for (auto &promise : promises) { + promise.set_error(r_country_list.error().clone()); + } + return; + } + + auto country_list = r_country_list.move_as_ok(); + CHECK(country_list != nullptr); + switch (country_list->get_id()) { + case telegram_api::help_countriesListNotModified::ID: + if (countries == nullptr) { + LOG(ERROR) << "Receive countriesListNotModified for unknown list with language code " << language_code; + countries_.erase(language_code); + } else { + LOG(INFO) << "List of countries with language code " << language_code << " is not modified"; + countries->next_reload_time = Time::now() + Random::fast(86400, 2 * 86400); + } + break; + case telegram_api::help_countriesList::ID: { + auto list = move_tl_object_as(country_list); + if (countries == nullptr) { + countries = make_unique(); + } + for (auto &c : list->countries_) { + CountryInfo info; + info.country_code = std::move(c->iso2_); + info.default_name = std::move(c->default_name_); + info.name = std::move(c->name_); + info.is_hidden = std::move(c->hidden_); + for (auto &code : c->country_codes_) { + auto r_calling_code = to_integer_safe(code->country_code_); + if (r_calling_code.is_error() || r_calling_code.ok() <= 0) { + LOG(ERROR) << "Receive invalid calling code " << code->country_code_ << " for country " + << info.country_code; + } else { + CallingCodeInfo calling_code_info; + calling_code_info.calling_code = std::move(code->country_code_); + calling_code_info.prefixes = std::move(code->prefixes_); + if (calling_code_info.prefixes.empty()) { + calling_code_info.prefixes.resize(1); + } + calling_code_info.patterns = std::move(code->patterns_); + info.calling_codes.push_back(std::move(calling_code_info)); + } + } + if (info.calling_codes.empty()) { + LOG(ERROR) << "Receive empty list of calling codes for " << info.country_code; + continue; + } + + countries->countries_.push_back(std::move(info)); + } + countries->hash = list->hash_; + countries->next_reload_time = Time::now() + Random::fast(86400, 2 * 86400); + break; + } + default: + UNREACHABLE(); + } + + for (auto &promise : promises) { + promise.set_value(Unit()); + } +} + +const CountryInfoManager::CountryList *CountryInfoManager::get_country_list(const string &language_code) { + auto it = countries_.find(language_code); + if (it == countries_.end()) { + return nullptr; + } + + auto *country = it->second.get(); + CHECK(country != nullptr); + if (country->next_reload_time < Time::now()) { + load_country_list(language_code, country->hash, Auto()); + } + + return country; +} + +} // namespace td diff --git a/td/telegram/CountryInfoManager.h b/td/telegram/CountryInfoManager.h new file mode 100644 index 000000000..5602a40e0 --- /dev/null +++ b/td/telegram/CountryInfoManager.h @@ -0,0 +1,64 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/actor/actor.h" +#include "td/actor/PromiseFuture.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" + +#include + +namespace td { + +class Td; + +class CountryInfoManager : public Actor { + public: + explicit CountryInfoManager(Td *td, ActorShared<> parent); + + void get_countries(Promise> &&promise); + + void get_current_country_code(Promise &&promise); + + void get_phone_number_info(string phone_number_prefix, + Promise> &&promise); + + private: + void tear_down() override; + + struct CallingCodeInfo; + struct CountryInfo; + struct CountryList; + + string get_main_language_code(); + + void do_get_countries(string language_code, bool is_recursive, + Promise> &&promise); + + void do_get_phone_number_info(string phone_number_prefix, string language_code, bool is_recursive, + Promise> &&promise); + + void load_country_list(string language_code, int32 hash, Promise &&promise); + + void on_get_country_list(const string &language_code, + Result> r_country_list); + + const CountryList *get_country_list(const string &language_code); + + std::unordered_map>> pending_load_country_queries_; + std::unordered_map> countries_; + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index 16fd37374..6e4038bd5 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -864,7 +864,8 @@ tl_object_ptr copy(const td_api::minithumbnail &obj) { template <> tl_object_ptr copy(const td_api::photoSize &obj) { - return make_tl_object(obj.type_, copy(obj.photo_), obj.width_, obj.height_); + return make_tl_object(obj.type_, copy(obj.photo_), obj.width_, obj.height_, + vector(obj.progressive_sizes_)); } template <> diff --git a/td/telegram/LanguagePackManager.cpp b/td/telegram/LanguagePackManager.cpp index fcb4011c5..e7847ff2e 100644 --- a/td/telegram/LanguagePackManager.cpp +++ b/td/telegram/LanguagePackManager.cpp @@ -238,6 +238,50 @@ void LanguagePackManager::tear_down() { } } +string LanguagePackManager::get_main_language_code() { + if (language_pack_.empty() || language_code_.empty()) { + return "en"; + } + if (language_code_.size() <= 2) { + return language_code_; + } + + std::lock_guard packs_lock(database_->mutex_); + auto pack_it = database_->language_packs_.find(language_pack_); + CHECK(pack_it != database_->language_packs_.end()); + + LanguageInfo *info = nullptr; + LanguagePack *pack = pack_it->second.get(); + std::lock_guard languages_lock(pack->mutex_); + if (is_custom_language_code(language_code_)) { + auto custom_it = pack->custom_language_pack_infos_.find(language_code_); + if (custom_it != pack->custom_language_pack_infos_.end()) { + info = &custom_it->second; + } + } else { + for (auto &server_info : pack->server_language_pack_infos_) { + if (server_info.first == language_code_) { + info = &server_info.second; + } + } + } + + if (info == nullptr) { + LOG(ERROR) << "Failed to find information about chosen language " << language_code_; + if (!is_custom_language_code(language_code_)) { + search_language_info(language_code_, Auto()); + } + } else { + if (!info->base_language_code_.empty()) { + return info->base_language_code_; + } + if (!info->plural_code_.empty()) { + return info->plural_code_; + } + } + return "en"; +} + vector LanguagePackManager::get_used_language_codes() { if (language_pack_.empty() || language_code_.empty()) { return {}; diff --git a/td/telegram/LanguagePackManager.h b/td/telegram/LanguagePackManager.h index 311f79403..bc3af5cc0 100644 --- a/td/telegram/LanguagePackManager.h +++ b/td/telegram/LanguagePackManager.h @@ -43,6 +43,8 @@ class LanguagePackManager : public NetQueryCallback { static bool is_custom_language_code(Slice language_code); + string get_main_language_code(); + vector get_used_language_codes(); void on_language_pack_changed(); diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 4d156a3da..ed3774ede 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -33,7 +33,7 @@ #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.hpp" #include "td/telegram/MessageId.h" -#include "td/telegram/MessagesDb.h" +#include "td/telegram/MessageSearchFilter.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" #include "td/telegram/Payments.h" @@ -2447,7 +2447,7 @@ static int32 get_message_content_text_index_mask(const MessageContent *content) for (auto &entity : text->entities) { if (entity.type == MessageEntity::Type::Url || entity.type == MessageEntity::Type::EmailAddress || entity.type == MessageEntity::Type::TextUrl) { - return search_messages_filter_index_mask(SearchMessagesFilter::Url); + return message_search_filter_index_mask(MessageSearchFilter::Url); } } return 0; @@ -2457,43 +2457,43 @@ static int32 get_message_content_media_index_mask(const MessageContent *content, bool is_outgoing) { switch (content->get_type()) { case MessageContentType::Animation: - return search_messages_filter_index_mask(SearchMessagesFilter::Animation); + return message_search_filter_index_mask(MessageSearchFilter::Animation); case MessageContentType::Audio: { auto message_audio = static_cast(content); auto duration = td->audios_manager_->get_audio_duration(message_audio->file_id); - return is_secret || duration > 0 ? search_messages_filter_index_mask(SearchMessagesFilter::Audio) - : search_messages_filter_index_mask(SearchMessagesFilter::Document); + return is_secret || duration > 0 ? message_search_filter_index_mask(MessageSearchFilter::Audio) + : message_search_filter_index_mask(MessageSearchFilter::Document); } case MessageContentType::Document: - return search_messages_filter_index_mask(SearchMessagesFilter::Document); + return message_search_filter_index_mask(MessageSearchFilter::Document); case MessageContentType::Photo: - return search_messages_filter_index_mask(SearchMessagesFilter::Photo) | - search_messages_filter_index_mask(SearchMessagesFilter::PhotoAndVideo); + return message_search_filter_index_mask(MessageSearchFilter::Photo) | + message_search_filter_index_mask(MessageSearchFilter::PhotoAndVideo); case MessageContentType::Video: { auto message_video = static_cast(content); auto duration = td->videos_manager_->get_video_duration(message_video->file_id); - return is_secret || duration > 0 ? search_messages_filter_index_mask(SearchMessagesFilter::Video) | - search_messages_filter_index_mask(SearchMessagesFilter::PhotoAndVideo) - : search_messages_filter_index_mask(SearchMessagesFilter::Document); + return is_secret || duration > 0 ? message_search_filter_index_mask(MessageSearchFilter::Video) | + message_search_filter_index_mask(MessageSearchFilter::PhotoAndVideo) + : message_search_filter_index_mask(MessageSearchFilter::Document); } case MessageContentType::VideoNote: { auto message_video_note = static_cast(content); auto duration = td->video_notes_manager_->get_video_note_duration(message_video_note->file_id); - return is_secret || duration > 0 ? search_messages_filter_index_mask(SearchMessagesFilter::VideoNote) | - search_messages_filter_index_mask(SearchMessagesFilter::VoiceAndVideoNote) - : search_messages_filter_index_mask(SearchMessagesFilter::Document); + return is_secret || duration > 0 ? message_search_filter_index_mask(MessageSearchFilter::VideoNote) | + message_search_filter_index_mask(MessageSearchFilter::VoiceAndVideoNote) + : message_search_filter_index_mask(MessageSearchFilter::Document); } case MessageContentType::VoiceNote: - return search_messages_filter_index_mask(SearchMessagesFilter::VoiceNote) | - search_messages_filter_index_mask(SearchMessagesFilter::VoiceAndVideoNote); + return message_search_filter_index_mask(MessageSearchFilter::VoiceNote) | + message_search_filter_index_mask(MessageSearchFilter::VoiceAndVideoNote); case MessageContentType::ChatChangePhoto: - return search_messages_filter_index_mask(SearchMessagesFilter::ChatPhoto); + return message_search_filter_index_mask(MessageSearchFilter::ChatPhoto); case MessageContentType::Call: { - int32 index_mask = search_messages_filter_index_mask(SearchMessagesFilter::Call); + int32 index_mask = message_search_filter_index_mask(MessageSearchFilter::Call); auto message_call = static_cast(content); if (!is_outgoing && (message_call->discard_reason == CallDiscardReason::Declined || message_call->discard_reason == CallDiscardReason::Missed)) { - index_mask |= search_messages_filter_index_mask(SearchMessagesFilter::MissedCall); + index_mask |= message_search_filter_index_mask(MessageSearchFilter::MissedCall); } return index_mask; } @@ -3355,17 +3355,17 @@ void unregister_message_content(Td *td, const MessageContent *content, FullMessa template static tl_object_ptr secret_to_telegram(FromT &from); -// fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation; +// fileLocationUnavailable volume_id:long local_id:int secret:long = FileLocation; static auto secret_to_telegram(secret_api::fileLocationUnavailable &file_location) { return make_tl_object(file_location.volume_id_, file_location.local_id_); } -// fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation; +// fileLocation dc_id:int volume_id:long local_id:int secret:long = FileLocation; static auto secret_to_telegram(secret_api::fileLocation &file_location) { return make_tl_object(file_location.volume_id_, file_location.local_id_); } -// photoSizeEmpty#e17e23c type:string = PhotoSize; +// photoSizeEmpty type:string = PhotoSize; static auto secret_to_telegram(secret_api::photoSizeEmpty &empty) { if (!clean_input_string(empty.type_)) { empty.type_.clear(); @@ -3373,7 +3373,7 @@ static auto secret_to_telegram(secret_api::photoSizeEmpty &empty) { return make_tl_object(empty.type_); } -// photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; +// photoSize type:string location:FileLocation w:int h:int size:int = PhotoSize; static auto secret_to_telegram(secret_api::photoSize &photo_size) { if (!clean_input_string(photo_size.type_)) { photo_size.type_.clear(); @@ -3383,7 +3383,7 @@ static auto secret_to_telegram(secret_api::photoSize &photo_size) { photo_size.w_, photo_size.h_, photo_size.size_); } -// photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; +// photoCachedSize type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; static auto secret_to_telegram(secret_api::photoCachedSize &photo_size) { if (!clean_input_string(photo_size.type_)) { photo_size.type_.clear(); @@ -3393,17 +3393,17 @@ static auto secret_to_telegram(secret_api::photoCachedSize &photo_size) { photo_size.w_, photo_size.h_, photo_size.bytes_.clone()); } -// documentAttributeImageSize #6c37c15c w:int h:int = DocumentAttribute; +// documentAttributeImageSize w:int h:int = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeImageSize &image_size) { return make_tl_object(image_size.w_, image_size.h_); } -// documentAttributeAnimated #11b58939 = DocumentAttribute; +// documentAttributeAnimated = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeAnimated &animated) { return make_tl_object(); } -// documentAttributeSticker23 #fb0a5727 = DocumentAttribute; +// documentAttributeSticker23 = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeSticker23 &sticker) { return make_tl_object( 0, false /*ignored*/, "", make_tl_object(), nullptr); @@ -3420,7 +3420,7 @@ static auto secret_to_telegram(secret_api::inputStickerSetShortName &sticker_set return make_tl_object(sticker_set.short_name_); } -// documentAttributeSticker #3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute; +// documentAttributeSticker alt:string stickerset:InputStickerSet = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeSticker &sticker) { if (!clean_input_string(sticker.alt_)) { sticker.alt_.clear(); @@ -3430,13 +3430,13 @@ static auto secret_to_telegram(secret_api::documentAttributeSticker &sticker) { nullptr); } -// documentAttributeVideo #5910cccb duration:int w:int h:int = DocumentAttribute; +// documentAttributeVideo duration:int w:int h:int = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeVideo &video) { return make_tl_object(0, false /*ignored*/, false /*ignored*/, video.duration_, video.w_, video.h_); } -// documentAttributeFilename #15590068 file_name:string = DocumentAttribute; +// documentAttributeFilename file_name:string = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeFilename &filename) { if (!clean_input_string(filename.file_name_)) { filename.file_name_.clear(); @@ -3444,7 +3444,7 @@ static auto secret_to_telegram(secret_api::documentAttributeFilename &filename) return make_tl_object(filename.file_name_); } -// documentAttributeVideo66#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute; +// documentAttributeVideo66 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeVideo66 &video) { return make_tl_object( (video.flags_ & secret_api::documentAttributeVideo66::ROUND_MESSAGE_MASK) != 0 @@ -3479,16 +3479,16 @@ static auto telegram_documentAttributeAudio(bool is_voice_note, int duration, st std::move(performer), std::move(waveform)); } -// documentAttributeAudio23 #51448e5 duration:int = DocumentAttribute; +// documentAttributeAudio23 duration:int = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeAudio23 &audio) { return telegram_documentAttributeAudio(false, audio.duration_, "", "", Auto()); } -// documentAttributeAudio45 #ded218e0 duration:int title:string performer:string = DocumentAttribute; +// documentAttributeAudio45 duration:int title:string performer:string = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeAudio45 &audio) { return telegram_documentAttributeAudio(false, audio.duration_, audio.title_, audio.performer_, Auto()); } -// documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string +// documentAttributeAudio flags:# voice:flags.10?true duration:int title:flags.0?string // performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeAudio &audio) { return telegram_documentAttributeAudio((audio.flags_ & secret_api::documentAttributeAudio::VOICE_MASK) != 0, @@ -3506,7 +3506,7 @@ static auto secret_to_telegram(std::vector = DecryptedMessageMedia; static auto secret_to_telegram_document(secret_api::decryptedMessageMediaExternalDocument &from) { if (!clean_input_string(from.mime_type_)) { @@ -3764,7 +3764,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, tl_object_ptr &&media, DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id, int32 *ttl) { - if (!td->auth_manager_->is_authorized() && !G()->close_flag() && media != nullptr) { + if (!td->auth_manager_->was_authorized() && !G()->close_flag() && media != nullptr) { LOG(ERROR) << "Receive without authorization " << to_string(media); media = nullptr; } diff --git a/td/telegram/MessageEntity.cpp b/td/telegram/MessageEntity.cpp index 53db4f05c..f4938b8c4 100644 --- a/td/telegram/MessageEntity.cpp +++ b/td/telegram/MessageEntity.cpp @@ -26,6 +26,7 @@ namespace td { int MessageEntity::get_type_priority(Type type) { static const int types[] = {50, 50, 50, 50, 50, 90, 91, 20, 11, 10, 49, 49, 50, 50, 92, 93, 0, 50}; + static_assert(sizeof(types) / sizeof(types[0]) == static_cast(MessageEntity::Type::Size), ""); return types[static_cast(type)]; } diff --git a/td/telegram/MessageEntity.h b/td/telegram/MessageEntity.h index 7a1d5e9cf..48c33d8fc 100644 --- a/td/telegram/MessageEntity.h +++ b/td/telegram/MessageEntity.h @@ -47,7 +47,8 @@ class MessageEntity { Underline, Strikethrough, BlockQuote, - BankCardNumber + BankCardNumber, + Size }; Type type; int32 offset; diff --git a/td/telegram/MessageReplyInfo.cpp b/td/telegram/MessageReplyInfo.cpp new file mode 100644 index 000000000..faa84002b --- /dev/null +++ b/td/telegram/MessageReplyInfo.cpp @@ -0,0 +1,56 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// 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/MessageReplyInfo.h" + +#include "td/utils/logging.h" + +namespace td { + +MessageReplyInfo::MessageReplyInfo(tl_object_ptr &&reply_info, bool is_bot) { + if (reply_info == nullptr) { + return; + } + if (reply_info->replies_ < 0) { + LOG(ERROR) << "Receive wrong " << to_string(reply_info); + return; + } + reply_count = reply_info->replies_; + pts = reply_info->replies_pts_; + + if (!is_bot) { + for (auto &user_id_int : reply_info->recent_repliers_) { + UserId user_id(user_id_int); + if (user_id.is_valid()) { + recent_replier_user_ids.push_back(user_id); + } else { + LOG(ERROR) << "Receive " << user_id << " as a recent replier"; + } + } + } + + is_comment = reply_info->comments_; + if (is_comment) { + channel_id = ChannelId(reply_info->channel_id_); + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + channel_id = ChannelId(); + } + } +} + +bool MessageReplyInfo::need_update_to(const MessageReplyInfo &other) const { + if (other.pts < pts) { + return false; + } + return true; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageReplyInfo &reply_info) { + return string_builder << reply_info.reply_count << " replies by " << reply_info.recent_replier_user_ids; +} + +} // namespace td \ No newline at end of file diff --git a/td/telegram/MessageReplyInfo.h b/td/telegram/MessageReplyInfo.h new file mode 100644 index 000000000..07e4c65c0 --- /dev/null +++ b/td/telegram/MessageReplyInfo.h @@ -0,0 +1,79 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// 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/ChannelId.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +struct MessageReplyInfo { + int32 reply_count = -1; + int32 pts = -1; + vector recent_replier_user_ids; + ChannelId channel_id; + bool is_comment = false; + + MessageReplyInfo() = default; + + MessageReplyInfo(tl_object_ptr &&reply_info, bool is_bot); + + bool is_empty() const { + return reply_count < 0; + } + + bool need_update_to(const MessageReplyInfo &other) const; + + template + void store(StorerT &storer) const { + CHECK(!is_empty()); + bool has_recent_replier_user_ids = !recent_replier_user_ids.empty(); + bool has_channel_id = channel_id.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_comment); + STORE_FLAG(has_recent_replier_user_ids); + STORE_FLAG(has_channel_id); + END_STORE_FLAGS(); + td::store(reply_count, storer); + td::store(pts, storer); + if (has_recent_replier_user_ids) { + td::store(recent_replier_user_ids, storer); + } + if (has_channel_id) { + td::store(channel_id, storer); + } + } + + template + void parse(ParserT &parser) { + CHECK(!is_empty()); + bool has_recent_replier_user_ids = !recent_replier_user_ids.empty(); + bool has_channel_id = channel_id.is_valid(); + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_comment); + PARSE_FLAG(has_recent_replier_user_ids); + PARSE_FLAG(has_channel_id); + END_PARSE_FLAGS(); + td::parse(reply_count, parser); + td::parse(pts, parser); + if (has_recent_replier_user_ids) { + td::parse(recent_replier_user_ids, parser); + } + if (has_channel_id) { + td::parse(channel_id, parser); + } + } +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageReplyInfo &reply_info); + +} // namespace td \ No newline at end of file diff --git a/td/telegram/MessageSearchFilter.cpp b/td/telegram/MessageSearchFilter.cpp new file mode 100644 index 000000000..baec0b1f2 --- /dev/null +++ b/td/telegram/MessageSearchFilter.cpp @@ -0,0 +1,141 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// 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/MessageSearchFilter.h" + +#include "td/utils/common.h" + +namespace td { + +tl_object_ptr get_input_messages_filter(MessageSearchFilter filter) { + switch (filter) { + case MessageSearchFilter::Empty: + return make_tl_object(); + case MessageSearchFilter::Animation: + return make_tl_object(); + case MessageSearchFilter::Audio: + return make_tl_object(); + case MessageSearchFilter::Document: + return make_tl_object(); + case MessageSearchFilter::Photo: + return make_tl_object(); + case MessageSearchFilter::Video: + return make_tl_object(); + case MessageSearchFilter::VoiceNote: + return make_tl_object(); + case MessageSearchFilter::PhotoAndVideo: + return make_tl_object(); + case MessageSearchFilter::Url: + return make_tl_object(); + case MessageSearchFilter::ChatPhoto: + return make_tl_object(); + case MessageSearchFilter::Call: + return make_tl_object(0, false /*ignored*/); + case MessageSearchFilter::MissedCall: + return make_tl_object( + telegram_api::inputMessagesFilterPhoneCalls::MISSED_MASK, false /*ignored*/); + case MessageSearchFilter::VideoNote: + return make_tl_object(); + case MessageSearchFilter::VoiceAndVideoNote: + return make_tl_object(); + case MessageSearchFilter::Mention: + return make_tl_object(); + case MessageSearchFilter::UnreadMention: + case MessageSearchFilter::FailedToSend: + default: + UNREACHABLE(); + return nullptr; + } +} + +MessageSearchFilter get_message_search_filter(const tl_object_ptr &filter) { + if (filter == nullptr) { + return MessageSearchFilter::Empty; + } + switch (filter->get_id()) { + case td_api::searchMessagesFilterEmpty::ID: + return MessageSearchFilter::Empty; + case td_api::searchMessagesFilterAnimation::ID: + return MessageSearchFilter::Animation; + case td_api::searchMessagesFilterAudio::ID: + return MessageSearchFilter::Audio; + case td_api::searchMessagesFilterDocument::ID: + return MessageSearchFilter::Document; + case td_api::searchMessagesFilterPhoto::ID: + return MessageSearchFilter::Photo; + case td_api::searchMessagesFilterVideo::ID: + return MessageSearchFilter::Video; + case td_api::searchMessagesFilterVoiceNote::ID: + return MessageSearchFilter::VoiceNote; + case td_api::searchMessagesFilterPhotoAndVideo::ID: + return MessageSearchFilter::PhotoAndVideo; + case td_api::searchMessagesFilterUrl::ID: + return MessageSearchFilter::Url; + case td_api::searchMessagesFilterChatPhoto::ID: + return MessageSearchFilter::ChatPhoto; + case td_api::searchMessagesFilterCall::ID: + return MessageSearchFilter::Call; + case td_api::searchMessagesFilterMissedCall::ID: + return MessageSearchFilter::MissedCall; + case td_api::searchMessagesFilterVideoNote::ID: + return MessageSearchFilter::VideoNote; + case td_api::searchMessagesFilterVoiceAndVideoNote::ID: + return MessageSearchFilter::VoiceAndVideoNote; + case td_api::searchMessagesFilterMention::ID: + return MessageSearchFilter::Mention; + case td_api::searchMessagesFilterUnreadMention::ID: + return MessageSearchFilter::UnreadMention; + case td_api::searchMessagesFilterFailedToSend::ID: + return MessageSearchFilter::FailedToSend; + default: + UNREACHABLE(); + return MessageSearchFilter::Empty; + } +} + +StringBuilder &operator<<(StringBuilder &string_builder, MessageSearchFilter filter) { + switch (filter) { + case MessageSearchFilter::Empty: + return string_builder << "Empty"; + case MessageSearchFilter::Animation: + return string_builder << "Animation"; + case MessageSearchFilter::Audio: + return string_builder << "Audio"; + case MessageSearchFilter::Document: + return string_builder << "Document"; + case MessageSearchFilter::Photo: + return string_builder << "Photo"; + case MessageSearchFilter::Video: + return string_builder << "Video"; + case MessageSearchFilter::VoiceNote: + return string_builder << "VoiceNote"; + case MessageSearchFilter::PhotoAndVideo: + return string_builder << "PhotoAndVideo"; + case MessageSearchFilter::Url: + return string_builder << "Url"; + case MessageSearchFilter::ChatPhoto: + return string_builder << "ChatPhoto"; + case MessageSearchFilter::Call: + return string_builder << "Call"; + case MessageSearchFilter::MissedCall: + return string_builder << "MissedCall"; + case MessageSearchFilter::VideoNote: + return string_builder << "VideoNote"; + case MessageSearchFilter::VoiceAndVideoNote: + return string_builder << "VoiceAndVideoNote"; + case MessageSearchFilter::Mention: + return string_builder << "Mention"; + case MessageSearchFilter::UnreadMention: + return string_builder << "UnreadMention"; + case MessageSearchFilter::FailedToSend: + return string_builder << "FailedToSend"; + default: + UNREACHABLE(); + return string_builder; + } +} + +} // namespace td diff --git a/td/telegram/MessageSearchFilter.h b/td/telegram/MessageSearchFilter.h new file mode 100644 index 000000000..b5f25bb26 --- /dev/null +++ b/td/telegram/MessageSearchFilter.h @@ -0,0 +1,66 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +// append only before Size +enum class MessageSearchFilter : int32 { + Empty, + Animation, + Audio, + Document, + Photo, + Video, + VoiceNote, + PhotoAndVideo, + Url, + ChatPhoto, + Call, + MissedCall, + VideoNote, + VoiceAndVideoNote, + Mention, + UnreadMention, + FailedToSend, + Size +}; + +inline constexpr size_t message_search_filter_count() { + return static_cast(MessageSearchFilter::Size) - 1; +} + +inline int32 message_search_filter_index(MessageSearchFilter filter) { + CHECK(filter != MessageSearchFilter::Empty); + return static_cast(filter) - 1; +} + +inline int32 message_search_filter_index_mask(MessageSearchFilter filter) { + if (filter == MessageSearchFilter::Empty) { + return 0; + } + return 1 << message_search_filter_index(filter); +} + +inline int32 call_message_search_filter_index(MessageSearchFilter filter) { + CHECK(filter == MessageSearchFilter::Call || filter == MessageSearchFilter::MissedCall); + return static_cast(filter) - static_cast(MessageSearchFilter::Call); +} + +tl_object_ptr get_input_messages_filter(MessageSearchFilter filter); + +MessageSearchFilter get_message_search_filter(const tl_object_ptr &filter); + +StringBuilder &operator<<(StringBuilder &string_builder, MessageSearchFilter filter); + +} // namespace td diff --git a/td/telegram/MessagesDb.cpp b/td/telegram/MessagesDb.cpp index 62b0c5575..d6190e549 100644 --- a/td/telegram/MessagesDb.cpp +++ b/td/telegram/MessagesDb.cpp @@ -7,6 +7,7 @@ #include "td/telegram/MessagesDb.h" #include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/MessageSearchFilter.h" #include "td/telegram/Version.h" #include "td/db/SqliteConnectionSafe.h" @@ -85,12 +86,12 @@ Status init_messages_db(SqliteDb &db, int32 version) { return Status::OK(); }; - auto add_call_index = [&db]() { - // for (int i = static_cast(SearchMessagesFilter::Call) - 1; - // i < static_cast(SearchMessagesFilter::MissedCall); i++) { - // TRY_STATUS(db.exec(PSLICE() << "CREATE INDEX IF NOT EXISTS full_message_index_" << i - // << " ON messages (unique_message_id) WHERE (index_mask & " << (1 << i) << ") != 0")); - // } + auto add_call_index = [&db] { +// for (int i = static_cast(MessageSearchFilter::Call) - 1; i < static_cast(MessageSearchFilter::MissedCall); +// i++) { +// TRY_STATUS(db.exec(PSLICE() << "CREATE INDEX IF NOT EXISTS full_message_index_" << i +// << " ON messages (unique_message_id) WHERE (index_mask & " << (1 << i) << ") != 0")); +// } return Status::OK(); }; auto add_notification_id_index = [&db] { @@ -241,8 +242,8 @@ class MessagesDbImpl : public MessagesDbSyncInterface { // LOG(ERROR) << get_messages_from_index_stmts_[i].asc_stmt_.explain().ok(); } - for (int i = static_cast(SearchMessagesFilter::Call) - 1, pos = 0; - i < static_cast(SearchMessagesFilter::MissedCall); i++, pos++) { + for (int i = static_cast(MessageSearchFilter::Call) - 1, pos = 0; + i < static_cast(MessageSearchFilter::MissedCall); i++, pos++) { TRY_RESULT_ASSIGN( get_calls_stmts_[pos], db_memory_.get_statement( @@ -765,12 +766,12 @@ class MessagesDbImpl : public MessagesDbSyncInterface { return Status::Error("Union is not supported"); } int32 pos; - if (index_i + 1 == static_cast(SearchMessagesFilter::Call)) { + if (index_i + 1 == static_cast(MessageSearchFilter::Call)) { pos = 0; - } else if (index_i + 1 == static_cast(SearchMessagesFilter::MissedCall)) { + } else if (index_i + 1 == static_cast(MessageSearchFilter::MissedCall)) { pos = 1; } else { - return Status::Error(PSLICE() << "Index_mask is not Call or MissedCall " << query.index_mask); + return Status::Error(PSLICE() << "Index mask is not Call or MissedCall " << query.index_mask); } auto &stmt = get_calls_stmts_[pos]; diff --git a/td/telegram/MessagesDb.h b/td/telegram/MessagesDb.h index 565225ae1..c8683cafd 100644 --- a/td/telegram/MessagesDb.h +++ b/td/telegram/MessagesDb.h @@ -26,28 +26,6 @@ namespace td { class SqliteConnectionSafe; class SqliteDb; -// append only before Size -enum class SearchMessagesFilter : int32 { - Empty, - Animation, - Audio, - Document, - Photo, - Video, - VoiceNote, - PhotoAndVideo, - Url, - ChatPhoto, - Call, - MissedCall, - VideoNote, - VoiceAndVideoNote, - Mention, - UnreadMention, - FailedToSend, - Size -}; - struct MessagesDbMessagesQuery { DialogId dialog_id; int32 index_mask{0}; @@ -178,25 +156,4 @@ std::shared_ptr create_messages_db_sync( std::shared_ptr create_messages_db_async(std::shared_ptr sync_db, int32 scheduler_id); -inline constexpr size_t search_messages_filter_size() { - return static_cast(SearchMessagesFilter::Size) - 1; -} - -inline int32 search_messages_filter_index(SearchMessagesFilter filter) { - CHECK(filter != SearchMessagesFilter::Empty); - return static_cast(filter) - 1; -} - -inline int32 search_messages_filter_index_mask(SearchMessagesFilter filter) { - if (filter == SearchMessagesFilter::Empty) { - return 0; - } - return 1 << search_messages_filter_index(filter); -} - -inline int32 search_calls_filter_index(SearchMessagesFilter filter) { - CHECK(filter == SearchMessagesFilter::Call || filter == SearchMessagesFilter::MissedCall); - return static_cast(filter) - static_cast(SearchMessagesFilter::Call); -} - } // namespace td diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index f803cec7b..f1fd5947c 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -1274,8 +1274,8 @@ class GetMessagesViewsQuery : public Td::ResultHandler { auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { - LOG(ERROR) << "Can't update message views because doesn't have info about the " << dialog_id; - return on_error(0, Status::Error(500, "Can't update message views")); + LOG(ERROR) << "Can't update message view count because doesn't have info about the " << dialog_id; + return on_error(0, Status::Error(500, "Can't update message view count")); } LOG(INFO) << "View " << message_ids_.size() << " messages in " << dialog_id @@ -1290,14 +1290,21 @@ class GetMessagesViewsQuery : public Td::ResultHandler { return on_error(id, result_ptr.move_as_error()); } - vector views = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetMessagesViewsQuery: " << format::as_array(views); - if (message_ids_.size() != views.size()) { + auto result = result_ptr.move_as_ok(); + auto interaction_infos = std::move(result->views_); + if (message_ids_.size() != interaction_infos.size()) { return on_error(id, Status::Error(500, "Wrong number of message views returned")); } - + td->contacts_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery"); for (size_t i = 0; i < message_ids_.size(); i++) { - td->messages_manager_->on_update_message_views({dialog_id_, message_ids_[i]}, views[i]); + FullMessageId full_message_id{dialog_id_, message_ids_[i]}; + + auto *info = interaction_infos[i].get(); + auto flags = info->flags_; + auto view_count = (flags & telegram_api::messageViews::VIEWS_MASK) != 0 ? info->views_ : 0; + auto forward_count = (flags & telegram_api::messageViews::FORWARDS_MASK) != 0 ? info->forwards_ : 0; + td->messages_manager_->on_update_message_interaction_info(full_message_id, view_count, forward_count, true, + std::move(info->replies_)); } } @@ -1585,7 +1592,7 @@ class SearchMessagesQuery : public Td::ResultHandler { MessageId from_message_id_; int32 offset_; int32 limit_; - SearchMessagesFilter filter_; + MessageSearchFilter filter_; int64 random_id_; public: @@ -1594,7 +1601,7 @@ class SearchMessagesQuery : public Td::ResultHandler { void send(DialogId dialog_id, const string &query, UserId sender_user_id, telegram_api::object_ptr &&sender_input_user, MessageId from_message_id, - int32 offset, int32 limit, SearchMessagesFilter filter, int64 random_id) { + int32 offset, int32 limit, MessageSearchFilter filter, int64 random_id) { auto input_peer = dialog_id.is_valid() ? td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read) : make_tl_object(); if (input_peer == nullptr) { @@ -1611,7 +1618,7 @@ class SearchMessagesQuery : public Td::ResultHandler { filter_ = filter; random_id_ = random_id; - if (filter == SearchMessagesFilter::UnreadMention) { + if (filter == MessageSearchFilter::UnreadMention) { send_query(G()->net_query_creator().create( telegram_api::messages_getUnreadMentions(std::move(input_peer), from_message_id.get_server_message_id().get(), offset, limit, std::numeric_limits::max(), 0))); @@ -1622,9 +1629,9 @@ class SearchMessagesQuery : public Td::ResultHandler { } send_query(G()->net_query_creator().create(telegram_api::messages_search( - flags, std::move(input_peer), query, std::move(sender_input_user), - MessagesManager::get_input_messages_filter(filter), 0, std::numeric_limits::max(), - from_message_id.get_server_message_id().get(), offset, limit, std::numeric_limits::max(), 0, 0))); + flags, std::move(input_peer), query, std::move(sender_input_user), 0, get_input_messages_filter(filter), 0, + std::numeric_limits::max(), from_message_id.get_server_message_id().get(), offset, limit, + std::numeric_limits::max(), 0, 0))); } } @@ -1659,6 +1666,7 @@ class SearchMessagesGlobalQuery : public Td::ResultHandler { DialogId offset_dialog_id_; MessageId offset_message_id_; int32 limit_; + MessageSearchFilter filter_; int64 random_id_; public: @@ -1666,13 +1674,15 @@ class SearchMessagesGlobalQuery : public Td::ResultHandler { } void send(FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date, - DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, int64 random_id) { + DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter, + int64 random_id) { query_ = query; offset_date_ = offset_date; offset_dialog_id_ = offset_dialog_id; offset_message_id_ = offset_message_id; limit_ = limit; random_id_ = random_id; + filter_ = filter; auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id); CHECK(input_peer != nullptr); @@ -1681,9 +1691,9 @@ class SearchMessagesGlobalQuery : public Td::ResultHandler { if (!ignore_folder_id) { flags |= telegram_api::messages_searchGlobal::FOLDER_ID_MASK; } - send_query(G()->net_query_creator().create( - telegram_api::messages_searchGlobal(flags, folder_id.get(), query, offset_date_, std::move(input_peer), - offset_message_id.get_server_message_id().get(), limit))); + send_query(G()->net_query_creator().create(telegram_api::messages_searchGlobal( + flags, folder_id.get(), query, get_input_messages_filter(filter), 0, 0, offset_date_, std::move(input_peer), + offset_message_id.get_server_message_id().get(), limit))); } void on_result(uint64 id, BufferSlice packet) override { @@ -1694,7 +1704,7 @@ class SearchMessagesGlobalQuery : public Td::ResultHandler { auto info = td->messages_manager_->on_get_messages(result_ptr.move_as_ok(), "SearchMessagesGlobalQuery"); td->messages_manager_->on_get_messages_search_result(query_, offset_date_, offset_dialog_id_, offset_message_id_, - limit_, random_id_, info.total_count, + limit_, filter_, random_id_, info.total_count, std::move(info.messages)); promise_.set_value(Unit()); @@ -1793,6 +1803,52 @@ class GetRecentLocationsQuery : public Td::ResultHandler { } }; +class GetMessagePublicForwardsQuery : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + int32 limit_; + int64 random_id_; + + public: + explicit GetMessagePublicForwardsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(FullMessageId full_message_id, int32 offset_date, DialogId offset_dialog_id, + ServerMessageId offset_message_id, int32 limit, int64 random_id) { + dialog_id_ = full_message_id.get_dialog_id(); + limit_ = limit; + random_id_ = random_id; + + auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::stats_getMessagePublicForwards( + td->contacts_manager_->get_input_channel(dialog_id_.get_channel_id()), + full_message_id.get_message_id().get_server_message_id().get(), offset_date, std::move(input_peer), + offset_message_id.get(), limit))); + } + + void on_result(uint64 id, BufferSlice packet) override { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(id, result_ptr.move_as_error()); + } + + auto info = td->messages_manager_->on_get_messages(result_ptr.move_as_ok(), "GetMessagePublicForwardsQuery"); + LOG_IF(ERROR, !info.is_channel_messages) << "Receive ordinary messages in GetMessagePublicForwardsQuery"; + td->messages_manager_->on_get_message_public_forwards_result(random_id_, info.total_count, + std::move(info.messages)); + + promise_.set_value(Unit()); + } + + void on_error(uint64 id, Status status) override { + td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePublicForwardsQuery"); + td->messages_manager_->on_failed_get_message_public_forwards(random_id_); + promise_.set_error(std::move(status)); + } +}; + class HidePromoDataQuery : public Td::ResultHandler { DialogId dialog_id_; @@ -4123,7 +4179,7 @@ void MessagesManager::Message::store(StorerT &storer) const { bool is_reply = reply_to_message_id.is_valid(); bool is_reply_to_random_id = reply_to_random_id != 0; bool is_via_bot = via_bot_user_id.is_valid(); - bool has_views = views > 0; + bool has_view_count = view_count > 0; bool has_reply_markup = reply_markup != nullptr; bool has_ttl = ttl != 0; bool has_author_signature = !author_signature.empty(); @@ -4140,6 +4196,8 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_legacy_layer = legacy_layer != 0; bool has_restriction_reasons = !restriction_reasons.empty(); bool has_forward_psa_type = is_forwarded && !forward_info->psa_type.empty(); + bool has_forward_count = forward_count > 0; + bool has_reply_info = !reply_info.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); STORE_FLAG(is_outgoing); @@ -4158,7 +4216,7 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(is_reply); STORE_FLAG(is_reply_to_random_id); STORE_FLAG(is_via_bot); - STORE_FLAG(has_views); + STORE_FLAG(has_view_count); STORE_FLAG(has_reply_markup); STORE_FLAG(has_ttl); STORE_FLAG(has_author_signature); @@ -4188,6 +4246,8 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(is_from_scheduled); STORE_FLAG(is_copy); STORE_FLAG(has_forward_psa_type); + STORE_FLAG(has_forward_count); + STORE_FLAG(has_reply_info); END_STORE_FLAGS(); } @@ -4237,8 +4297,14 @@ void MessagesManager::Message::store(StorerT &storer) const { if (is_via_bot) { store(via_bot_user_id, storer); } - if (has_views) { - store(views, storer); + if (has_view_count) { + store(view_count, storer); + } + if (has_forward_count) { + store(forward_count, storer); + } + if (has_reply_info) { + store(reply_info, storer); } if (has_ttl) { store(ttl, storer); @@ -4283,7 +4349,7 @@ void MessagesManager::Message::parse(ParserT &parser) { bool is_reply; bool is_reply_to_random_id; bool is_via_bot; - bool has_views; + bool has_view_count; bool has_reply_markup; bool has_ttl; bool has_author_signature; @@ -4299,6 +4365,8 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_legacy_layer = false; bool has_restriction_reasons = false; bool has_forward_psa_type = false; + bool has_forward_count = false; + bool has_reply_info = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_channel_post); PARSE_FLAG(is_outgoing); @@ -4317,7 +4385,7 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(is_reply); PARSE_FLAG(is_reply_to_random_id); PARSE_FLAG(is_via_bot); - PARSE_FLAG(has_views); + PARSE_FLAG(has_view_count); PARSE_FLAG(has_reply_markup); PARSE_FLAG(has_ttl); PARSE_FLAG(has_author_signature); @@ -4347,6 +4415,8 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(is_from_scheduled); PARSE_FLAG(is_copy); PARSE_FLAG(has_forward_psa_type); + PARSE_FLAG(has_forward_count); + PARSE_FLAG(has_reply_info); END_PARSE_FLAGS(); } @@ -4402,8 +4472,14 @@ void MessagesManager::Message::parse(ParserT &parser) { if (is_via_bot) { parse(via_bot_user_id, parser); } - if (has_views) { - parse(views, parser); + if (has_view_count) { + parse(view_count, parser); + } + if (has_forward_count) { + parse(forward_count, parser); + } + if (has_reply_info) { + parse(reply_info, parser); } if (has_ttl) { parse(ttl, parser); @@ -4802,7 +4878,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { parse(message_count_by_index[i], parser); } } - unread_mention_count = message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)]; + unread_mention_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)]; LOG(INFO) << "Set unread mention message count in " << dialog_id << " to " << unread_mention_count; if (unread_mention_count < 0) { unread_mention_count = 0; @@ -5169,7 +5245,7 @@ void MessagesManager::invalidate_message_indexes(Dialog *d) { CHECK(d != nullptr); bool is_secret = d->dialog_id.get_type() == DialogType::SecretChat; for (size_t i = 0; i < d->message_count_by_index.size(); i++) { - if (is_secret || i == static_cast(search_messages_filter_index(SearchMessagesFilter::FailedToSend))) { + if (is_secret || i == static_cast(message_search_filter_index(MessageSearchFilter::FailedToSend))) { // always know all messages d->first_database_message_id_by_index[i] = MessageId::min(); // keep the count @@ -5183,8 +5259,8 @@ void MessagesManager::invalidate_message_indexes(Dialog *d) { void MessagesManager::update_message_count_by_index(Dialog *d, int diff, const Message *m) { auto index_mask = get_message_index_mask(d->dialog_id, m); - index_mask &= ~search_messages_filter_index_mask( - SearchMessagesFilter::UnreadMention); // unread mention count has been already manually updated + index_mask &= ~message_search_filter_index_mask( + MessageSearchFilter::UnreadMention); // unread mention count has been already manually updated update_message_count_by_index(d, diff, index_mask); } @@ -5201,7 +5277,7 @@ void MessagesManager::update_message_count_by_index(Dialog *d, int diff, int32 i message_count += diff; if (message_count < 0) { if (d->dialog_id.get_type() == DialogType::SecretChat || - i == search_messages_filter_index(SearchMessagesFilter::FailedToSend)) { + i == message_search_filter_index(MessageSearchFilter::FailedToSend)) { message_count = 0; } else { message_count = -1; @@ -5212,7 +5288,7 @@ void MessagesManager::update_message_count_by_index(Dialog *d, int diff, int32 i i++; } - i = static_cast(SearchMessagesFilter::Call) - 1; + i = static_cast(MessageSearchFilter::Call) - 1; for (auto &message_count : calls_db_state_.message_count_by_index) { if (((index_mask >> i) & 1) != 0 && message_count != -1) { message_count += diff; @@ -5235,7 +5311,7 @@ int32 MessagesManager::get_message_index_mask(DialogId dialog_id, const Message return 0; } if (m->is_failed_to_send) { - return search_messages_filter_index_mask(SearchMessagesFilter::FailedToSend); + return message_search_filter_index_mask(MessageSearchFilter::FailedToSend); } bool is_secret = dialog_id.get_type() == DialogType::SecretChat; if (!m->message_id.is_server() && !is_secret) { @@ -5247,9 +5323,9 @@ int32 MessagesManager::get_message_index_mask(DialogId dialog_id, const Message } int32 index_mask = get_message_content_index_mask(m->content.get(), td_, is_secret, m->is_outgoing); if (m->contains_mention) { - index_mask |= search_messages_filter_index_mask(SearchMessagesFilter::Mention); + index_mask |= message_search_filter_index_mask(MessageSearchFilter::Mention); if (m->contains_unread_mention) { - index_mask |= search_messages_filter_index_mask(SearchMessagesFilter::UnreadMention); + index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadMention); } } LOG(INFO) << "Have index mask " << index_mask << " for " << m->message_id << " in " << dialog_id; @@ -5952,27 +6028,30 @@ void MessagesManager::on_update_channel_too_long(tl_object_ptr d->last_new_message_id && - dialog_id.get_type() == DialogType::Channel) { - get_channel_difference(dialog_id, d->pts, true, "on_update_message_views"); - } +void MessagesManager::on_update_message_view_count(FullMessageId full_message_id, int32 view_count) { + if (view_count < 0) { + LOG(ERROR) << "Receive " << view_count << " views in updateChannelMessageViews for " << full_message_id; return; } + update_message_interaction_info(full_message_id, view_count, -1, false, nullptr); +} - if (update_message_views(dialog_id, m, views)) { - on_message_changed(d, m, true, "on_update_message_views"); +void MessagesManager::on_update_message_forward_count(FullMessageId full_message_id, int32 forward_count) { + if (forward_count < 0) { + LOG(ERROR) << "Receive " << forward_count << " forwards in updateChannelMessageForwards for " << full_message_id; + return; } + update_message_interaction_info(full_message_id, -1, forward_count, false, nullptr); +} + +void MessagesManager::on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count, + int32 forward_count, bool has_reply_info, + tl_object_ptr &&reply_info) { + if (view_count < 0 || forward_count < 0) { + LOG(ERROR) << "Receive " << view_count << "/" << forward_count << " interaction counters for " << full_message_id; + return; + } + update_message_interaction_info(full_message_id, view_count, forward_count, has_reply_info, std::move(reply_info)); } void MessagesManager::on_pending_message_views_timeout(DialogId dialog_id) { @@ -6000,14 +6079,82 @@ void MessagesManager::on_pending_message_views_timeout(DialogId dialog_id) { d->increment_view_counter = false; } -bool MessagesManager::update_message_views(DialogId dialog_id, Message *m, int32 views) { +void MessagesManager::update_message_interaction_info(FullMessageId full_message_id, int32 view_count, + int32 forward_count, bool has_reply_info, + tl_object_ptr &&reply_info) { + auto dialog_id = full_message_id.get_dialog_id(); + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + return; + } + auto message_id = full_message_id.get_message_id(); + Message *m = get_message_force(d, message_id, "update_message_interaction_info"); + if (m == nullptr) { + LOG(INFO) << "Ignore message interaction info about unknown " << full_message_id; + if (!message_id.is_scheduled() && message_id > d->last_new_message_id && + dialog_id.get_type() == DialogType::Channel) { + get_channel_difference(dialog_id, d->pts, true, "update_message_interaction_info"); + } + return; + } + + if (view_count < 0) { + view_count = m->view_count; + } + if (forward_count < 0) { + forward_count = m->forward_count; + } + bool is_empty_reply_info = reply_info == nullptr; + MessageReplyInfo new_reply_info(std::move(reply_info), td_->auth_manager_->is_bot()); + if (new_reply_info.is_empty() && !is_empty_reply_info) { + has_reply_info = false; + } + + if (update_message_interaction_info(dialog_id, m, view_count, forward_count, has_reply_info, + std::move(new_reply_info))) { + on_message_changed(d, m, true, "update_message_interaction_info"); + } +} + +td_api::object_ptr MessagesManager::get_message_interaction_info_object( + DialogId dialog_id, const Message *m) const { + if (m->view_count == 0 && m->forward_count == 0 && m->reply_info.is_empty()) { + return nullptr; + } + if (m->message_id.is_scheduled() && (m->forward_info == nullptr || is_broadcast_channel(dialog_id))) { + return nullptr; + } + if (m->message_id.is_local() && m->forward_info == nullptr) { + return nullptr; + } + + return td_api::make_object( + m->view_count, m->forward_count, m->reply_info.reply_count, + td_->contacts_manager_->get_user_ids_object(m->reply_info.recent_replier_user_ids, + "get_message_interaction_info_object")); +} + +bool MessagesManager::update_message_interaction_info(DialogId dialog_id, Message *m, int32 view_count, + int32 forward_count, bool has_reply_info, + MessageReplyInfo &&reply_info) { CHECK(m != nullptr); - if (views > m->views) { - LOG(DEBUG) << "Update views of " << FullMessageId{dialog_id, m->message_id} << " from " << m->views << " to " - << views; - m->views = views; + bool need_update_reply_info = has_reply_info && m->reply_info.need_update_to(reply_info); + if (view_count > m->view_count || forward_count > m->forward_count || need_update_reply_info) { + LOG(DEBUG) << "Update interaction info of " << FullMessageId{dialog_id, m->message_id} << " from " << m->view_count + << '/' << m->forward_count << "/" << m->reply_info << " to " << view_count << '/' << forward_count << "/" + << reply_info; + if (view_count > m->view_count) { + m->view_count = view_count; + } + if (forward_count > m->forward_count) { + m->forward_count = forward_count; + } + if (need_update_reply_info) { + m->reply_info = std::move(reply_info); + } send_closure(G()->td(), &Td::send_update, - make_tl_object(dialog_id.get(), m->message_id.get(), m->views)); + make_tl_object( + dialog_id.get(), m->message_id.get(), get_message_interaction_info_object(dialog_id, m))); return true; } return false; @@ -8505,7 +8652,7 @@ void MessagesManager::on_failed_public_dialogs_search(const string &query, Statu void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, - int32 offset, int32 limit, SearchMessagesFilter filter, + int32 offset, int32 limit, MessageSearchFilter filter, int64 random_id, int32 total_count, vector> &&messages) { LOG(INFO) << "Receive " << messages.size() << " found messages in " << dialog_id; @@ -8546,7 +8693,7 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c if (G()->parameters().use_message_db) { bool update_state = false; - auto &old_message_count = calls_db_state_.message_count_by_index[search_calls_filter_index(filter)]; + 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; @@ -8554,7 +8701,7 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c } auto &old_first_db_message_id = - calls_db_state_.first_calls_database_message_id_by_index[search_calls_filter_index(filter)]; + 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; @@ -8600,7 +8747,7 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c } auto message_id = new_full_message_id.get_message_id(); - if (filter == SearchMessagesFilter::UnreadMention && message_id <= d->last_read_all_mentions_message_id) { + if (filter == MessageSearchFilter::UnreadMention && message_id <= d->last_read_all_mentions_message_id) { total_count--; continue; } @@ -8616,14 +8763,14 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c << " messages"; total_count = static_cast(result.size()); } - if (query.empty() && !sender_user_id.is_valid() && filter != SearchMessagesFilter::Empty && + if (query.empty() && !sender_user_id.is_valid() && filter != MessageSearchFilter::Empty && G()->parameters().use_message_db) { bool update_dialog = false; - auto &old_message_count = d->message_count_by_index[search_messages_filter_index(filter)]; + auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)]; if (old_message_count != total_count) { old_message_count = total_count; - if (filter == SearchMessagesFilter::UnreadMention) { + if (filter == MessageSearchFilter::UnreadMention) { d->unread_mention_count = old_message_count; update_dialog_mention_notification_count(d); send_update_chat_unread_mention_count(d); @@ -8631,7 +8778,7 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c update_dialog = true; } - auto &old_first_db_message_id = d->first_database_message_id_by_index[search_messages_filter_index(filter)]; + auto &old_first_db_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)]; bool from_the_end = !from_message_id.is_valid() || (d->last_message_id != MessageId() && from_message_id > d->last_message_id) || from_message_id >= MessageId::max(); @@ -8661,8 +8808,8 @@ void MessagesManager::on_failed_dialog_messages_search(DialogId dialog_id, int64 } void MessagesManager::on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id, - MessageId offset_message_id, int32 limit, int64 random_id, - int32 total_count, + MessageId offset_message_id, int32 limit, + MessageSearchFilter filter, int64 random_id, int32 total_count, vector> &&messages) { LOG(INFO) << "Receive " << messages.size() << " found messages"; auto it = found_messages_.find(random_id); @@ -8800,6 +8947,48 @@ void MessagesManager::on_get_recent_locations_failed(int64 random_id) { found_dialog_recent_location_messages_.erase(it); } +void MessagesManager::on_get_message_public_forwards_result(int64 random_id, int32 total_count, + vector> &&messages) { + LOG(INFO) << "Receive " << messages.size() << " forwarded messages"; + auto it = found_message_public_forwards_.find(random_id); + CHECK(it != found_message_public_forwards_.end()); + + auto &result = it->second.full_message_ids; + CHECK(result.empty()); + FullMessageId last_full_message_id; + for (auto &message : messages) { + auto dialog_id = get_message_dialog_id(message); + auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, + false, false, false, "get message public forwards"); + if (new_full_message_id != FullMessageId()) { + CHECK(dialog_id == new_full_message_id.get_dialog_id()); + result.push_back(new_full_message_id); + last_full_message_id = new_full_message_id; + } else { + total_count--; + } + } + if (total_count < static_cast(result.size())) { + LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size() + << " messages"; + total_count = static_cast(result.size()); + } + if (!result.empty()) { + auto m = get_message(last_full_message_id); + CHECK(m != nullptr); + it->second.next_offset = PSTRING() << m->date << "," << last_full_message_id.get_dialog_id().get() << "," + << m->message_id.get_server_message_id().get(); + } + + it->second.total_count = total_count; +} + +void MessagesManager::on_failed_get_message_public_forwards(int64 random_id) { + auto it = found_message_public_forwards_.find(random_id); + CHECK(it != found_message_public_forwards_.end()); + found_message_public_forwards_.erase(it); +} + void MessagesManager::delete_messages_from_updates(const vector &message_ids) { std::unordered_map, DialogIdHash> deleted_message_ids; std::unordered_map need_update_dialog_pos; @@ -8897,6 +9086,22 @@ bool MessagesManager::can_forward_message(DialogId from_dialog_id, const Message return can_forward_message_content(m->content.get()); } +bool MessagesManager::can_get_message_statistics(FullMessageId full_message_id) { + return can_get_message_statistics(full_message_id.get_dialog_id(), + get_message_force(full_message_id, "can_get_message_statistics")); +} + +bool MessagesManager::can_get_message_statistics(DialogId dialog_id, const Message *m) const { + if (td_->auth_manager_->is_bot()) { + return false; + } + if (m == nullptr || m->message_id.is_scheduled() || !m->message_id.is_server() || m->view_count == 0 || + m->had_forward_info || (m->forward_info != nullptr && m->forward_info->dialog_id.is_valid())) { + return false; + } + return td_->contacts_manager_->can_get_channel_message_statistics(dialog_id); +} + bool MessagesManager::can_delete_channel_message(DialogParticipantStatus status, const Message *m, bool is_bot) { if (m == nullptr) { return true; @@ -11824,8 +12029,12 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.via_bot_user_id = UserId(); } } - if (message->flags_ & MESSAGE_FLAG_HAS_VIEWS) { - message_info.views = message->views_; + if (message->flags_ & MESSAGE_FLAG_HAS_INTERACTION_INFO) { + message_info.view_count = message->views_; + message_info.forward_count = message->forwards_; + } + if (message->flags_ & MESSAGE_FLAG_HAS_REPLY_INFO) { + message_info.reply_info = std::move(message->replies_); } if (message->flags_ & MESSAGE_FLAG_HAS_EDIT_DATE) { message_info.edit_date = message->edit_date_; @@ -11993,11 +12202,17 @@ std::pair> MessagesManager::creat ttl = max(ttl, get_message_content_duration(message_info.content.get(), td_) + 1); } - int32 views = message_info.views; - if (views < 0) { - LOG(ERROR) << "Wrong views = " << views << " received in " << message_id << " in " << dialog_id; - views = 0; + int32 view_count = message_info.view_count; + if (view_count < 0) { + LOG(ERROR) << "Wrong view_count = " << view_count << " received in " << message_id << " in " << dialog_id; + view_count = 0; } + int32 forward_count = message_info.forward_count; + if (forward_count < 0) { + LOG(ERROR) << "Wrong forward_count = " << forward_count << " received in " << message_id << " in " << dialog_id; + forward_count = 0; + } + MessageReplyInfo reply_info(std::move(message_info.reply_info), td_->auth_manager_->is_bot()); bool has_forward_info = message_info.forward_header != nullptr; @@ -12028,7 +12243,9 @@ std::pair> MessagesManager::creat message->is_content_secret = is_content_secret; message->hide_edit_date = hide_edit_date; message->is_from_scheduled = is_from_scheduled; - message->views = views; + message->view_count = view_count; + message->forward_count = forward_count; + message->reply_info = std::move(reply_info); message->legacy_layer = (is_legacy ? MTPROTO_LAYER : 0); message->content = std::move(message_info.content); message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), td_->auth_manager_->is_bot(), false, @@ -12400,7 +12617,7 @@ void MessagesManager::set_dialog_unread_mention_count(Dialog *d, int32 unread_me CHECK(unread_mention_count >= 0); d->unread_mention_count = unread_mention_count; - d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] = unread_mention_count; + d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)] = unread_mention_count; } void MessagesManager::set_dialog_is_empty(Dialog *d, const char *source) { @@ -17131,7 +17348,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, const vectormessage_id.is_server() && m->views > 0) { + if (m->message_id.is_server() && m->view_count > 0) { d->pending_viewed_message_ids.insert(m->message_id); } @@ -18262,8 +18479,7 @@ void MessagesManager::on_read_history_finished(DialogId dialog_id, uint64 genera std::pair> MessagesManager::search_dialog_messages( DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, int32 offset, - int32 limit, const tl_object_ptr &filter, int64 &random_id, bool use_db, - Promise &&promise) { + int32 limit, MessageSearchFilter filter, int64 &random_id, bool use_db, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_dialog_messages_.find(random_id); @@ -18276,8 +18492,8 @@ std::pair> MessagesManager::search_dialog_messages( random_id = 0; } LOG(INFO) << "Search messages with query \"" << query << "\" in " << dialog_id << " sent by " << sender_user_id - << " filtered by " << to_string(filter) << " from " << from_message_id << " with offset " << offset - << " and limit " << limit; + << " filtered by " << filter << " from " << from_message_id << " with offset " << offset << " and limit " + << limit; std::pair> result; if (limit <= 0) { @@ -18312,8 +18528,7 @@ std::pair> MessagesManager::search_dialog_messages( return result; } - auto filter_type = get_search_messages_filter(filter); - if (filter_type == SearchMessagesFilter::FailedToSend && sender_user_id.is_valid()) { + if (filter == MessageSearchFilter::FailedToSend && sender_user_id.is_valid()) { if (sender_user_id != td_->contacts_manager_->get_my_id()) { promise.set_value(Unit()); return result; @@ -18332,7 +18547,7 @@ std::pair> MessagesManager::search_dialog_messages( } while (random_id == 0 || found_dialog_messages_.find(random_id) != found_dialog_messages_.end()); found_dialog_messages_[random_id]; // reserve place for result - if (filter_type == SearchMessagesFilter::UnreadMention) { + if (filter == MessageSearchFilter::UnreadMention) { if (!query.empty()) { promise.set_error(Status::Error(6, "Non-empty query is unsupported with the specified filter")); return result; @@ -18344,10 +18559,10 @@ std::pair> MessagesManager::search_dialog_messages( } // Trying to use database - if (use_db && query.empty() && G()->parameters().use_message_db && filter_type != SearchMessagesFilter::Empty && + if (use_db && query.empty() && G()->parameters().use_message_db && filter != MessageSearchFilter::Empty && input_user == nullptr) { // TODO support filter by users in the database - MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter_type); - int32 message_count = d->message_count_by_index[search_messages_filter_index(filter_type)]; + MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter); + int32 message_count = d->message_count_by_index[message_search_filter_index(filter)]; auto fixed_from_message_id = from_message_id; if (fixed_from_message_id == MessageId()) { fixed_from_message_id = MessageId::max(); @@ -18359,15 +18574,15 @@ std::pair> MessagesManager::search_dialog_messages( LOG(INFO) << "Search messages in database in " << dialog_id << " from " << fixed_from_message_id << " and with limit " << limit; auto new_promise = PromiseCreator::lambda( - [random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter_type, offset, limit, + [random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit, promise = std::move(promise)](Result> r_messages) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_search_dialog_messages_db_result, random_id, - dialog_id, fixed_from_message_id, first_db_message_id, filter_type, offset, limit, + dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit, std::move(r_messages), std::move(promise)); }); MessagesDbMessagesQuery db_query; db_query.dialog_id = dialog_id; - db_query.index_mask = search_messages_filter_index_mask(filter_type); + db_query.index_mask = message_search_filter_index_mask(filter); db_query.from_message_id = fixed_from_message_id; db_query.offset = offset; db_query.limit = limit; @@ -18375,7 +18590,7 @@ std::pair> MessagesManager::search_dialog_messages( return result; } } - if (filter_type == SearchMessagesFilter::FailedToSend) { + if (filter == MessageSearchFilter::FailedToSend) { promise.set_value(Unit()); return result; } @@ -18389,11 +18604,11 @@ std::pair> MessagesManager::search_dialog_messages( case DialogType::Chat: case DialogType::Channel: td_->create_handler(std::move(promise)) - ->send(dialog_id, query, sender_user_id, std::move(input_user), from_message_id, offset, limit, filter_type, + ->send(dialog_id, query, sender_user_id, std::move(input_user), from_message_id, offset, limit, filter, random_id); break; case DialogType::SecretChat: - if (filter_type == SearchMessagesFilter::UnreadMention) { + if (filter == MessageSearchFilter::UnreadMention) { promise.set_value(Unit()); } else { promise.set_error(Status::Error(500, "Search messages in secret chats is not supported")); @@ -18446,13 +18661,13 @@ std::pair> MessagesManager::search_call_messages(Me } while (random_id == 0 || found_call_messages_.find(random_id) != found_call_messages_.end()); found_call_messages_[random_id]; // reserve place for result - auto filter_type = only_missed ? SearchMessagesFilter::MissedCall : SearchMessagesFilter::Call; + auto filter = only_missed ? MessageSearchFilter::MissedCall : MessageSearchFilter::Call; if (use_db && G()->parameters().use_message_db) { // try to use database MessageId first_db_message_id = - calls_db_state_.first_calls_database_message_id_by_index[search_calls_filter_index(filter_type)]; - int32 message_count = calls_db_state_.message_count_by_index[search_calls_filter_index(filter_type)]; + calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)]; + int32 message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; auto fixed_from_message_id = from_message_id; if (fixed_from_message_id == MessageId()) { fixed_from_message_id = MessageId::max(); @@ -18464,14 +18679,14 @@ std::pair> MessagesManager::search_call_messages(Me LOG(INFO) << "Search messages in database from " << fixed_from_message_id << " and with limit " << limit; MessagesDbCallsQuery db_query; - db_query.index_mask = search_messages_filter_index_mask(filter_type); + db_query.index_mask = message_search_filter_index_mask(filter); db_query.from_unique_message_id = fixed_from_message_id.get_server_message_id().get(); db_query.limit = limit; G()->td_db()->get_messages_db_async()->get_calls( - db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter_type, promise = std::move(promise)]( + db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter, promise = std::move(promise)]( Result calls_result) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_messages_db_calls_result, - std::move(calls_result), random_id, first_db_message_id, filter_type, std::move(promise)); + std::move(calls_result), random_id, first_db_message_id, filter, std::move(promise)); })); return result; } @@ -18479,7 +18694,7 @@ std::pair> MessagesManager::search_call_messages(Me LOG(DEBUG) << "Search call messages on server from " << from_message_id << " and with limit " << limit; td_->create_handler(std::move(promise)) - ->send(DialogId(), "", UserId(), nullptr, from_message_id, 0, limit, filter_type, random_id); + ->send(DialogId(), "", UserId(), nullptr, from_message_id, 0, limit, filter, random_id); return result; } @@ -18850,11 +19065,11 @@ void MessagesManager::change_message_files(DialogId dialog_id, const Message *m, } } -MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *d, SearchMessagesFilter filter) { +MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter) { CHECK(d != nullptr); - auto message_id = filter == SearchMessagesFilter::Empty + auto message_id = filter == MessageSearchFilter::Empty ? d->first_database_message_id - : d->first_database_message_id_by_index[search_messages_filter_index(filter)]; + : d->first_database_message_id_by_index[message_search_filter_index(filter)]; CHECK(!message_id.is_scheduled()); if (!message_id.is_valid()) { if (d->dialog_id.get_type() == DialogType::SecretChat) { @@ -18868,7 +19083,7 @@ MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog * void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id, MessageId first_db_message_id, - SearchMessagesFilter filter_type, int32 offset, int32 limit, + MessageSearchFilter filter, int32 offset, int32 limit, Result> r_messages, Promise<> promise) { if (G()->close_flag()) { @@ -18877,7 +19092,7 @@ void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, Dialo if (r_messages.is_error()) { LOG(ERROR) << r_messages.error(); if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat && - filter_type != SearchMessagesFilter::FailedToSend) { + filter != MessageSearchFilter::FailedToSend) { found_dialog_messages_.erase(random_id); } return promise.set_value(Unit()); @@ -18898,7 +19113,7 @@ void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, Dialo for (auto &message : messages) { auto m = on_get_message_from_database(dialog_id, d, message, false, "on_search_dialog_messages_db_result"); if (m != nullptr && first_db_message_id <= m->message_id) { - if (filter_type == SearchMessagesFilter::UnreadMention && !m->contains_unread_mention) { + if (filter == MessageSearchFilter::UnreadMention && !m->contains_unread_mention) { // skip already read by d->last_read_all_mentions_message_id mentions } else { CHECK(!m->message_id.is_scheduled()); @@ -18907,7 +19122,7 @@ void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, Dialo } } - auto &message_count = d->message_count_by_index[search_messages_filter_index(filter_type)]; + auto &message_count = d->message_count_by_index[message_search_filter_index(filter)]; int32 result_size = narrow_cast(res.size()); bool from_the_end = from_message_id == MessageId::max() || (offset < 0 && (result_size == 0 || res[0] < from_message_id)); @@ -18916,7 +19131,7 @@ void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, Dialo result_size < limit + offset)) { LOG(INFO) << "Fix found message count in " << dialog_id << " from " << message_count << " to " << result_size; message_count = result_size; - if (filter_type == SearchMessagesFilter::UnreadMention) { + if (filter == MessageSearchFilter::UnreadMention) { d->unread_mention_count = message_count; update_dialog_mention_notification_count(d); send_update_chat_unread_mention_count(d); @@ -18933,9 +19148,25 @@ void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, Dialo promise.set_value(Unit()); } -std::pair> MessagesManager::offline_search_messages( - DialogId dialog_id, const string &query, int64 from_search_id, int32 limit, - const tl_object_ptr &filter, int64 &random_id, Promise<> &&promise) { +td_api::object_ptr MessagesManager::get_found_messages_object( + const FoundMessages &found_messages) { + vector> result; + result.reserve(found_messages.full_message_ids.size()); + for (auto full_message_id : found_messages.full_message_ids) { + auto message = get_message_object(full_message_id); + if (message != nullptr) { + result.push_back(std::move(message)); + } + } + + return td_api::make_object(found_messages.total_count, std::move(result), + found_messages.next_offset); +} + +MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId dialog_id, const string &query, + const string &offset, int32 limit, + MessageSearchFilter filter, int64 &random_id, + Promise<> &&promise) { if (!G()->parameters().use_message_db) { promise.set_error(Status::Error(400, "Message database is required to search messages in secret chats")); return {}; @@ -18970,8 +19201,15 @@ std::pair> MessagesManager::offline_search_messages MessagesDbFtsQuery fts_query; fts_query.query = query; fts_query.dialog_id = dialog_id; - fts_query.index_mask = search_messages_filter_index_mask(get_search_messages_filter(filter)); - fts_query.from_search_id = from_search_id; + fts_query.index_mask = message_search_filter_index_mask(filter); + if (!offset.empty()) { + auto r_from_search_id = to_integer_safe(offset); + if (r_from_search_id.is_error()) { + promise.set_error(Status::Error(400, "Wrong offset specified")); + return {}; + } + fts_query.from_search_id = r_from_search_id.ok(); + } fts_query.limit = limit; do { @@ -18981,16 +19219,17 @@ std::pair> MessagesManager::offline_search_messages G()->td_db()->get_messages_db_async()->get_messages_fts( std::move(fts_query), - PromiseCreator::lambda([random_id, promise = std::move(promise)](Result fts_result) mutable { + PromiseCreator::lambda([random_id, offset = std::move(offset), limit, + promise = std::move(promise)](Result fts_result) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_messages_db_fts_result, std::move(fts_result), - random_id, std::move(promise)); + std::move(offset), limit, random_id, std::move(promise)); })); return {}; } -void MessagesManager::on_messages_db_fts_result(Result result, int64 random_id, - Promise &&promise) { +void MessagesManager::on_messages_db_fts_result(Result result, string offset, int32 limit, + int64 random_id, Promise &&promise) { if (G()->close_flag()) { result = Status::Error(500, "Request aborted"); } @@ -19002,7 +19241,7 @@ void MessagesManager::on_messages_db_fts_result(Result resu auto it = found_fts_messages_.find(random_id); CHECK(it != found_fts_messages_.end()); - auto &res = it->second.second; + auto &res = it->second.full_message_ids; res.reserve(fts_result.messages.size()); for (auto &message : fts_result.messages) { @@ -19013,13 +19252,16 @@ void MessagesManager::on_messages_db_fts_result(Result resu } } - it->second.first = fts_result.next_search_id; + it->second.next_offset = fts_result.next_search_id <= 1 ? string() : to_string(fts_result.next_search_id); + it->second.total_count = offset.empty() && fts_result.messages.size() < static_cast(limit) + ? static_cast(fts_result.messages.size()) + : -1; promise.set_value(Unit()); } void MessagesManager::on_messages_db_calls_result(Result result, int64 random_id, - MessageId first_db_message_id, SearchMessagesFilter filter, + MessageId first_db_message_id, MessageSearchFilter filter, Promise<> &&promise) { CHECK(!first_db_message_id.is_scheduled()); if (G()->close_flag()) { @@ -19044,7 +19286,7 @@ void MessagesManager::on_messages_db_calls_result(Result res.push_back(FullMessageId(message.dialog_id, m->message_id)); } } - it->second.first = calls_db_state_.message_count_by_index[search_calls_filter_index(filter)]; + it->second.first = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)]; if (res.empty() && first_db_message_id != MessageId::min()) { LOG(INFO) << "No messages in database found"; @@ -19054,11 +19296,9 @@ void MessagesManager::on_messages_db_calls_result(Result promise.set_value(Unit()); } -std::pair> MessagesManager::search_messages(FolderId folder_id, bool ignore_folder_id, - const string &query, int32 offset_date, - DialogId offset_dialog_id, - MessageId offset_message_id, int32 limit, - int64 &random_id, Promise &&promise) { +std::pair> MessagesManager::search_messages( + FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date, DialogId offset_dialog_id, + MessageId offset_message_id, int32 limit, MessageSearchFilter filter, int64 &random_id, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_messages_.find(random_id); @@ -19069,10 +19309,9 @@ std::pair> MessagesManager::search_messages(FolderI return result; } - std::pair> result; if (limit <= 0) { promise.set_error(Status::Error(3, "Parameter limit must be positive")); - return result; + return {}; } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; @@ -19084,19 +19323,26 @@ std::pair> MessagesManager::search_messages(FolderI if (!offset_message_id.is_valid()) { if (offset_message_id.is_valid_scheduled()) { promise.set_error(Status::Error(3, "Parameter offset_message_id can't be a scheduled message identifier")); - return result; + return {}; } offset_message_id = MessageId(); } if (offset_message_id != MessageId() && !offset_message_id.is_server()) { promise.set_error( Status::Error(3, "Parameter offset_message_id must be identifier of the last found message or 0")); - return result; + return {}; } - if (query.empty()) { + if (filter == MessageSearchFilter::Call || filter == MessageSearchFilter::MissedCall || + filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention || + filter == MessageSearchFilter::FailedToSend) { + promise.set_error(Status::Error(400, "The filter is not supported")); + return {}; + } + + if (query.empty() && filter == MessageSearchFilter::Empty) { promise.set_value(Unit()); - return result; + return {}; } do { @@ -19104,12 +19350,13 @@ std::pair> MessagesManager::search_messages(FolderI } while (random_id == 0 || found_messages_.find(random_id) != found_messages_.end()); found_messages_[random_id]; // reserve place for result - LOG(DEBUG) << "Search messages globally with query = \"" << query << "\" from date " << offset_date << ", " - << offset_dialog_id << ", " << offset_message_id << " and with limit " << limit; + LOG(DEBUG) << "Search all messages filtered by " << filter << " with query = \"" << query << "\" from date " + << offset_date << ", " << offset_dialog_id << ", " << offset_message_id << " and limit " << limit; td_->create_handler(std::move(promise)) - ->send(folder_id, ignore_folder_id, query, offset_date, offset_dialog_id, offset_message_id, limit, random_id); - return result; + ->send(folder_id, ignore_folder_id, query, offset_date, offset_dialog_id, offset_message_id, limit, filter, + random_id); + return {}; } int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise &&promise) { @@ -19270,9 +19517,8 @@ tl_object_ptr MessagesManager::get_dialog_message_by_date_objec return get_message_object(full_message_id); } -int32 MessagesManager::get_dialog_message_count(DialogId dialog_id, - const tl_object_ptr &filter, - bool return_local, int64 &random_id, Promise &&promise) { +int32 MessagesManager::get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, bool return_local, + int64 &random_id, Promise &&promise) { if (random_id != 0) { // request has already been sent before auto it = found_dialog_messages_.find(random_id); @@ -19284,7 +19530,7 @@ int32 MessagesManager::get_dialog_message_count(DialogId dialog_id, } LOG(INFO) << "Get " << (return_local ? "local " : "") << "number of messages in " << dialog_id << " filtered by " - << to_string(filter); + << filter; const Dialog *d = get_dialog_force(dialog_id); if (d == nullptr) { @@ -19292,26 +19538,25 @@ int32 MessagesManager::get_dialog_message_count(DialogId dialog_id, return -1; } - auto filter_type = get_search_messages_filter(filter); - if (filter_type == SearchMessagesFilter::Empty) { + if (filter == MessageSearchFilter::Empty) { promise.set_error(Status::Error(6, "SearchMessagesFilterEmpty is not supported")); return -1; } auto dialog_type = dialog_id.get_type(); - int32 message_count = d->message_count_by_index[search_messages_filter_index(filter_type)]; + int32 message_count = d->message_count_by_index[message_search_filter_index(filter)]; if (message_count == -1) { - if (filter_type == SearchMessagesFilter::UnreadMention) { + if (filter == MessageSearchFilter::UnreadMention) { message_count = d->unread_mention_count; } } if (message_count != -1 || return_local || dialog_type == DialogType::SecretChat || - filter_type == SearchMessagesFilter::FailedToSend) { + filter == MessageSearchFilter::FailedToSend) { promise.set_value(Unit()); return message_count; } - LOG(INFO) << "Get number of messages in " << dialog_id << " filtered by " << to_string(filter) << " from the server"; + LOG(INFO) << "Get number of messages in " << dialog_id << " filtered by " << filter << " from the server"; do { random_id = Random::secure_int64(); @@ -19323,7 +19568,7 @@ int32 MessagesManager::get_dialog_message_count(DialogId dialog_id, case DialogType::Chat: case DialogType::Channel: td_->create_handler(std::move(promise)) - ->send(dialog_id, "", UserId(), nullptr, MessageId(), 0, 1, filter_type, random_id); + ->send(dialog_id, "", UserId(), nullptr, MessageId(), 0, 1, filter, random_id); break; case DialogType::None: case DialogType::SecretChat: @@ -19862,6 +20107,83 @@ void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id } } +MessagesManager::FoundMessages MessagesManager::get_message_public_forwards(FullMessageId full_message_id, + const string &offset, int32 limit, + int64 &random_id, Promise &&promise) { + if (random_id != 0) { + // request has already been sent before + auto it = found_message_public_forwards_.find(random_id); + CHECK(it != found_message_public_forwards_.end()); + auto result = std::move(it->second); + found_message_public_forwards_.erase(it); + promise.set_value(Unit()); + return result; + } + + auto dialog_id = full_message_id.get_dialog_id(); + Dialog *d = get_dialog_force(dialog_id); + if (d == nullptr) { + promise.set_error(Status::Error(5, "Chat not found")); + return {}; + } + + const Message *m = get_message_force(d, full_message_id.get_message_id(), "get_message_public_forwards"); + if (m == nullptr) { + promise.set_error(Status::Error(5, "Message not found")); + return {}; + } + + if (m->view_count == 0 || m->forward_info != nullptr || m->had_forward_info || m->message_id.is_scheduled() || + !m->message_id.is_server()) { + promise.set_error(Status::Error(5, "Message forwards are inaccessible")); + return {}; + } + + FoundMessages result; + if (limit <= 0) { + promise.set_error(Status::Error(3, "Parameter limit must be positive")); + return {}; + } + if (limit > MAX_SEARCH_MESSAGES) { + limit = MAX_SEARCH_MESSAGES; + } + + int32 offset_date = std::numeric_limits::max(); + DialogId offset_dialog_id; + ServerMessageId offset_message_id; + + if (!offset.empty()) { + auto parts = full_split(offset, ','); + if (parts.size() != 3) { + promise.set_error(Status::Error(3, "Wrong offset specified")); + return {}; + } + auto r_offset_date = to_integer_safe(parts[0]); + auto r_offset_dialog_id = to_integer_safe(parts[1]); + auto r_offset_message_id = to_integer_safe(parts[2]); + if (r_offset_date.is_error() || r_offset_dialog_id.is_error() || r_offset_message_id.is_error()) { + promise.set_error(Status::Error(3, "Wrong offset specified")); + return {}; + } + + offset_date = r_offset_date.ok(); + offset_dialog_id = DialogId(r_offset_dialog_id.ok()); + offset_message_id = ServerMessageId(r_offset_message_id.ok()); + } + + do { + random_id = Random::secure_int64(); + } while (random_id == 0 || found_message_public_forwards_.find(random_id) != found_message_public_forwards_.end()); + found_message_public_forwards_[random_id]; // reserve place for result + + LOG(DEBUG) << "Get public message forwards from date " << offset_date << ", " << offset_dialog_id + << ", server message " << offset_message_id.get() << " and with limit " << limit; + + td_->create_handler(std::move(promise)) + ->send(full_message_id, offset_date, offset_dialog_id, offset_message_id, limit, random_id); + return {}; +} + Result MessagesManager::get_message_schedule_date( td_api::object_ptr &&scheduling_state) { if (scheduling_state == nullptr) { @@ -19982,27 +20304,22 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto scheduling_state = is_scheduled ? get_message_scheduling_state_object(m->date) : nullptr; bool can_be_edited = for_event_log ? false : can_edit_message(dialog_id, m, false, td_->auth_manager_->is_bot()); bool can_be_forwarded = for_event_log ? false : can_forward_message(dialog_id, m); + bool can_get_statistics = for_event_log ? false : can_get_message_statistics(dialog_id, m); + bool can_get_replies = for_event_log || is_scheduled ? false : !m->reply_info.is_empty() && m->message_id.is_server(); + auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"); auto media_album_id = for_event_log ? static_cast(0) : m->media_album_id; auto reply_to_message_id = for_event_log ? static_cast(0) : m->reply_to_message_id.get(); bool contains_unread_mention = for_event_log ? false : m->contains_unread_mention; auto live_location_date = m->is_failed_to_send ? 0 : m->date; auto date = is_scheduled ? 0 : m->date; auto edit_date = m->hide_edit_date ? 0 : m->edit_date; - auto views = m->views; - if (m->message_id.is_scheduled()) { - if (m->forward_info == nullptr || is_broadcast_channel(dialog_id)) { - views = 0; - } - } else if (m->message_id.is_local() && m->forward_info == nullptr) { - views = 0; - } return make_tl_object( m->message_id.get(), td_->contacts_manager_->get_user_id_object(m->sender_user_id, "sender_user_id"), dialog_id.get(), std::move(sending_state), std::move(scheduling_state), is_outgoing, can_be_edited, - can_be_forwarded, can_delete_for_self, can_delete_for_all_users, m->is_channel_post, contains_unread_mention, - date, edit_date, get_message_forward_info_object(m->forward_info), reply_to_message_id, ttl, ttl_expires_in, - td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"), m->author_signature, views, - media_album_id, get_restriction_reason_description(m->restriction_reasons), + can_be_forwarded, can_delete_for_self, can_delete_for_all_users, can_get_statistics, can_get_replies, + m->is_channel_post, contains_unread_mention, date, edit_date, get_message_forward_info_object(m->forward_info), + get_message_interaction_info_object(dialog_id, m), reply_to_message_id, ttl, ttl_expires_in, via_bot_user_id, + m->author_signature, media_album_id, get_restriction_reason_description(m->restriction_reasons), get_message_content_object(m->content.get(), td_, live_location_date, m->is_content_secret), get_reply_markup_object(m->reply_markup)); } @@ -20065,7 +20382,25 @@ MessagesManager::Message *MessagesManager::get_message_to_send( m->is_channel_post = is_channel_post; m->is_outgoing = is_scheduled || dialog_id != DialogId(my_id); m->from_background = options.from_background; - m->views = is_channel_post && !is_scheduled ? 1 : 0; + m->view_count = is_channel_post && !is_scheduled ? 1 : 0; + m->forward_count = 0; + if ([&] { + if (is_scheduled) { + return false; + } + if (dialog_type != DialogType::Channel) { + return false; + } + if (td_->auth_manager_->is_bot()) { + return false; + } + if (is_channel_post) { + return td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id()); + } + return !m->reply_to_message_id.is_valid(); + }()) { + m->reply_info.reply_count = 0; + } m->content = std::move(content); m->forward_info = std::move(forward_info); m->is_copy = is_copy || forward_info != nullptr; @@ -20512,6 +20847,9 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, Dialo add_dialog_dependencies(dependencies, m->forward_info->from_dialog_id); } } + for (auto recent_replier_user_id : m->reply_info.recent_replier_user_ids) { + dependencies.user_ids.insert(recent_replier_user_id); + } add_message_content_dependencies(dependencies, m->content.get()); } @@ -20706,12 +21044,8 @@ Result MessagesManager::process_message_sen } auto dialog_type = dialog_id.get_type(); - bool is_secret = dialog_type == DialogType::SecretChat; - if (result.disable_notification && is_secret) { - return Status::Error(400, "Can't send messages with silent notifications to secret chats"); - } if (result.schedule_date != 0) { - if (is_secret) { + if (dialog_type == DialogType::SecretChat) { return Status::Error(400, "Can't schedule messages in secret chats"); } if (td_->auth_manager_->is_bot()) { @@ -23041,7 +23375,7 @@ Result> MessagesManager::forward_messages(DialogId to_dialog_i for (size_t i = 0; i < message_ids.size(); i++) { MessageId message_id = get_persistent_message_id(from_dialog, message_ids[i]); - const Message *forwarded_message = get_message_force(from_dialog, message_id, "forward_messages"); + Message *forwarded_message = get_message_force(from_dialog, message_id, "forward_messages"); if (forwarded_message == nullptr) { LOG(INFO) << "Can't find " << message_id << " to forward"; continue; @@ -23127,9 +23461,16 @@ Result> MessagesManager::forward_messages(DialogId to_dialog_i m->real_forward_from_message_id = message_id; m->via_bot_user_id = forwarded_message->via_bot_user_id; m->in_game_share = in_game_share; - if (forwarded_message->views > 0 && m->forward_info != nullptr && m->views == 0 && + if (forwarded_message->view_count > 0 && is_broadcast_channel(from_dialog_id)) { + if (update_message_interaction_info(from_dialog_id, forwarded_message, forwarded_message->view_count, + forwarded_message->forward_count + 1, false, {})) { + on_message_changed(from_dialog, forwarded_message, true, "forward_messages"); + } + } + if (forwarded_message->view_count > 0 && m->forward_info != nullptr && m->view_count == 0 && !(m->message_id.is_scheduled() && is_broadcast_channel(to_dialog_id))) { - m->views = forwarded_message->views; + m->view_count = forwarded_message->view_count; + m->forward_count = forwarded_message->forward_count; } if (is_game) { @@ -23536,7 +23877,8 @@ Result MessagesManager::add_local_message( m->is_outgoing = dialog_id != DialogId(my_id) && sender_user_id == my_id; m->disable_notification = disable_notification; m->from_background = false; - m->views = 0; + m->view_count = 0; + m->forward_count = 0; m->content = std::move(message_content.content); m->disable_web_page_preview = message_content.disable_web_page_preview; m->clear_draft = message_content.clear_draft; @@ -24124,7 +24466,7 @@ Result> MessagesManager::do_get_message_notifications_from_d // ignore first_db_message_id, notifications can be nonconsecutive MessagesDbMessagesQuery db_query; db_query.dialog_id = d->dialog_id; - db_query.index_mask = search_messages_filter_index_mask(SearchMessagesFilter::UnreadMention); + db_query.index_mask = message_search_filter_index_mask(MessageSearchFilter::UnreadMention); db_query.from_message_id = from_message_id; db_query.offset = 0; db_query.limit = limit; @@ -24243,7 +24585,7 @@ void MessagesManager::do_get_message_notifications_from_database(Dialog *d, bool // ignore first_db_message_id, notifications can be nonconsecutive MessagesDbMessagesQuery db_query; db_query.dialog_id = dialog_id; - db_query.index_mask = search_messages_filter_index_mask(SearchMessagesFilter::UnreadMention); + db_query.index_mask = message_search_filter_index_mask(MessageSearchFilter::UnreadMention); db_query.from_message_id = from_message_id; db_query.offset = 0; db_query.limit = limit; @@ -25824,8 +26166,8 @@ void MessagesManager::fail_send_message(FullMessageId full_message_id, int error } else { CHECK(message->message_id.is_valid()); } - if (message->forward_info == nullptr && message->views == 1) { - message->views = 0; + if (message->forward_info == nullptr && message->view_count == 1) { + message->view_count = 0; } message->is_failed_to_send = true; message->send_error_code = error_code; @@ -27081,93 +27423,6 @@ void MessagesManager::clear_active_dialog_actions(DialogId dialog_id) { } } -tl_object_ptr MessagesManager::get_input_messages_filter(SearchMessagesFilter filter) { - switch (filter) { - case SearchMessagesFilter::Empty: - return make_tl_object(); - case SearchMessagesFilter::Animation: - return make_tl_object(); - case SearchMessagesFilter::Audio: - return make_tl_object(); - case SearchMessagesFilter::Document: - return make_tl_object(); - case SearchMessagesFilter::Photo: - return make_tl_object(); - case SearchMessagesFilter::Video: - return make_tl_object(); - case SearchMessagesFilter::VoiceNote: - return make_tl_object(); - case SearchMessagesFilter::PhotoAndVideo: - return make_tl_object(); - case SearchMessagesFilter::Url: - return make_tl_object(); - case SearchMessagesFilter::ChatPhoto: - return make_tl_object(); - case SearchMessagesFilter::Call: - return make_tl_object(0, false /*ignored*/); - case SearchMessagesFilter::MissedCall: - return make_tl_object( - telegram_api::inputMessagesFilterPhoneCalls::MISSED_MASK, false /*ignored*/); - case SearchMessagesFilter::VideoNote: - return make_tl_object(); - case SearchMessagesFilter::VoiceAndVideoNote: - return make_tl_object(); - case SearchMessagesFilter::Mention: - return make_tl_object(); - case SearchMessagesFilter::UnreadMention: - case SearchMessagesFilter::FailedToSend: - default: - UNREACHABLE(); - return nullptr; - } -} - -SearchMessagesFilter MessagesManager::get_search_messages_filter( - const tl_object_ptr &filter) { - if (filter == nullptr) { - return SearchMessagesFilter::Empty; - } - switch (filter->get_id()) { - case td_api::searchMessagesFilterEmpty::ID: - return SearchMessagesFilter::Empty; - case td_api::searchMessagesFilterAnimation::ID: - return SearchMessagesFilter::Animation; - case td_api::searchMessagesFilterAudio::ID: - return SearchMessagesFilter::Audio; - case td_api::searchMessagesFilterDocument::ID: - return SearchMessagesFilter::Document; - case td_api::searchMessagesFilterPhoto::ID: - return SearchMessagesFilter::Photo; - case td_api::searchMessagesFilterVideo::ID: - return SearchMessagesFilter::Video; - case td_api::searchMessagesFilterVoiceNote::ID: - return SearchMessagesFilter::VoiceNote; - case td_api::searchMessagesFilterPhotoAndVideo::ID: - return SearchMessagesFilter::PhotoAndVideo; - case td_api::searchMessagesFilterUrl::ID: - return SearchMessagesFilter::Url; - case td_api::searchMessagesFilterChatPhoto::ID: - return SearchMessagesFilter::ChatPhoto; - case td_api::searchMessagesFilterCall::ID: - return SearchMessagesFilter::Call; - case td_api::searchMessagesFilterMissedCall::ID: - return SearchMessagesFilter::MissedCall; - case td_api::searchMessagesFilterVideoNote::ID: - return SearchMessagesFilter::VideoNote; - case td_api::searchMessagesFilterVoiceAndVideoNote::ID: - return SearchMessagesFilter::VoiceAndVideoNote; - case td_api::searchMessagesFilterMention::ID: - return SearchMessagesFilter::Mention; - case td_api::searchMessagesFilterUnreadMention::ID: - return SearchMessagesFilter::UnreadMention; - case td_api::searchMessagesFilterFailedToSend::ID: - return SearchMessagesFilter::FailedToSend; - default: - UNREACHABLE(); - return SearchMessagesFilter::Empty; - } -} - vector MessagesManager::get_dialog_lists_to_add_dialog(DialogId dialog_id) { vector result; const Dialog *d = get_dialog_force(dialog_id); @@ -28737,7 +28992,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq message->have_next = false; } if (!message->from_database) { - const int32 INDEX_MASK_MASK = ~search_messages_filter_index_mask(SearchMessagesFilter::UnreadMention); + const int32 INDEX_MASK_MASK = ~message_search_filter_index_mask(MessageSearchFilter::UnreadMention); auto old_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK; bool was_deleted = delete_active_live_location(dialog_id, m); auto old_file_ids = get_message_content_file_ids(m->content.get(), td_); @@ -29215,6 +29470,10 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } } + if (!td_->auth_manager_->is_bot() && from_update && m->forward_info != nullptr && + m->forward_info->dialog_id.is_valid() && m->forward_info->message_id.is_valid()) { + update_forward_count(m->forward_info->dialog_id, m->forward_info->message_id); + } return result_message; } @@ -29855,7 +30114,8 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr update_message_contains_unread_mention(d, old_message, new_message->contains_unread_mention, "update_message")) { need_send_update = true; } - if (update_message_views(dialog_id, old_message, new_message->views)) { + if (update_message_interaction_info(dialog_id, old_message, new_message->view_count, new_message->forward_count, true, + std::move(new_message->reply_info))) { need_send_update = true; } if (old_message->restriction_reasons != new_message->restriction_reasons) { @@ -32377,6 +32637,21 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) { } } +void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message_id) { + Dialog *d = get_dialog(dialog_id); + CHECK(d != nullptr); + Message *m = get_message_force(d, message_id, "update_forward_count"); + if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0) { + if (m->forward_count == 0 && update_message_interaction_info(dialog_id, m, m->view_count, 1, false, {})) { + on_message_changed(d, m, true, "update_forward_count"); + } + + if (d->pending_viewed_message_ids.insert(m->message_id).second) { + pending_message_views_timeout_.add_timeout_in(dialog_id.get(), 0.0); + } + } +} + void MessagesManager::try_hide_distance(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (!m->is_outgoing && dialog_id != get_my_dialog_id()) { diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index c267253cb..d2ef8a319 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -6,10 +6,6 @@ // #pragma once -#include "td/telegram/secret_api.h" -#include "td/telegram/td_api.h" -#include "td/telegram/telegram_api.h" - #include "td/telegram/AccessRights.h" #include "td/telegram/ChannelId.h" #include "td/telegram/Dependencies.h" @@ -31,7 +27,9 @@ #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageReplyInfo.h" #include "td/telegram/MessagesDb.h" +#include "td/telegram/MessageSearchFilter.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/Notification.h" #include "td/telegram/NotificationGroupId.h" @@ -47,6 +45,10 @@ #include "td/telegram/ServerMessageId.h" #include "td/telegram/UserId.h" +#include "td/telegram/secret_api.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + #include "td/actor/actor.h" #include "td/actor/MultiPromise.h" #include "td/actor/PromiseFuture.h" @@ -151,7 +153,7 @@ class MessagesManager : public Actor { static constexpr int32 MESSAGE_FLAG_HAS_ENTITIES = 1 << 7; static constexpr int32 MESSAGE_FLAG_HAS_FROM_ID = 1 << 8; static constexpr int32 MESSAGE_FLAG_HAS_MEDIA = 1 << 9; - static constexpr int32 MESSAGE_FLAG_HAS_VIEWS = 1 << 10; + static constexpr int32 MESSAGE_FLAG_HAS_INTERACTION_INFO = 1 << 10; static constexpr int32 MESSAGE_FLAG_IS_SENT_VIA_BOT = 1 << 11; static constexpr int32 MESSAGE_FLAG_IS_SILENT = 1 << 13; static constexpr int32 MESSAGE_FLAG_IS_POST = 1 << 14; @@ -162,6 +164,8 @@ class MessagesManager : public Actor { static constexpr int32 MESSAGE_FLAG_IS_LEGACY = 1 << 19; static constexpr int32 MESSAGE_FLAG_HIDE_EDIT_DATE = 1 << 21; static constexpr int32 MESSAGE_FLAG_IS_RESTRICTED = 1 << 22; + static constexpr int32 MESSAGE_FLAG_HAS_REPLY_INFO = 1 << 23; + static constexpr int32 MESSAGE_FLAG_HAS_RECENT_REPLIERS = 1 << 24; static constexpr int32 SEND_MESSAGE_FLAG_IS_REPLY = 1 << 0; static constexpr int32 SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW = 1 << 1; @@ -230,12 +234,13 @@ class MessagesManager : public Actor { void on_get_dialog_messages_search_result(DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, int32 offset, int32 limit, - SearchMessagesFilter filter, int64 random_id, int32 total_count, + MessageSearchFilter filter, int64 random_id, int32 total_count, vector> &&messages); void on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id); void on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id, - MessageId offset_message_id, int32 limit, int64 random_id, int32 total_count, + MessageId offset_message_id, int32 limit, MessageSearchFilter filter, + int64 random_id, int32 total_count, vector> &&messages); void on_failed_messages_search(int64 random_id); @@ -246,6 +251,10 @@ class MessagesManager : public Actor { vector> &&messages); void on_get_recent_locations_failed(int64 random_id); + void on_get_message_public_forwards_result(int64 random_id, int32 total_count, + vector> &&messages); + void on_failed_get_message_public_forwards(int64 random_id); + // if message is from_update, flags have_previous and have_next are ignored and should be both true FullMessageId on_get_message(tl_object_ptr message_ptr, bool from_update, bool is_channel_message, bool is_scheduled, bool have_previous, bool have_next, @@ -323,7 +332,13 @@ class MessagesManager : public Actor { void on_update_channel_too_long(tl_object_ptr &&update, bool force_apply); - void on_update_message_views(FullMessageId full_message_id, int32 views); + void on_update_message_view_count(FullMessageId full_message_id, int32 view_count); + + void on_update_message_forward_count(FullMessageId full_message_id, int32 forward_count); + + void on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count, + bool has_reply_info, + tl_object_ptr &&reply_info); void on_update_live_location_viewed(FullMessageId full_message_id); @@ -528,6 +543,8 @@ class MessagesManager : public Actor { vector get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit, bool force, Promise &&promise); + bool can_get_message_statistics(FullMessageId full_message_id); + bool have_message_force(FullMessageId full_message_id, const char *source); void get_message(FullMessageId full_message_id, Promise &&promise); @@ -643,18 +660,25 @@ class MessagesManager : public Actor { std::pair> search_dialog_messages(DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, - int32 offset, int32 limit, - const tl_object_ptr &filter, + int32 offset, int32 limit, MessageSearchFilter filter, int64 &random_id, bool use_db, Promise &&promise); - std::pair> offline_search_messages( - DialogId dialog_id, const string &query, int64 from_search_id, int32 limit, - const tl_object_ptr &filter, int64 &random_id, Promise<> &&promise); + struct FoundMessages { + vector full_message_ids; + string next_offset; + int32 total_count = 0; + }; + + td_api::object_ptr get_found_messages_object(const FoundMessages &found_messages); + + FoundMessages offline_search_messages(DialogId dialog_id, const string &query, const string &offset, int32 limit, + MessageSearchFilter filter, int64 &random_id, Promise<> &&promise); std::pair> search_messages(FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, - int32 limit, int64 &random_id, Promise &&promise); + int32 limit, MessageSearchFilter filter, int64 &random_id, + Promise &&promise); std::pair> search_call_messages(MessageId from_message_id, int32 limit, bool only_missed, int64 &random_id, bool use_db, Promise &&promise); @@ -671,12 +695,15 @@ class MessagesManager : public Actor { void on_get_dialog_message_by_date_fail(int64 random_id); - int32 get_dialog_message_count(DialogId dialog_id, const tl_object_ptr &filter, - bool return_local, int64 &random_id, Promise &&promise); + int32 get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, bool return_local, int64 &random_id, + Promise &&promise); vector get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result, Promise &&promise); + FoundMessages get_message_public_forwards(FullMessageId full_message_id, const string &offset, int32 limit, + int64 &random_id, Promise &&promise); + tl_object_ptr get_dialog_message_by_date_object(int64 random_id); tl_object_ptr get_message_object(FullMessageId full_message_id); @@ -710,10 +737,6 @@ class MessagesManager : public Actor { void on_resolved_username(const string &username, DialogId dialog_id); void drop_username(const string &username); - static tl_object_ptr get_input_messages_filter(SearchMessagesFilter filter); - - static SearchMessagesFilter get_search_messages_filter(const tl_object_ptr &filter); - tl_object_ptr get_input_notify_peer(DialogId dialogId) const; void on_update_dialog_notify_settings(DialogId dialog_id, @@ -887,7 +910,9 @@ class MessagesManager : public Actor { tl_object_ptr forward_header; MessageId reply_to_message_id; UserId via_bot_user_id; - int32 views = 0; + int32 view_count = 0; + int32 forward_count = 0; + tl_object_ptr reply_info; int32 flags = 0; int32 edit_date = 0; vector restriction_reasons; @@ -999,7 +1024,10 @@ class MessagesManager : public Actor { NotificationId notification_id; NotificationId removed_notification_id; - int32 views = 0; + int32 view_count = 0; + int32 forward_count = 0; + MessageReplyInfo reply_info; + int32 legacy_layer = 0; int32 send_error_code = 0; @@ -1065,9 +1093,9 @@ class MessagesManager : public Actor { // is known and last_message_id is known, then last_database_message_id <= // last_message_id - std::array first_database_message_id_by_index; + std::array first_database_message_id_by_index; // use struct Count? - std::array message_count_by_index{{0}}; + std::array message_count_by_index{{0}}; int32 server_unread_count = 0; int32 local_unread_count = 0; @@ -1759,6 +1787,8 @@ class MessagesManager : public Actor { static bool can_forward_message(DialogId from_dialog_id, const Message *m); + bool can_get_message_statistics(DialogId dialog_id, const Message *m) const; + static bool can_delete_channel_message(DialogParticipantStatus status, const Message *m, bool is_bot); bool can_delete_message(DialogId dialog_id, const Message *m) const; @@ -1820,7 +1850,14 @@ class MessagesManager : public Actor { void on_pending_message_views_timeout(DialogId dialog_id); - bool update_message_views(DialogId dialog_id, Message *m, int32 views); + void update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count, + bool has_reply_info, tl_object_ptr &&reply_info); + + td_api::object_ptr get_message_interaction_info_object(DialogId dialog_id, + const Message *m) const; + + bool update_message_interaction_info(DialogId dialog_id, Message *m, int32 view_count, int32 forward_count, + bool has_reply_info, MessageReplyInfo &&reply_info); bool update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, const char *source); @@ -2459,17 +2496,17 @@ class MessagesManager : public Actor { void on_get_message_link_dialog(MessageLinkInfo &&info, Promise &&promise); - static MessageId get_first_database_message_id_by_index(const Dialog *d, SearchMessagesFilter filter); + static MessageId get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter); void on_search_dialog_messages_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id, - MessageId first_db_message_id, SearchMessagesFilter filter_type, - int32 offset, int32 limit, Result> r_messages, - Promise<> promise); + MessageId first_db_message_id, MessageSearchFilter filter, int32 offset, + int32 limit, Result> r_messages, Promise<> promise); - void on_messages_db_fts_result(Result result, int64 random_id, Promise<> &&promise); + void on_messages_db_fts_result(Result result, string offset, int32 limit, int64 random_id, + Promise<> &&promise); void on_messages_db_calls_result(Result result, int64 random_id, MessageId first_db_message_id, - SearchMessagesFilter filter, Promise &&promise); + MessageSearchFilter filter, Promise &&promise); void on_load_active_live_location_full_message_ids_from_database(string value); @@ -2690,6 +2727,8 @@ class MessagesManager : public Actor { void update_top_dialogs(DialogId dialog_id, const Message *m); + void update_forward_count(DialogId dialog_id, MessageId message_id); + void try_hide_distance(DialogId dialog_id, const Message *m); string get_message_search_text(const Message *m) const; @@ -2914,8 +2953,8 @@ class MessagesManager : public Actor { std::unordered_map>> found_dialog_recent_location_messages_; // random_id -> [total_count, [message_id]...] - std::unordered_map>> - found_fts_messages_; // random_id -> [from_search_id, [full_message_id]...] + std::unordered_map found_fts_messages_; // random_id -> FoundMessages + std::unordered_map found_message_public_forwards_; // random_id -> FoundMessages std::unordered_map, FullMessageIdHash> public_message_links_[2]; diff --git a/td/telegram/Photo.cpp b/td/telegram/Photo.cpp index 52612abfb..98a7dd30f 100644 --- a/td/telegram/Photo.cpp +++ b/td/telegram/Photo.cpp @@ -395,6 +395,24 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo auto size = move_tl_object_as(size_ptr); return size->bytes_.as_slice().str(); } + case telegram_api::photoSizeProgressive::ID: { + auto size = move_tl_object_as(size_ptr); + + if (size->sizes_.empty()) { + LOG(ERROR) << "Receive " << to_string(size); + return std::move(res); + } + std::sort(size->sizes_.begin(), size->sizes_.end()); + + type = std::move(size->type_); + location = std::move(size->location_); + res.dimensions = get_dimensions(size->w_, size->h_); + res.size = size->sizes_.back(); + size->sizes_.pop_back(); + res.progressive_sizes = std::move(size->sizes_); + + break; + } default: UNREACHABLE(); break; @@ -554,7 +572,8 @@ static tl_object_ptr get_photo_size_object(FileManager *file_ return td_api::make_object( photo_size->type ? std::string(1, static_cast(photo_size->type)) : std::string(), // TODO replace string type with integer type - file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height); + file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height, + vector(photo_size->progressive_sizes)); } static vector> get_photo_sizes_object(FileManager *file_manager, @@ -573,7 +592,8 @@ static vector> get_photo_sizes_object(File } bool operator==(const PhotoSize &lhs, const PhotoSize &rhs) { - return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size && lhs.file_id == rhs.file_id; + return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size && + lhs.file_id == rhs.file_id && lhs.progressive_sizes == rhs.progressive_sizes; } bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs) { @@ -602,7 +622,8 @@ bool operator<(const PhotoSize &lhs, const PhotoSize &rhs) { StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size) { return string_builder << "{type = " << photo_size.type << ", dimensions = " << photo_size.dimensions - << ", size = " << photo_size.size << ", file_id = " << photo_size.file_id << "}"; + << ", size = " << photo_size.size << ", file_id = " << photo_size.file_id + << ", progressive_sizes = " << photo_size.progressive_sizes << "}"; } static tl_object_ptr get_animated_chat_photo_object(FileManager *file_manager, @@ -936,6 +957,15 @@ tl_object_ptr convert_photo_to_profile_photo( } case telegram_api::photoStrippedSize::ID: break; + case telegram_api::photoSizeProgressive::ID: { + auto size = static_cast(size_ptr.get()); + if (size->type_ == "a") { + photo_small = copy_location(size->location_); + } else if (size->type_ == "c") { + photo_big = copy_location(size->location_); + } + break; + } default: UNREACHABLE(); break; diff --git a/td/telegram/Photo.h b/td/telegram/Photo.h index 46a18ce4b..43db9372a 100644 --- a/td/telegram/Photo.h +++ b/td/telegram/Photo.h @@ -48,6 +48,7 @@ struct PhotoSize { Dimensions dimensions; int32 size = 0; FileId file_id; + vector progressive_sizes; }; struct AnimationSize : public PhotoSize { diff --git a/td/telegram/Photo.hpp b/td/telegram/Photo.hpp index 0ffd0a086..911316040 100644 --- a/td/telegram/Photo.hpp +++ b/td/telegram/Photo.hpp @@ -75,6 +75,7 @@ void store(const PhotoSize &photo_size, StorerT &storer) { store(photo_size.dimensions, storer); store(photo_size.size, storer); store(photo_size.file_id, storer); + store(photo_size.progressive_sizes, storer); } template @@ -83,6 +84,11 @@ void parse(PhotoSize &photo_size, ParserT &parser) { parse(photo_size.dimensions, parser); parse(photo_size.size, parser); parse(photo_size.file_id, parser); + if (parser.version() >= static_cast(Version::AddPhotoProgressiveSizes)) { + parse(photo_size.progressive_sizes, parser); + } else { + photo_size.progressive_sizes.clear(); + } LOG(DEBUG) << "Parsed photo size " << photo_size; } diff --git a/td/telegram/ReplyMarkup.cpp b/td/telegram/ReplyMarkup.cpp index 65deae1a0..42ad336ca 100644 --- a/td/telegram/ReplyMarkup.cpp +++ b/td/telegram/ReplyMarkup.cpp @@ -84,6 +84,9 @@ static StringBuilder &operator<<(StringBuilder &string_builder, const InlineKeyb case InlineKeyboardButton::Type::UrlAuth: string_builder << "UrlAuth, id = " << keyboard_button.id; break; + case InlineKeyboardButton::Type::CallbackWithPassword: + string_builder << "CallbackWithPassword"; + break; default: UNREACHABLE(); } @@ -219,7 +222,8 @@ static InlineKeyboardButton get_inline_keyboard_button( } case telegram_api::keyboardButtonCallback::ID: { auto keyboard_button = move_tl_object_as(keyboard_button_ptr); - button.type = InlineKeyboardButton::Type::Callback; + button.type = keyboard_button->requires_password_ ? InlineKeyboardButton::Type::CallbackWithPassword + : InlineKeyboardButton::Type::Callback; button.text = std::move(keyboard_button->text_); button.data = keyboard_button->data_.as_slice().str(); break; @@ -435,6 +439,8 @@ static Result get_inline_keyboard_button(tl_object_ptr(button->type_); if (!switch_inline_buttons_allowed) { @@ -618,7 +624,7 @@ static tl_object_ptr get_inline_keyboard_button( case InlineKeyboardButton::Type::Url: return make_tl_object(keyboard_button.text, keyboard_button.data); case InlineKeyboardButton::Type::Callback: - return make_tl_object(keyboard_button.text, + return make_tl_object(0, false /*ignored*/, keyboard_button.text, BufferSlice(keyboard_button.data)); case InlineKeyboardButton::Type::CallbackGame: return make_tl_object(keyboard_button.text); @@ -653,6 +659,9 @@ static tl_object_ptr get_inline_keyboard_button( keyboard_button.forward_text, keyboard_button.data, std::move(input_user)); } + case InlineKeyboardButton::Type::CallbackWithPassword: + UNREACHABLE(); + break; default: UNREACHABLE(); return nullptr; @@ -762,6 +771,9 @@ static tl_object_ptr get_inline_keyboard_button_ob type = make_tl_object(keyboard_button.data, keyboard_button.id, keyboard_button.forward_text); break; + case InlineKeyboardButton::Type::CallbackWithPassword: + type = make_tl_object(keyboard_button.data); + break; default: UNREACHABLE(); return nullptr; diff --git a/td/telegram/ReplyMarkup.h b/td/telegram/ReplyMarkup.h index 79e15f574..9c2729ddb 100644 --- a/td/telegram/ReplyMarkup.h +++ b/td/telegram/ReplyMarkup.h @@ -31,7 +31,16 @@ struct KeyboardButton { struct InlineKeyboardButton { // append only - enum class Type : int32 { Url, Callback, CallbackGame, SwitchInline, SwitchInlineCurrentDialog, Buy, UrlAuth }; + enum class Type : int32 { + Url, + Callback, + CallbackGame, + SwitchInline, + SwitchInlineCurrentDialog, + Buy, + UrlAuth, + CallbackWithPassword + }; Type type; int32 id = 0; // UrlAuth only, button_id or (2 * request_write_access - 1) * bot_user_id string text; diff --git a/td/telegram/SecretChatActor.cpp b/td/telegram/SecretChatActor.cpp index 06f0a71fb..4310a2b2f 100644 --- a/td/telegram/SecretChatActor.cpp +++ b/td/telegram/SecretChatActor.cpp @@ -324,7 +324,7 @@ void SecretChatActor::send_message_impl(tl_object_ptrneed_notify_user = (flags & SendFlag::Push) == 0; binlog_event->is_external = (flags & SendFlag::External) != 0; + binlog_event->is_silent = (message->get_id() == secret_api::decryptedMessage::ID && + (static_cast(message.get())->flags_ & + secret_api::decryptedMessage::SILENT_MASK) != 0); if (message->get_id() == secret_api::decryptedMessageService::ID) { binlog_event->is_rewritable = false; auto service_message = move_tl_object_as(message); @@ -1467,14 +1470,22 @@ NetQueryPtr SecretChatActor::create_net_query(const logevent::OutboundSecretMess telegram_api::messages_sendEncryptedService(get_input_chat(), message.random_id, message.encrypted_message.clone())); } else if (message.file.empty()) { + int32 flags = 0; + if (message.is_silent) { + flags |= telegram_api::messages_sendEncrypted::SILENT_MASK; + } query = create_net_query( - QueryType::Message, - telegram_api::messages_sendEncrypted(get_input_chat(), message.random_id, message.encrypted_message.clone())); + QueryType::Message, telegram_api::messages_sendEncrypted(flags, false /*ignored*/, get_input_chat(), + message.random_id, message.encrypted_message.clone())); } else { - query = create_net_query( - QueryType::Message, - telegram_api::messages_sendEncryptedFile(get_input_chat(), message.random_id, message.encrypted_message.clone(), - message.file.as_input_encrypted_file())); + int32 flags = 0; + if (message.is_silent) { + flags |= telegram_api::messages_sendEncryptedFile::SILENT_MASK; + } + query = create_net_query(QueryType::Message, + telegram_api::messages_sendEncryptedFile( + flags, false /*ignored*/, get_input_chat(), message.random_id, + message.encrypted_message.clone(), message.file.as_input_encrypted_file())); } if (!message.is_rewritable) { query->total_timeout_limit_ = 1000000000; // inf. We will re-sent it immediately anyway @@ -1562,6 +1573,7 @@ Status SecretChatActor::outbound_rewrite_with_empty(uint64 state_id) { state->message->is_rewritable = false; state->message->is_external = false; state->message->need_notify_user = false; + state->message->is_silent = true; state->message->file = logevent::EncryptedInputFile::from_input_encrypted_file(nullptr); binlog_rewrite(context_->binlog(), state->message->logevent_id(), LogEvent::HandlerType::SecretChats, create_storer(*state->message)); diff --git a/td/telegram/SecureManager.cpp b/td/telegram/SecureManager.cpp index 32dfd4d51..accd573a0 100644 --- a/td/telegram/SecureManager.cpp +++ b/td/telegram/SecureManager.cpp @@ -1280,7 +1280,8 @@ void SecureManager::send_passport_authorization_form(int32 authorization_form_id send_with_promise(std::move(query), std::move(new_promise)); } -void SecureManager::get_preferred_country_code(string country_code, Promise> promise) { +void SecureManager::get_preferred_country_language(string country_code, + Promise> promise) { refcnt_++; for (auto &c : country_code) { c = to_upper(c); diff --git a/td/telegram/SecureManager.h b/td/telegram/SecureManager.h index f83000123..20802ea6a 100644 --- a/td/telegram/SecureManager.h +++ b/td/telegram/SecureManager.h @@ -54,7 +54,7 @@ class SecureManager : public NetQueryCallback { void send_passport_authorization_form(int32 authorization_form_id, std::vector types, Promise<> promise); - void get_preferred_country_code(string country_code, Promise> promise); + void get_preferred_country_language(string country_code, Promise> promise); private: ActorShared<> parent_; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index c69bae077..62dee268f 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -22,6 +22,7 @@ #include "td/telegram/ConfigManager.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/ContactsManager.h" +#include "td/telegram/CountryInfoManager.h" #include "td/telegram/DeviceTokenManager.h" #include "td/telegram/DialogAdministrator.h" #include "td/telegram/DialogFilter.h" @@ -50,6 +51,7 @@ #include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/net/ConnectionCreator.h" @@ -155,35 +157,6 @@ void Td::ResultHandler::send_query(NetQueryPtr query) { td->send(std::move(query)); } -class GetNearestDcQuery : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetNearestDcQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create_unauth(telegram_api::help_getNearestDc())); - } - - void on_result(uint64 id, BufferSlice packet) override { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(id, result_ptr.move_as_error()); - } - - auto result = result_ptr.move_as_ok(); - promise_.set_value(std::move(result->country_)); - } - - void on_error(uint64 id, Status status) override { - if (!G()->is_expected_error(status) && status.message() != "BOT_METHOD_INVALID") { - LOG(ERROR) << "GetNearestDc returned " << status; - } - promise_.set_error(std::move(status)); - } -}; - class GetPromoDataQuery : public Td::ResultHandler { Promise> promise_; @@ -1424,7 +1397,7 @@ class SearchChatMessagesRequest : public RequestActor<> { MessageId from_message_id_; int32 offset_; int32 limit_; - tl_object_ptr filter_; + MessageSearchFilter filter_; int64 random_id_; std::pair> messages_; @@ -1459,46 +1432,40 @@ class SearchChatMessagesRequest : public RequestActor<> { , from_message_id_(from_message_id) , offset_(offset) , limit_(limit) - , filter_(std::move(filter)) + , filter_(get_message_search_filter(filter)) , random_id_(0) { set_tries(3); } }; -class OfflineSearchMessagesRequest : public RequestActor<> { +class SearchSecretMessagesRequest : public RequestActor<> { DialogId dialog_id_; string query_; - int64 from_search_id_; + string offset_; int32 limit_; - tl_object_ptr filter_; + MessageSearchFilter filter_; int64 random_id_; - std::pair> messages_; + MessagesManager::FoundMessages found_messages_; void do_run(Promise &&promise) override { - messages_ = td->messages_manager_->offline_search_messages(dialog_id_, query_, from_search_id_, limit_, filter_, - random_id_, std::move(promise)); + found_messages_ = td->messages_manager_->offline_search_messages(dialog_id_, query_, offset_, limit_, filter_, + random_id_, std::move(promise)); } void do_send_result() override { - vector> result; - result.reserve(messages_.second.size()); - for (auto full_message_id : messages_.second) { - result.push_back(td->messages_manager_->get_message_object(full_message_id)); - } - - send_result(make_tl_object(std::move(result), messages_.first)); + send_result(td->messages_manager_->get_found_messages_object(found_messages_)); } public: - OfflineSearchMessagesRequest(ActorShared td, uint64 request_id, int64 dialog_id, string query, - int64 from_search_id, int32 limit, tl_object_ptr filter) + SearchSecretMessagesRequest(ActorShared td, uint64 request_id, int64 dialog_id, string query, string offset, + int32 limit, tl_object_ptr filter) : RequestActor(std::move(td), request_id) , dialog_id_(dialog_id) , query_(std::move(query)) - , from_search_id_(from_search_id) + , offset_(std::move(offset)) , limit_(limit) - , filter_(std::move(filter)) + , filter_(get_message_search_filter(filter)) , random_id_(0) { } }; @@ -1511,6 +1478,7 @@ class SearchMessagesRequest : public RequestActor<> { DialogId offset_dialog_id_; MessageId offset_message_id_; int32 limit_; + MessageSearchFilter filter_; int64 random_id_; std::pair> messages_; @@ -1518,7 +1486,7 @@ class SearchMessagesRequest : public RequestActor<> { void do_run(Promise &&promise) override { messages_ = td->messages_manager_->search_messages(folder_id_, ignore_folder_id_, query_, offset_date_, offset_dialog_id_, - offset_message_id_, limit_, random_id_, std::move(promise)); + offset_message_id_, limit_, filter_, random_id_, std::move(promise)); } void do_send_result() override { @@ -1536,7 +1504,8 @@ class SearchMessagesRequest : public RequestActor<> { public: SearchMessagesRequest(ActorShared td, uint64 request_id, FolderId folder_id, bool ignore_folder_id, string query, - int32 offset_date, int64 offset_dialog_id, int64 offset_message_id, int32 limit) + int32 offset_date, int64 offset_dialog_id, int64 offset_message_id, int32 limit, + tl_object_ptr &&filter) : RequestActor(std::move(td), request_id) , folder_id_(folder_id) , ignore_folder_id_(ignore_folder_id) @@ -1545,6 +1514,7 @@ class SearchMessagesRequest : public RequestActor<> { , offset_dialog_id_(offset_dialog_id) , offset_message_id_(offset_message_id) , limit_(limit) + , filter_(get_message_search_filter(filter)) , random_id_(0) { } }; @@ -1638,7 +1608,7 @@ class GetChatMessageByDateRequest : public RequestOnceActor { class GetChatMessageCountRequest : public RequestActor<> { DialogId dialog_id_; - tl_object_ptr filter_; + MessageSearchFilter filter_; bool return_local_; int64 random_id_; @@ -1658,7 +1628,7 @@ class GetChatMessageCountRequest : public RequestActor<> { tl_object_ptr filter, bool return_local) : RequestActor(std::move(td), request_id) , dialog_id_(dialog_id) - , filter_(std::move(filter)) + , filter_(get_message_search_filter(filter)) , return_local_(return_local) , random_id_(0) { } @@ -1685,6 +1655,34 @@ class GetChatScheduledMessagesRequest : public RequestActor<> { } }; +class GetMessagePublicForwardsRequest : public RequestActor<> { + FullMessageId full_message_id_; + string offset_; + int32 limit_; + int64 random_id_; + + MessagesManager::FoundMessages messages_; + + void do_run(Promise &&promise) override { + messages_ = td->messages_manager_->get_message_public_forwards(full_message_id_, offset_, limit_, random_id_, + std::move(promise)); + } + + void do_send_result() override { + send_result(td->messages_manager_->get_found_messages_object(messages_)); + } + + public: + GetMessagePublicForwardsRequest(ActorShared td, uint64 request_id, int64 dialog_id, int64 message_id, + string offset, int32 limit) + : RequestActor(std::move(td), request_id) + , full_message_id_(DialogId(dialog_id), MessageId(message_id)) + , offset_(std::move(offset)) + , limit_(limit) + , random_id_(0) { + } +}; + class GetWebPagePreviewRequest : public RequestOnceActor { td_api::object_ptr text_; @@ -1875,10 +1873,6 @@ class GetChatMemberRequest : public RequestActor<> { send_result(td->contacts_manager_->get_chat_member_object(dialog_participant_)); } - void do_send_error(Status &&status) override { - send_error(std::move(status)); - } - public: GetChatMemberRequest(ActorShared td, uint64 request_id, int64 dialog_id, int32 user_id) : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), user_id_(user_id), random_id_(0) { @@ -2901,7 +2895,8 @@ class GetCallbackQueryAnswerRequest : public RequestOnceActor { int64 result_id_; void do_run(Promise &&promise) override { - result_id_ = td->callback_queries_manager_->send_callback_query(full_message_id_, payload_, std::move(promise)); + result_id_ = + td->callback_queries_manager_->send_callback_query(full_message_id_, std::move(payload_), std::move(promise)); } void do_send_result() override { @@ -3007,6 +3002,7 @@ class SetBackgroundRequest : public RequestActor<> { Td::Td(unique_ptr callback, Options options) : callback_(std::move(callback)), td_options_(std::move(options)) { + CHECK(callback_ != nullptr); } Td::~Td() = default; @@ -3302,7 +3298,9 @@ bool Td::is_preauthentication_request(int32 id) { case td_api::getNetworkStatistics::ID: case td_api::addNetworkStatistics::ID: case td_api::resetNetworkStatistics::ID: + case td_api::getCountries::ID: case td_api::getCountryCode::ID: + case td_api::getPhoneNumberInfo::ID: case td_api::getDeepLinkInfo::ID: case td_api::getApplicationConfig::ID: case td_api::saveApplicationLogEvent::ID: @@ -3724,7 +3722,7 @@ void Td::start_up() { } VLOG(td_init) << "Create Global"; - set_context(std::make_shared()); + old_context_ = set_context(std::make_shared()); G()->set_net_query_stats(td_options_.net_query_stats); inc_request_actor_refcnt(); // guard inc_actor_refcnt(); // guard @@ -3774,6 +3772,7 @@ void Td::inc_actor_refcnt() { void Td::dec_actor_refcnt() { actor_refcnt_--; + LOG(DEBUG) << "Decrease reference count to " << actor_refcnt_; if (actor_refcnt_ == 0) { if (close_flag_ == 2) { create_reference(); @@ -3789,8 +3788,12 @@ void Td::dec_actor_refcnt() { LOG(DEBUG) << "AuthManager was cleared" << timer; background_manager_.reset(); LOG(DEBUG) << "BackgroundManager was cleared" << timer; + callback_queries_manager_.reset(); + LOG(DEBUG) << "CallbackQueriesManager was cleared" << timer; contacts_manager_.reset(); LOG(DEBUG) << "ContactsManager was cleared" << timer; + country_info_manager_.reset(); + LOG(DEBUG) << "CountryInfoManager was cleared" << timer; documents_manager_.reset(); LOG(DEBUG) << "DocumentsManager was cleared" << timer; file_manager_.reset(); @@ -3847,6 +3850,7 @@ void Td::dec_stop_cnt() { stop_cnt_--; if (stop_cnt_ == 0) { LOG(WARNING) << "Stop Td"; + set_context(std::move(old_context_)); stop(); } } @@ -3857,6 +3861,7 @@ void Td::inc_request_actor_refcnt() { void Td::dec_request_actor_refcnt() { request_actor_refcnt_--; + LOG(DEBUG) << "Decrease request actor count to " << request_actor_refcnt_; if (request_actor_refcnt_ == 0) { LOG(WARNING) << "Have no request actors"; clear(); @@ -3973,6 +3978,8 @@ void Td::clear() { LOG(DEBUG) << "BackgroundManager actor was cleared" << timer; contacts_manager_actor_.reset(); LOG(DEBUG) << "ContactsManager actor was cleared" << timer; + country_info_manager_actor_.reset(); + LOG(DEBUG) << "CountryInfoManager actor was cleared" << timer; file_manager_actor_.reset(); LOG(DEBUG) << "FileManager actor was cleared" << timer; file_reference_manager_actor_.reset(); @@ -4205,7 +4212,7 @@ Status Td::init(DbKey key) { VLOG(td_init) << "Ping datacenter"; if (!auth_manager_->is_authorized()) { - send_get_nearest_dc_query(Promise()); + country_info_manager_->get_current_country_code(Promise()); } else { updates_manager_->get_difference("init"); schedule_get_terms_of_service(0); @@ -4412,6 +4419,8 @@ void Td::init_managers() { contacts_manager_ = make_unique(this, create_reference()); contacts_manager_actor_ = register_actor("ContactsManager", contacts_manager_.get()); G()->set_contacts_manager(contacts_manager_actor_.get()); + country_info_manager_ = make_unique(this, create_reference()); + country_info_manager_actor_ = register_actor("CountryInfoManager", country_info_manager_.get()); inline_queries_manager_ = make_unique(this, create_reference()); inline_queries_manager_actor_ = register_actor("InlineQueriesManager", inline_queries_manager_.get()); messages_manager_ = make_unique(this, create_reference()); @@ -4454,10 +4463,6 @@ void Td::init_managers() { "VerifyPhoneNumberManager", PhoneNumberManager::Type::VerifyPhone, create_reference()); } -void Td::send_get_nearest_dc_query(Promise promise) { - create_handler(std::move(promise))->send(); -} - void Td::send_update(tl_object_ptr &&object) { CHECK(object != nullptr); auto object_id = object->get_id(); @@ -4516,7 +4521,6 @@ void Td::send_result(uint64 id, tl_object_ptr object) { void Td::send_error_impl(uint64 id, tl_object_ptr error) { CHECK(id != 0); - CHECK(callback_ != nullptr); CHECK(error != nullptr); auto it = request_set_.find(id); if (it != request_set_.end()) { @@ -5479,7 +5483,8 @@ void Td::on_request(uint64 id, td_api::searchChatMessages &request) { void Td::on_request(uint64 id, td_api::searchSecretMessages &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); - CREATE_REQUEST(OfflineSearchMessagesRequest, request.chat_id_, std::move(request.query_), request.from_search_id_, + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST(SearchSecretMessagesRequest, request.chat_id_, std::move(request.query_), std::move(request.offset_), request.limit_, std::move(request.filter_)); } @@ -5492,7 +5497,7 @@ void Td::on_request(uint64 id, td_api::searchMessages &request) { } CREATE_REQUEST(SearchMessagesRequest, dialog_list_id.get_folder_id(), request.chat_list_ == nullptr, std::move(request.query_), request.offset_date_, request.offset_chat_id_, request.offset_message_id_, - request.limit_); + request.limit_, std::move(request.filter_)); } void Td::on_request(uint64 id, td_api::searchCallMessages &request) { @@ -5524,6 +5529,13 @@ void Td::on_request(uint64 id, const td_api::getChatScheduledMessages &request) CREATE_REQUEST(GetChatScheduledMessagesRequest, request.chat_id_); } +void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST(GetMessagePublicForwardsRequest, request.chat_id_, request.message_id_, request.offset_, + request.limit_); +} + void Td::on_request(uint64 id, const td_api::removeNotification &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6745,7 +6757,14 @@ void Td::on_request(uint64 id, const td_api::getChatStatistics &request) { contacts_manager_->get_channel_statistics(DialogId(request.chat_id_), request.is_dark_, std::move(promise)); } -void Td::on_request(uint64 id, td_api::getChatStatisticsGraph &request) { +void Td::on_request(uint64 id, const td_api::getMessageStatistics &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + contacts_manager_->get_channel_message_statistics({DialogId(request.chat_id_), MessageId(request.message_id_)}, + request.is_dark_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getStatisticsGraph &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); CLEAN_INPUT_STRING(request.token_); @@ -7466,7 +7485,7 @@ void Td::on_request(uint64 id, td_api::getPreferredCountryLanguage &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.country_code_); CREATE_REQUEST_PROMISE(); - send_closure(secure_manager_, &SecureManager::get_preferred_country_code, std::move(request.country_code_), + send_closure(secure_manager_, &SecureManager::get_preferred_country_language, std::move(request.country_code_), std::move(promise)); } @@ -7688,6 +7707,11 @@ void Td::on_request(uint64 id, td_api::acceptTermsOfService &request) { accept_terms_of_service(this, std::move(request.terms_of_service_id_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getCountries &request) { + CREATE_REQUEST_PROMISE(); + country_info_manager_->get_countries(std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getCountryCode &request) { CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { @@ -7697,7 +7721,12 @@ void Td::on_request(uint64 id, const td_api::getCountryCode &request) { promise.set_value(make_tl_object(result.move_as_ok())); } }); - create_handler(std::move(query_promise))->send(); + country_info_manager_->get_current_country_code(std::move(query_promise)); +} + +void Td::on_request(uint64 id, const td_api::getPhoneNumberInfo &request) { + CREATE_REQUEST_PROMISE(); + country_info_manager_->get_phone_number_info(request.phone_number_prefix_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getInviteText &request) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index dcb372b9a..939c18558 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -52,6 +52,7 @@ class CallManager; class CallbackQueriesManager; class ConfigManager; class ContactsManager; +class CountryInfoManager; class DeviceTokenManager; class DocumentsManager; class FileManager; @@ -155,6 +156,8 @@ class Td final : public NetQueryCallback { ActorOwn background_manager_actor_; unique_ptr contacts_manager_; ActorOwn contacts_manager_actor_; + unique_ptr country_info_manager_; + ActorOwn country_info_manager_actor_; unique_ptr file_manager_; ActorOwn file_manager_actor_; unique_ptr file_reference_manager_; @@ -353,6 +356,8 @@ class Td final : public NetQueryCallback { std::shared_ptr upload_file_callback_; + std::shared_ptr old_context_; + static int *get_log_verbosity_level(Slice name); template @@ -589,6 +594,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::getChatScheduledMessages &request); + void on_request(uint64 id, td_api::getMessagePublicForwards &request); + void on_request(uint64 id, const td_api::removeNotification &request); void on_request(uint64 id, const td_api::removeNotificationGroup &request); @@ -919,7 +926,9 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::getChatStatistics &request); - void on_request(uint64 id, td_api::getChatStatisticsGraph &request); + void on_request(uint64 id, const td_api::getMessageStatistics &request); + + void on_request(uint64 id, td_api::getStatisticsGraph &request); void on_request(uint64 id, const td_api::getMapThumbnailFile &request); @@ -1051,8 +1060,12 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::acceptTermsOfService &request); + void on_request(uint64 id, const td_api::getCountries &request); + void on_request(uint64 id, const td_api::getCountryCode &request); + void on_request(uint64 id, const td_api::getPhoneNumberInfo &request); + void on_request(uint64 id, const td_api::getInviteText &request); void on_request(uint64 id, td_api::getDeepLinkInfo &request); @@ -1169,8 +1182,6 @@ class Td final : public NetQueryCallback { static Status fix_parameters(TdParameters ¶meters) TD_WARN_UNUSED_RESULT; Status set_parameters(td_api::object_ptr parameters) TD_WARN_UNUSED_RESULT; - void send_get_nearest_dc_query(Promise promise); - static td_api::object_ptr make_error(int32 code, CSlice error) { return td_api::make_object(code, error.str()); } diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 8eb33dc54..42dfc7c21 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -506,6 +506,17 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ CHECK(message->media_ == nullptr); } + /* + // the users are always min, so no need to check + if (message->replies_ != nullptr) { + for (auto &user_id : message->replies_->recent_repliers_) { + if (!is_acceptable_user(UserId(user_id))) { + return false; + } + } + } + */ + break; } case telegram_api::messageService::ID: { @@ -695,8 +706,8 @@ void UpdatesManager::on_get_updates(tl_object_ptr &&updat update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, update->id_, from_id, make_tl_object(update->user_id_), std::move(update->fwd_from_), - update->via_bot_id_, update->reply_to_msg_id_, update->date_, update->message_, nullptr, nullptr, - std::move(update->entities_), 0, 0, "", 0, Auto()), + update->via_bot_id_, update->reply_to_msg_id_, 0, update->date_, update->message_, nullptr, nullptr, + std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, Auto()), update->pts_, update->pts_count_), 0, "telegram_api::updatesShortMessage"); break; @@ -713,16 +724,16 @@ void UpdatesManager::on_get_updates(tl_object_ptr &&updat } update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID; - on_pending_update( - make_tl_object( - make_tl_object( - update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, update->id_, - update->from_id_, make_tl_object(update->chat_id_), - std::move(update->fwd_from_), update->via_bot_id_, update->reply_to_msg_id_, update->date_, - update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, "", 0, Auto()), - update->pts_, update->pts_count_), - 0, "telegram_api::updatesShortChatMessage"); + on_pending_update(make_tl_object( + make_tl_object( + update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, update->id_, update->from_id_, + make_tl_object(update->chat_id_), std::move(update->fwd_from_), + update->via_bot_id_, update->reply_to_msg_id_, 0, update->date_, update->message_, + nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, Auto()), + update->pts_, update->pts_count_), + 0, "telegram_api::updatesShortChatMessage"); break; } case telegram_api::updateShort::ID: { @@ -1684,7 +1695,19 @@ void UpdatesManager::on_update(tl_object_ptrmessages_manager_->on_update_message_views({dialog_id, MessageId(ServerMessageId(update->id_))}, update->views_); + td_->messages_manager_->on_update_message_view_count({dialog_id, MessageId(ServerMessageId(update->id_))}, + update->views_); +} + +void UpdatesManager::on_update(tl_object_ptr update, bool /*force_apply*/) { + ChannelId channel_id(update->channel_id_); + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + return; + } + DialogId dialog_id(channel_id); + td_->messages_manager_->on_update_message_forward_count({dialog_id, MessageId(ServerMessageId(update->id_))}, + update->forwards_); } void UpdatesManager::on_update(tl_object_ptr update, @@ -2174,4 +2197,7 @@ void UpdatesManager::on_update(tl_object_ptr update, bool /*force_apply*/) { } +void UpdatesManager::on_update(tl_object_ptr update, bool /*force_apply*/) { +} + } // namespace td diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index 91c50760c..45c4978e3 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -243,6 +243,7 @@ class UpdatesManager : public Actor { void on_update(tl_object_ptr update, bool /*force_apply*/); void on_update(tl_object_ptr update, bool force_apply); void on_update(tl_object_ptr update, bool force_apply); + void on_update(tl_object_ptr update, bool force_apply); void on_update(tl_object_ptr update, bool /*force_apply*/); void on_update(tl_object_ptr update, bool /*force_apply*/); @@ -314,6 +315,8 @@ class UpdatesManager : public Actor { // unsupported updates void on_update(tl_object_ptr update, bool /*force_apply*/); + + void on_update(tl_object_ptr update, bool /*force_apply*/); }; } // namespace td diff --git a/td/telegram/Version.h b/td/telegram/Version.h index d5436e02d..25fa03756 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -8,7 +8,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 117; +constexpr int32 MTPROTO_LAYER = 119; enum class Version : int32 { Initial, // 0 @@ -39,6 +39,7 @@ enum class Version : int32 { AddDiceEmoji, // 25 AddAnimationStickers, AddDialogPhotoHasAnimation, + AddPhotoProgressiveSizes, Next }; diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index e07c65a72..1f65569d6 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -1013,7 +1013,7 @@ class CliClient final : public Actor { return nullptr; } - static td_api::object_ptr get_search_messages_filter(MutableSlice filter) { + static td_api::object_ptr as_search_messages_filter(MutableSlice filter) { filter = trim(filter); to_lower_inplace(filter); if (filter == "an" || filter == "animation") { @@ -1848,6 +1848,18 @@ class CliClient final : public Actor { } else if (op == "gcsm") { string chat_id = args; send_request(td_api::make_object(as_chat_id(chat_id))); + } else if (op == "gmpf") { + string chat_id; + string message_id; + string offset; + string limit; + + std::tie(chat_id, args) = split(args); + std::tie(message_id, args) = split(args); + std::tie(offset, limit) = split(args); + + send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(message_id), + offset, to_integer(limit))); } else if (op == "ghf") { get_history_chat_id_ = as_chat_id(args); @@ -1862,9 +1874,11 @@ class CliClient final : public Actor { string from_date; string limit; string query; + string filter; std::tie(query, args) = split(args); - std::tie(limit, from_date) = split(args); + std::tie(limit, args) = split(args); + std::tie(filter, from_date) = split(args); if (from_date.empty()) { from_date = "0"; } @@ -1876,7 +1890,8 @@ class CliClient final : public Actor { chat_list = td_api::make_object(); } send_request(td_api::make_object( - std::move(chat_list), query, to_integer(from_date), 2147482647, 0, to_integer(limit))); + std::move(chat_list), query, to_integer(from_date), 2147482647, 0, to_integer(limit), + as_search_messages_filter(filter))); } else if (op == "SCM") { string chat_id; string limit; @@ -1937,7 +1952,7 @@ class CliClient final : public Actor { send_request(td_api::make_object( as_chat_id(chat_id), "", 0, as_message_id(offset_message_id), to_integer(offset), - to_integer(limit), get_search_messages_filter(filter))); + to_integer(limit), as_search_messages_filter(filter))); } else if (op == "SC") { string limit; string offset_message_id; @@ -2046,7 +2061,7 @@ class CliClient final : public Actor { std::tie(filter, return_local) = split(args); send_request(td_api::make_object( - as_chat_id(chat_id), get_search_messages_filter(filter), as_bool(return_local))); + as_chat_id(chat_id), as_search_messages_filter(filter), as_bool(return_local))); } else if (op == "gup" || op == "gupp") { string user_id; string offset; @@ -2258,8 +2273,12 @@ class CliClient final : public Actor { send_request(td_api::make_object(to_integer(args))); } else if (op == "rbgs") { send_request(td_api::make_object()); - } else if (op == "gccode") { + } else if (op == "gcos") { + send_request(td_api::make_object()); + } else if (op == "gcoc") { send_request(td_api::make_object()); + } else if (op == "gpni") { + send_request(td_api::make_object(args)); } else if (op == "git") { send_request(td_api::make_object()); } else if (op == "atos") { @@ -2758,9 +2777,11 @@ class CliClient final : public Actor { vector> problems; problems.emplace_back(td_api::make_object()); problems.emplace_back(td_api::make_object()); + problems.emplace_back(td_api::make_object()); problems.emplace_back(nullptr); problems.emplace_back(td_api::make_object()); problems.emplace_back(td_api::make_object()); + problems.emplace_back(td_api::make_object()); problems.emplace_back(td_api::make_object()); send_request(td_api::make_object( as_call_id(call_id), to_integer(rating), "Wow, such good call! (TDLib test)", std::move(problems))); @@ -2918,19 +2939,18 @@ class CliClient final : public Actor { } } else if (op == "ssm") { string chat_id; - string from_search_id; + string offset; string limit; string filter; string query; std::tie(chat_id, args) = split(args); - std::tie(from_search_id, args) = split(args); + std::tie(offset, args) = split(args); std::tie(limit, args) = split(args); std::tie(filter, query) = split(args); send_request(td_api::make_object( - as_chat_id(chat_id), query, to_integer(from_search_id), to_integer(limit), - get_search_messages_filter(filter))); + as_chat_id(chat_id), query, offset, to_integer(limit), as_search_messages_filter(filter))); } else if (op == "ssd") { schedule_date_ = args; } else if (op == "sm" || op == "sms" || op == "smr" || op == "smf") { @@ -3105,7 +3125,7 @@ class CliClient final : public Actor { auto chat = as_chat_id(chat_id); send_request(td_api::make_object( chat, 0, default_message_send_options(), to_integer(query_id), result_id, op == "siqrh")); - } else if (op == "gcqr") { + } else if (op == "gcqa") { string chat_id; string message_id; string data; @@ -3113,7 +3133,18 @@ class CliClient final : public Actor { std::tie(message_id, data) = split(args); send_request(td_api::make_object( as_chat_id(chat_id), as_message_id(message_id), td_api::make_object(data))); - } else if (op == "gcgqr") { + } else if (op == "gcpqa") { + string chat_id; + string message_id; + string password; + string data; + std::tie(chat_id, args) = split(args); + std::tie(message_id, args) = split(args); + std::tie(password, data) = split(args); + send_request(td_api::make_object( + as_chat_id(chat_id), as_message_id(message_id), + td_api::make_object(password, data))); + } else if (op == "gcgqa") { string chat_id; string message_id; std::tie(chat_id, message_id) = split(args); @@ -4022,15 +4053,23 @@ class CliClient final : public Actor { std::tie(chat_id, is_dark) = split(args); send_request(td_api::make_object(as_chat_id(chat_id), as_bool(is_dark))); - } else if (op == "gcstg") { + } else if (op == "gmst") { + string chat_id; + string message_id; + string is_dark; + std::tie(chat_id, args) = split(args); + std::tie(message_id, is_dark) = split(args); + + send_request(td_api::make_object(as_chat_id(chat_id), as_message_id(message_id), + as_bool(is_dark))); + } else if (op == "gstg") { string chat_id; string token; string x; std::tie(chat_id, args) = split(args); std::tie(token, x) = split(args); - send_request( - td_api::make_object(as_chat_id(chat_id), token, to_integer(x))); + send_request(td_api::make_object(as_chat_id(chat_id), token, to_integer(x))); } else if (op == "hsa" || op == "glu" || op == "glua") { send_request(td_api::make_object(as_suggested_action(args))); } else if (op == "glui" || op == "glu" || op == "glua") { diff --git a/td/telegram/files/FileLoader.cpp b/td/telegram/files/FileLoader.cpp index 462c8e966..f4ed1f865 100644 --- a/td/telegram/files/FileLoader.cpp +++ b/td/telegram/files/FileLoader.cpp @@ -42,7 +42,11 @@ size_t FileLoader::get_part_size() const { } void FileLoader::hangup() { - delay_dispatcher_.reset(); + if (delay_dispatcher_.empty()) { + stop(); + } else { + delay_dispatcher_.reset(); + } } void FileLoader::hangup_shared() { diff --git a/td/telegram/logevent/SecretChatEvent.h b/td/telegram/logevent/SecretChatEvent.h index c01e86993..361574cf8 100644 --- a/td/telegram/logevent/SecretChatEvent.h +++ b/td/telegram/logevent/SecretChatEvent.h @@ -330,6 +330,7 @@ class OutboundSecretMessage : public SecretChatLogEventBase action; uint64 crc = 0; // DEBUG; @@ -360,6 +361,7 @@ class OutboundSecretMessage : public SecretChatLogEventBase { , answer_(std::move(answer)) , tl_constructor_(tl_constructor) , total_timeout_limit_(total_timeout_limit) { - get_data_unsafe().my_id_ = get_my_id(); - get_data_unsafe().start_timestamp_ = Time::now(); + auto &data = get_data_unsafe(); + data.my_id_ = get_my_id(); + data.start_timestamp_ = data.state_timestamp_ = Time::now(); LOG(INFO) << *this; if (stats) { nq_counter_ = stats->register_query(this); diff --git a/tdactor/td/actor/impl/ActorInfo.h b/tdactor/td/actor/impl/ActorInfo.h index bb58d82c3..c1e6ef64d 100644 --- a/tdactor/td/actor/impl/ActorInfo.h +++ b/tdactor/td/actor/impl/ActorInfo.h @@ -40,6 +40,7 @@ inline void ActorInfo::init(int32 sched_id, Slice name, ObjectPool::O if (!is_lite) { context_ = Scheduler::context()->this_ptr_.lock(); + VLOG(actor) << "Set context " << context_.get() << " for " << name; #ifdef TD_DEBUG name_ = name.str(); #endif @@ -77,6 +78,7 @@ inline void ActorInfo::clear() { // NB: must be in non migrating state // store invalid scheduler id. sched_id_.store((1 << 30) - 1, std::memory_order_relaxed); + VLOG(actor) << "Clear context " << context_.get() << " for " << get_name(); context_.reset(); } diff --git a/tdnet/td/net/SslStream.cpp b/tdnet/td/net/SslStream.cpp index 92d68e363..64f3af931 100644 --- a/tdnet/td/net/SslStream.cpp +++ b/tdnet/td/net/SslStream.cpp @@ -124,7 +124,7 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { int err = X509_STORE_CTX_get_error(ctx); auto warning = PSTRING() << "verify error:num=" << err << ":" << X509_verify_cert_error_string(err) - << ":depth=" << X509_STORE_CTX_get_error_depth(ctx) << ":" << buf; + << ":depth=" << X509_STORE_CTX_get_error_depth(ctx) << ":" << Slice(buf, std::strlen(buf)); double now = Time::now(); static std::mutex warning_mutex; diff --git a/tdutils/td/utils/format.h b/tdutils/td/utils/format.h index f6bbc4063..a3b5b3a46 100644 --- a/tdutils/td/utils/format.h +++ b/tdutils/td/utils/format.h @@ -174,7 +174,7 @@ inline StringBuilder &operator<<(StringBuilder &logger, Time t) { while (i + 1 < durations_n && t.seconds_ > 10 * durations[i + 1].value) { i++; } - logger << StringBuilder::FixedDouble(t.seconds_ / durations[i].value, 1) << durations[i].name; + logger << StringBuilder::FixedDouble(t.seconds_ / durations[i].value, 1) << Slice(durations[i].name); return logger; } @@ -200,7 +200,7 @@ inline StringBuilder &operator<<(StringBuilder &logger, Size t) { while (i + 1 < sizes_n && t.size_ > 10 * sizes[i + 1].value) { i++; } - logger << t.size_ / sizes[i].value << sizes[i].name; + logger << t.size_ / sizes[i].value << Slice(sizes[i].name); return logger; } diff --git a/tdutils/td/utils/logging.h b/tdutils/td/utils/logging.h index 94a20c13e..07d37c384 100644 --- a/tdutils/td/utils/logging.h +++ b/tdutils/td/utils/logging.h @@ -251,7 +251,7 @@ class Logger { Logger(LogInterface &log, const LogOptions &options, int log_level, Slice file_name, int line_num, Slice comment); template - Logger &operator<<(const T &other) { + Logger &operator<<(T &&other) { sb_ << other; return *this; } diff --git a/tdutils/td/utils/port/detail/Epoll.cpp b/tdutils/td/utils/port/detail/Epoll.cpp index e30b87336..d15eccfd6 100644 --- a/tdutils/td/utils/port/detail/Epoll.cpp +++ b/tdutils/td/utils/port/detail/Epoll.cpp @@ -112,7 +112,7 @@ void Epoll::run(int timeout_ms) { flags = flags | PollFlags::Error(); } if (event->events) { - LOG(FATAL) << "Unsupported epoll events: " << event->events; + LOG(FATAL) << "Unsupported epoll events: " << static_cast(event->events); } //LOG(DEBUG) << "Epoll event " << tag("fd", event->data.fd) << tag("flags", format::as_binary(flags)); auto pollable_fd = PollableFd::from_list_node(static_cast(event->data.ptr)); diff --git a/tdutils/td/utils/port/detail/EventFdBsd.cpp b/tdutils/td/utils/port/detail/EventFdBsd.cpp index 3544069d9..0eb9e5a84 100644 --- a/tdutils/td/utils/port/detail/EventFdBsd.cpp +++ b/tdutils/td/utils/port/detail/EventFdBsd.cpp @@ -77,7 +77,7 @@ void EventFdBsd::release() { } size_t size = result.ok(); if (size != sizeof(value)) { - LOG(FATAL) << "EventFdBsd write returned " << value << " instead of " << sizeof(value); + LOG(FATAL) << "EventFdBsd write returned " << size << " instead of " << sizeof(value); } } diff --git a/tdutils/td/utils/port/uname.cpp b/tdutils/td/utils/port/uname.cpp index 4a35b6897..ee0fde119 100644 --- a/tdutils/td/utils/port/uname.cpp +++ b/tdutils/td/utils/port/uname.cpp @@ -16,6 +16,8 @@ #if TD_PORT_POSIX +#include + #if TD_ANDROID #include #else @@ -99,7 +101,8 @@ Slice get_operating_system_version() { utsname name; int err = uname(&name); if (err == 0) { - auto os_name = trim(PSTRING() << name.sysname << " " << name.release); + auto os_name = trim(PSTRING() << Slice(name.sysname, std::strlen(name.sysname)) << " " + << Slice(name.release, std::strlen(name.release))); if (!os_name.empty()) { return os_name; } diff --git a/test/secret.cpp b/test/secret.cpp index b6041789e..170f4317a 100644 --- a/test/secret.cpp +++ b/test/secret.cpp @@ -177,6 +177,7 @@ class messages_sendEncryptedService final { class messages_sendEncrypted final { public: + int32 flags_; tl_object_ptr peer_; int64 random_id_{}; BufferSlice data_; @@ -185,7 +186,8 @@ class messages_sendEncrypted final { static const int32 ID = -1451792525; explicit messages_sendEncrypted(TlBufferParser &p) - : peer_(TlFetchBoxed, -247351839>::parse(p)) + : flags_(TlFetchInt::parse(p)) + , peer_(TlFetchBoxed, -247351839>::parse(p)) , random_id_(TlFetchLong::parse(p)) , data_(TlFetchBytes::parse(p)) { }