Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2021-08-16 10:43:58 +02:00
commit fa1204d3b5
44 changed files with 1169 additions and 490 deletions

View File

@ -544,6 +544,7 @@ set(TDLIB_SOURCE
td/telegram/MessageId.h td/telegram/MessageId.h
td/telegram/MessageLinkInfo.h td/telegram/MessageLinkInfo.h
td/telegram/MessageReplyInfo.h td/telegram/MessageReplyInfo.h
td/telegram/MessageThreadInfo.h
td/telegram/MessagesDb.h td/telegram/MessagesDb.h
td/telegram/MessageSearchFilter.h td/telegram/MessageSearchFilter.h
td/telegram/MessagesManager.h td/telegram/MessagesManager.h

View File

@ -203,11 +203,9 @@ For C++ projects that use CMake, the best approach is to build `TDLib` as part o
There are several libraries that you could use in your CMake project: There are several libraries that you could use in your CMake project:
* Td::TdJson, Td::TdJsonStatic — dynamic and static version of a JSON interface. This has a simple C interface, so it can be easily used with any programming language that is able to execute C functions. * Td::TdJson, Td::TdJsonStatic — dynamic and static version of a JSON interface. This has a simple C interface, so it can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) and [td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) documentation for more information. See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for more information.
* Td::TdStatic — static library with C++ interface for general usage. * Td::TdStatic — static library with C++ interface for general usage.
See [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) and [Log](https://core.telegram.org/tdlib/docs/classtd_1_1_log.html) documentation for more information. See [ClientManager](https://core.telegram.org/tdlib/docs/classtd_1_1_client_manager.html) and [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) documentation for more information.
* Td::TdCoreStatic — static library with low-level C++ interface intended mostly for internal usage.
See [ClientActor](https://core.telegram.org/tdlib/docs/classtd_1_1_client_actor.html) and [Log](https://core.telegram.org/tdlib/docs/classtd_1_1_log.html) documentation for more information.
For example, part of your CMakeLists.txt may look like this: For example, part of your CMakeLists.txt may look like this:
``` ```
@ -245,7 +243,7 @@ git checkout td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
## Using from other programming languages ## Using from other programming languages
`TDLib` provides efficient native C++, Java, and .NET interfaces. `TDLib` provides efficient native C++, Java, and .NET interfaces.
But for most use cases we suggest to use the JSON interface, which can be easily used with any programming language that is able to execute C functions. But for most use cases we suggest to use the JSON interface, which can be easily used with any programming language that is able to execute C functions.
See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) and [td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) documentation for detailed JSON interface description, See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for detailed JSON interface description,
the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of
all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html). all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).

View File

@ -626,7 +626,7 @@ function onOptionsChanged() {
cmake = 'cmake3'; cmake = 'cmake3';
packages += ' gperf'; packages += ' gperf';
} else { } else {
commands.push(sudo + 'dnf --enablerepo=PowerTools install gperf'); commands.push(sudo + 'dnf --enablerepo=powertools install gperf');
} }
packages += ' ' + cmake; packages += ' ' + cmake;
if (target === 'JNI') { if (target === 'JNI') {

View File

@ -7,5 +7,5 @@ Then you can run the example:
python tdjson_example.py python tdjson_example.py
``` ```
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html), Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
[td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation. and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@ -11,5 +11,5 @@ cmake --build . --target install
Then you can open and build the example with the latest Xcode. Then you can open and build the example with the latest Xcode.
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html), Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
[td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation. and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@ -759,6 +759,8 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r
//@can_be_deleted_for_all_users True, if the message can be deleted for all users //@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_statistics True, if the message statistics are available
//@can_get_message_thread True, if the message thread info is available //@can_get_message_thread True, if the message thread info is available
//@can_get_media_timestamp_links True, if media timestamp links can be generated for media timestamp entities in the message text, caption or web page description
//@has_timestamped_media True, if media timestamp entities refers to a media in this message as opposed to a media in the replied message
//@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 //@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 //@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 //@date Point in time (Unix timestamp) when the message was sent
@ -776,7 +778,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r
//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted //@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 //@content Content of the message
//@reply_markup Reply markup for the message; may be null //@reply_markup Reply markup for the message; may be null
message id:int53 sender:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned: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_message_thread:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_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; message id:int53 sender:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned: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_message_thread:Bool can_get_media_timestamp_links:Bool has_timestamped_media:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_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 //@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<message> = Messages; messages total_count:int32 messages:vector<message> = Messages;
@ -1852,6 +1854,9 @@ textEntityTypeTextUrl url:string = TextEntityType;
//@description A text shows instead of a raw mention of the user (e.g., when the user has no username) @user_id Identifier of the mentioned user //@description A text shows instead of a raw mention of the user (e.g., when the user has no username) @user_id Identifier of the mentioned user
textEntityTypeMentionName user_id:int32 = TextEntityType; textEntityTypeMentionName user_id:int32 = TextEntityType;
//@description A media timestamp @media_timestamp Timestamp from which a video/audio/video note/voice note playing should start, in seconds. The media can be in the content or the web page preview of the current message, or in the same places in the replied message
textEntityTypeMediaTimestamp media_timestamp:int32 = TextEntityType;
//@description A thumbnail to be sent along with a file; must be in JPEG or WEBP format for stickers, and less than 200 KB in size @thumbnail Thumbnail file to send. Sending thumbnails by file_id is currently not supported //@description A thumbnail to be sent along with a file; must be in JPEG or WEBP format for stickers, and less than 200 KB in size @thumbnail Thumbnail file to send. Sending thumbnails by file_id is currently not supported
//@width Thumbnail width, usually shouldn't exceed 320. Use 0 if unknown @height Thumbnail height, usually shouldn't exceed 320. Use 0 if unknown //@width Thumbnail width, usually shouldn't exceed 320. Use 0 if unknown @height Thumbnail height, usually shouldn't exceed 320. Use 0 if unknown
@ -3058,7 +3063,7 @@ internalLinkTypeChatInvite = InternalLinkType;
//@description The link is a link to the filter settings section of the app //@description The link is a link to the filter settings section of the app
internalLinkTypeFilterSettings = InternalLinkType; internalLinkTypeFilterSettings = InternalLinkType;
//@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a group to send the game, and then call sendMessage with inputMessageGame //@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a chat to send the game, and then call sendMessage with inputMessageGame
//@bot_username Username of the bot that owns the game @game_short_name Short name of the game //@bot_username Username of the bot that owns the game @game_short_name Short name of the game
internalLinkTypeGame bot_username:string game_short_name:string = InternalLinkType; internalLinkTypeGame bot_username:string game_short_name:string = InternalLinkType;
@ -3119,7 +3124,7 @@ messageLink link:string is_public:Bool = MessageLink;
//@is_public True, if the link is a public link for a message in a chat //@is_public True, if the link is a public link for a message in a chat
//@chat_id If found, identifier of the chat to which the message belongs, 0 otherwise //@chat_id If found, identifier of the chat to which the message belongs, 0 otherwise
//@message If found, the linked message; may be null //@message If found, the linked message; may be null
//@media_timestamp Timestamp from which the video/audio/video note/voice note playing should start, in seconds; 0 if not specified. The media can be in the message content or in its link preview //@media_timestamp Timestamp from which the video/audio/video note/voice note playing should start, in seconds; 0 if not specified. The media can be in the message content or in its web page preview
//@for_album True, if the whole media album to which the message belongs is linked //@for_album True, if the whole media album to which the message belongs is linked
//@for_comment True, if the message is linked as a channel post comment or from a message thread //@for_comment True, if the message is linked as a channel post comment or from a message thread
messageLinkInfo is_public:Bool chat_id:int53 message:message media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLinkInfo; messageLinkInfo is_public:Bool chat_id:int53 message:message media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLinkInfo;
@ -4027,10 +4032,10 @@ getChannelDifference channel_difference_id:int64 = Ok;
getRemoteFile remote_file_id:string file_type:FileType = File; getRemoteFile remote_file_id:string file_type:FileType = File;
//@description Returns an ordered list of chats in a chat list. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. (For example, to get a list of chats from the beginning, the offset_order should be equal to a biggest signed 64-bit number 9223372036854775807 == 2^63 - 1). //@description Returns an ordered list of chats in a chat list. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. (For example, to get a list of chats from the beginning, the offset_order should be equal to a biggest signed 64-bit number 9223372036854775807 == 2^63 - 1).
//-For optimal performance the number of returned chats is chosen by the library //-For optimal performance, the number of returned chats is chosen by TDLib
//@chat_list The chat list in which to return chats //@chat_list The chat list in which to return chats
//@offset_order Chat order to return chats from @offset_chat_id Chat identifier to return chats from //@offset_order Chat order to return chats from @offset_chat_id Chat identifier to return chats from
//@limit The maximum number of chats to be returned. It is possible that fewer chats than the limit are returned even if the end of the list is not reached //@limit The maximum number of chats to be returned. For optimal performance, the number of returned chats is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached
getChats chat_list:ChatList offset_order:int64 offset_chat_id:int53 limit:int32 = Chats; getChats chat_list:ChatList offset_order:int64 offset_chat_id:int53 limit:int32 = Chats;
//@description Searches a public chat by its username. Currently only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved //@description Searches a public chat by its username. Currently only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved
@ -4084,21 +4089,21 @@ getGroupsInCommon user_id:int32 offset_chat_id:int53 limit:int32 = Chats;
//@description Returns messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). //@description Returns messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id).
//-For optimal performance the number of returned messages is chosen by the library. This is an offline request if only_local is true //-For optimal performance, the number of returned messages is chosen by TDLib. This is an offline request if only_local is true
//@chat_id Chat identifier //@chat_id Chat identifier
//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message //@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages //@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached //@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@only_local If true, returns only messages that are available locally without sending network requests //@only_local If true, returns only messages that are available locally without sending network requests
getChatHistory chat_id:int53 from_message_id:int53 offset:int32 limit:int32 only_local:Bool = Messages; getChatHistory chat_id:int53 from_message_id:int53 offset:int32 limit:int32 only_local:Bool = Messages;
//@description Returns messages in a message thread of a message. Can be used only if message.can_get_message_thread == true. Message thread of a channel message is in the channel's linked supergroup. //@description Returns messages in a message thread of a message. Can be used only if message.can_get_message_thread == true. Message thread of a channel message is in the channel's linked supergroup.
//-The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). For optimal performance the number of returned messages is chosen by the library //-The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Chat identifier //@chat_id Chat identifier
//@message_id Message identifier, which thread history needs to be returned //@message_id Message identifier, which thread history needs to be returned
//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message //@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages //@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. Fewer messages may be returned than specified by the limit, even if the end of the message thread history has not been reached //@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 offset:int32 limit:int32 = Messages; getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 offset:int32 limit:int32 = Messages;
//@description Deletes all messages in the chat. Use Chat.can_be_deleted_only_for_self and Chat.can_be_deleted_for_all_users fields to find whether and how the method can be applied to the chat //@description Deletes all messages in the chat. Use Chat.can_be_deleted_only_for_self and Chat.can_be_deleted_for_all_users fields to find whether and how the method can be applied to the chat
@ -4109,41 +4114,41 @@ deleteChatHistory chat_id:int53 remove_from_chat_list:Bool revoke:Bool = Ok;
deleteChat chat_id:int53 = Ok; deleteChat chat_id:int53 = Ok;
//@description Searches for messages with given words in the chat. Returns the results in reverse chronological order, i.e. in order of decreasing message_id. Cannot be used in secret chats with a non-empty query //@description Searches for messages with given words in the chat. Returns the results in reverse chronological order, i.e. in order of decreasing message_id. Cannot be used in secret chats with a non-empty query
//-(searchSecretMessages should be used instead), or without an enabled message database. For optimal performance the number of returned messages is chosen by the library //-(searchSecretMessages should be used instead), or without an enabled message database. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@chat_id Identifier of the chat in which to search messages //@chat_id Identifier of the chat in which to search messages
//@query Query to search for //@query Query to search for
//@sender If not null, only messages sent by the specified sender will be returned. Not supported in secret chats //@sender If not null, only messages sent by the specified sender will be returned. Not supported in secret chats
//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message //@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset to get the specified message and some newer messages //@offset Specify 0 to get results from exactly the from_message_id or a negative offset to get the specified message and some newer messages
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached //@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter Filter for message content in the search results //@filter Filter for message content in the search results
//@message_thread_id If not 0, only messages in the specified thread will be returned; supergroups only //@message_thread_id If not 0, only messages in the specified thread will be returned; supergroups only
searchChatMessages chat_id:int53 query:string sender:MessageSender from_message_id:int53 offset:int32 limit:int32 filter:SearchMessagesFilter message_thread_id:int53 = Messages; searchChatMessages chat_id:int53 query:string sender:MessageSender from_message_id:int53 offset:int32 limit:int32 filter:SearchMessagesFilter message_thread_id:int53 = Messages;
//@description Searches for messages in all chats except secret chats. Returns the results in reverse chronological order (i.e., in order of decreasing (date, chat_id, message_id)). //@description Searches for messages in all chats except secret chats. Returns the results in reverse chronological order (i.e., in order of decreasing (date, chat_id, message_id)).
//-For optimal performance the number of returned messages is chosen by the library //-For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@chat_list Chat list in which to search messages; pass null to search in all chats regardless of their chat list. Only Main and Archive chat lists are supported //@chat_list Chat list in which to search messages; pass null to search in all chats regardless of their chat list. Only Main and Archive chat lists are supported
//@query Query to search for //@query Query to search for
//@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_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_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 //@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 //@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter Filter for message content in the search results; searchMessagesFilterCall, searchMessagesFilterMissedCall, searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterFailedToSend and searchMessagesFilterPinned are unsupported in this function //@filter Filter for message content in the search results; searchMessagesFilterCall, searchMessagesFilterMissedCall, searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterFailedToSend and searchMessagesFilterPinned are unsupported in this function
//@min_date If not 0, the minimum date of the messages to return //@min_date If not 0, the minimum date of the messages to return
//@max_date If not 0, the maximum date of the messages to return //@max_date If not 0, the maximum date of the messages to return
searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = Messages; searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = 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 //@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 TDLib
//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats //@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 //@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 //@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 //@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter A filter for message content in the search results //@filter A filter for message content in the search results
searchSecretMessages chat_id:int53 query:string offset:string limit:int32 filter:SearchMessagesFilter = FoundMessages; 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 //@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 TDLib
//@from_message_id Identifier of the message from which to search; use 0 to get results from the last message //@from_message_id Identifier of the message from which to search; use 0 to get results from the last message
//@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 @only_missed If true, returns only messages with missed calls //@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit @only_missed If true, returns only messages with missed calls
searchCallMessages from_message_id:int53 limit:int32 only_missed:Bool = Messages; searchCallMessages from_message_id:int53 limit:int32 only_missed:Bool = Messages;
//@description Deletes all call messages @revoke Pass true to delete the messages for all users //@description Deletes all call messages @revoke Pass true to delete the messages for all users
@ -4164,11 +4169,11 @@ 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 //@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; getChatScheduledMessages chat_id:int53 = Messages;
//@description Returns forwarded copies of a channel message to different public channels. For optimal performance the number of returned messages is chosen by the library //@description Returns forwarded copies of a channel message to different public channels. For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Chat identifier of the message //@chat_id Chat identifier of the message
//@message_id Message identifier //@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 //@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 //@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages; getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages;
@ -4179,10 +4184,10 @@ removeNotification notification_group_id:int32 notification_id:int32 = Ok;
removeNotificationGroup notification_group_id:int32 max_notification_id:int32 = Ok; removeNotificationGroup notification_group_id:int32 max_notification_id:int32 = Ok;
//@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels. This is an offline request //@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels, or if message.can_get_media_timestamp_links and a media timestamp link is generated. This is an offline request
//@chat_id Identifier of the chat to which the message belongs //@chat_id Identifier of the chat to which the message belongs
//@message_id Identifier of the message //@message_id Identifier of the message
//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing should start, in seconds. The media can be in the message content or in its link preview //@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing should start, in seconds. The media can be in the message content or in its web page preview
//@for_album Pass true to create a link for the whole media album //@for_album Pass true to create a link for the whole media album
//@for_comment Pass true to create a link to the message as a channel post comment, or from a message thread //@for_comment Pass true to create a link to the message as a channel post comment, or from a message thread
getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLink; getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bool for_comment:Bool = MessageLink;
@ -4193,7 +4198,7 @@ getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bo
//@for_album Pass true to return an HTML code for embedding of the whole media album //@for_album Pass true to return an HTML code for embedding of the whole media album
getMessageEmbeddingCode chat_id:int53 message_id:int53 for_album:Bool = Text; getMessageEmbeddingCode chat_id:int53 message_id:int53 for_album:Bool = Text;
//@description Returns information about a public or private message link @url The message link //@description Returns information about a public or private message link. Can be called for any internal link of the type internalLinkTypeMessage @url The message link
getMessageLinkInfo url:string = MessageLinkInfo; getMessageLinkInfo url:string = MessageLinkInfo;
@ -4341,11 +4346,11 @@ getJsonString json_value:JsonValue = Text;
//@option_ids 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers //@option_ids 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers
setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok; setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
//@description Returns users voted for the specified option in a non-anonymous polls. For the optimal performance the number of returned users is chosen by the library //@description Returns users voted for the specified option in a non-anonymous polls. For optimal performance, the number of returned users is chosen by TDLib
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll //@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_id 0-based identifier of the answer option //@option_id 0-based identifier of the answer option
//@offset Number of users to skip in the result; must be non-negative //@offset Number of users to skip in the result; must be non-negative
//@limit The maximum number of users to be returned; must be positive and can't be greater than 50. Fewer users may be returned than specified by the limit, even if the end of the voter list has not been reached //@limit The maximum number of users to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned users is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached
getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = Users; getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = Users;
//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set //@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set
@ -4435,7 +4440,7 @@ openMessageContent chat_id:int53 message_id:int53 = Ok;
//@description Returns information about the type of an internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link //@description Returns information about the type of an internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link
getInternalLinkType link:string = InternalLinkType; getInternalLinkType link:string = InternalLinkType;
//@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if link preview is disabled in secret chats @link The link //@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if web page preview is disabled in secret chats @link The link
getExternalLinkInfo link:string = LoginUrlInfo; getExternalLinkInfo link:string = LoginUrlInfo;
//@description Returns an HTTP URL which can be used to automatically authorize the current user on a website after clicking an HTTP link. Use the method getExternalLinkInfo to find whether a prior user confirmation is needed //@description Returns an HTTP URL which can be used to automatically authorize the current user on a website after clicking an HTTP link. Use the method getExternalLinkInfo to find whether a prior user confirmation is needed
@ -4945,9 +4950,9 @@ getInstalledStickerSets is_masks:Bool = StickerSets;
//@description Returns a list of archived sticker sets @is_masks Pass true to return mask stickers sets; pass false to return ordinary sticker sets @offset_sticker_set_id Identifier of the sticker set from which to return the result @limit The maximum number of sticker sets to return //@description Returns a list of archived sticker sets @is_masks Pass true to return mask stickers sets; pass false to return ordinary sticker sets @offset_sticker_set_id Identifier of the sticker set from which to return the result @limit The maximum number of sticker sets to return
getArchivedStickerSets is_masks:Bool offset_sticker_set_id:int64 limit:int32 = StickerSets; getArchivedStickerSets is_masks:Bool offset_sticker_set_id:int64 limit:int32 = StickerSets;
//@description Returns a list of trending sticker sets. For the optimal performance the number of returned sticker sets is chosen by the library //@description Returns a list of trending sticker sets. For optimal performance, the number of returned sticker sets is chosen by TDLib
//@offset The offset from which to return the sticker sets; must be non-negative //@offset The offset from which to return the sticker sets; must be non-negative
//@limit The maximum number of sticker sets to be returned; must be non-negative. Fewer sticker sets may be returned than specified by the limit, even if the end of the list has not been reached //@limit The maximum number of sticker sets to be returned; must be non-negative. For optimal performance, the number of returned sticker sets is chosen by TDLib and can be smaller than the specified limit, even if the end of the list has not been reached
getTrendingStickerSets offset:int32 limit:int32 = StickerSets; getTrendingStickerSets offset:int32 limit:int32 = StickerSets;
//@description Returns a list of sticker sets attached to a file. Currently only photos and videos can have attached sticker sets @file_id File identifier //@description Returns a list of sticker sets attached to a file. Currently only photos and videos can have attached sticker sets @file_id File identifier

View File

@ -1485,7 +1485,8 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
for (auto &key_value : static_cast<telegram_api::jsonObject *>(config.get())->value_) { for (auto &key_value : static_cast<telegram_api::jsonObject *>(config.get())->value_) {
Slice key = key_value->key_; Slice key = key_value->key_;
telegram_api::JSONValue *value = key_value->value_.get(); telegram_api::JSONValue *value = key_value->value_.get();
if (key == "test" || key == "wallet_enabled" || key == "wallet_blockchain_name" || key == "wallet_config") { if (key == "test" || key == "wallet_enabled" || key == "wallet_blockchain_name" || key == "wallet_config" ||
key == "stickers_emoji_cache_time") {
continue; continue;
} }
if (key == "ignore_restriction_reasons") { if (key == "ignore_restriction_reasons") {

View File

@ -9540,7 +9540,9 @@ void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, s
Dependencies dependencies; Dependencies dependencies;
dependencies.channel_ids.insert(channel_id); dependencies.channel_ids.insert(channel_id);
add_dialog_and_dependencies(dependencies, DialogId(channel_full->linked_channel_id)); // must not depend on the linked_dialog_id itself, because message database can be disabled
// the Dialog will be forcely created in update_channel_full
add_dialog_dependencies(dependencies, DialogId(channel_full->linked_channel_id));
dependencies.chat_ids.insert(channel_full->migrated_from_chat_id); dependencies.chat_ids.insert(channel_full->migrated_from_chat_id);
dependencies.user_ids.insert(channel_full->bot_user_ids.begin(), channel_full->bot_user_ids.end()); dependencies.user_ids.insert(channel_full->bot_user_ids.begin(), channel_full->bot_user_ids.end());
dependencies.user_ids.insert(channel_full->invite_link.get_creator_user_id()); dependencies.user_ids.insert(channel_full->invite_link.get_creator_user_id());

View File

@ -240,8 +240,9 @@ void CountryInfoManager::do_get_phone_number_info(string phone_number_prefix, st
result += pattern[current_pattern_pos++]; result += pattern[current_pattern_pos++];
} }
if (current_pattern_pos == pattern.size()) { if (current_pattern_pos == pattern.size()) {
result += c; result += ' ';
} else if (pattern[current_pattern_pos] == 'X') { }
if (current_pattern_pos >= pattern.size() || pattern[current_pattern_pos] == 'X') {
result += c; result += c;
current_pattern_pos++; current_pattern_pos++;
} else { } else {

View File

@ -45,13 +45,13 @@ unique_ptr<DraftMessage> get_draft_message(ContactsManager *contacts_manager,
} }
auto entities = get_message_entities(contacts_manager, std::move(draft->entities_), "draftMessage"); auto entities = get_message_entities(contacts_manager, std::move(draft->entities_), "draftMessage");
auto status = fix_formatted_text(draft->message_, entities, true, true, true, true); auto status = fix_formatted_text(draft->message_, entities, true, true, true, true, true);
if (status.is_error()) { if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft->message_; LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft->message_;
if (!clean_input_string(draft->message_)) { if (!clean_input_string(draft->message_)) {
draft->message_.clear(); draft->message_.clear();
} }
entities = find_entities(draft->message_, false); entities = find_entities(draft->message_, false, true);
} }
result->input_message_text.text = FormattedText{std::move(draft->message_), std::move(entities)}; result->input_message_text.text = FormattedText{std::move(draft->message_), std::move(entities)};
result->input_message_text.disable_web_page_preview = (flags & telegram_api::draftMessage::NO_WEBPAGE_MASK) != 0; result->input_message_text.disable_web_page_preview = (flags & telegram_api::draftMessage::NO_WEBPAGE_MASK) != 0;

View File

@ -93,7 +93,7 @@ const FormattedText &Game::get_text() const {
tl_object_ptr<td_api::game> Game::get_game_object(Td *td, bool skip_bot_commands) const { tl_object_ptr<td_api::game> Game::get_game_object(Td *td, bool skip_bot_commands) const {
return make_tl_object<td_api::game>( return make_tl_object<td_api::game>(
id_, short_name_, title_, get_formatted_text_object(text_, skip_bot_commands), description_, id_, short_name_, title_, get_formatted_text_object(text_, skip_bot_commands, -1), description_,
get_photo_object(td->file_manager_.get(), photo_), get_photo_object(td->file_manager_.get(), photo_),
td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object")); td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object"));
} }

View File

@ -39,22 +39,24 @@ Result<InputMessageText> process_input_message_text(const ContactsManager *conta
} }
TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(input_message_text->text_->entities_))); TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(input_message_text->text_->entities_)));
auto need_skip_commands = need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot); auto need_skip_bot_commands = need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot);
bool parse_markdown = G()->shared_config().get_option_boolean("always_parse_markdown"); bool parse_markdown = G()->shared_config().get_option_boolean("always_parse_markdown");
TRY_STATUS(fix_formatted_text(input_message_text->text_->text_, entities, for_draft, parse_markdown, TRY_STATUS(fix_formatted_text(input_message_text->text_->text_, entities, for_draft, parse_markdown,
need_skip_commands, for_draft)); need_skip_bot_commands, is_bot || for_draft || parse_markdown, for_draft));
InputMessageText result{FormattedText{std::move(input_message_text->text_->text_), std::move(entities)}, InputMessageText result{FormattedText{std::move(input_message_text->text_->text_), std::move(entities)},
input_message_text->disable_web_page_preview_, input_message_text->clear_draft_}; input_message_text->disable_web_page_preview_, input_message_text->clear_draft_};
if (G()->shared_config().get_option_boolean("always_parse_markdown")) { if (parse_markdown) {
result.text = parse_markdown_v3(std::move(result.text)); result.text = parse_markdown_v3(std::move(result.text));
fix_formatted_text(result.text.text, result.text.entities, for_draft, false, need_skip_commands, for_draft) fix_formatted_text(result.text.text, result.text.entities, for_draft, false, need_skip_bot_commands,
is_bot || for_draft, for_draft)
.ensure(); .ensure();
} }
return std::move(result); return std::move(result);
} }
// used only for draft
td_api::object_ptr<td_api::inputMessageText> get_input_message_text_object(const InputMessageText &input_message_text) { td_api::object_ptr<td_api::inputMessageText> get_input_message_text_object(const InputMessageText &input_message_text) {
return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text, false), return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text, false, -1),
input_message_text.disable_web_page_preview, input_message_text.disable_web_page_preview,
input_message_text.clear_draft); input_message_text.clear_draft);
} }

View File

@ -16,6 +16,7 @@
#include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h" #include "td/telegram/MessageId.h"
#include "td/telegram/MessagesManager.h" #include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
#include "td/telegram/ServerMessageId.h" #include "td/telegram/ServerMessageId.h"
#include "td/telegram/Td.h" #include "td/telegram/Td.h"
#include "td/telegram/TdDb.h" #include "td/telegram/TdDb.h"
@ -191,7 +192,7 @@ class LinkManager::InternalLinkMessageDraft final : public InternalLink {
bool contains_link_ = false; bool contains_link_ = false;
td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final { td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true), return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true, -1),
contains_link_); contains_link_);
} }
@ -329,6 +330,55 @@ class LinkManager::InternalLinkVoiceChat final : public InternalLink {
} }
}; };
class GetDeepLinkInfoQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::deepLinkInfo>> promise_;
public:
explicit GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise)
: promise_(std::move(promise)) {
}
void send(Slice link) {
send_query(G()->net_query_creator().create_unauth(telegram_api::help_getDeepLinkInfo(link.str())));
}
void on_result(uint64 id, BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_getDeepLinkInfo>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
switch (result->get_id()) {
case telegram_api::help_deepLinkInfoEmpty::ID:
return promise_.set_value(nullptr);
case telegram_api::help_deepLinkInfo::ID: {
auto info = telegram_api::move_object_as<telegram_api::help_deepLinkInfo>(result);
bool need_update = (info->flags_ & telegram_api::help_deepLinkInfo::UPDATE_APP_MASK) != 0;
auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery");
auto status = fix_formatted_text(info->message_, entities, true, true, true, true, true);
if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_;
if (!clean_input_string(info->message_)) {
info->message_.clear();
}
entities = find_entities(info->message_, true, true);
}
FormattedText text{std::move(info->message_), std::move(entities)};
return promise_.set_value(
td_api::make_object<td_api::deepLinkInfo>(get_formatted_text_object(text, true, -1), need_update));
}
default:
UNREACHABLE();
}
}
void on_error(uint64 id, Status status) final {
promise_.set_error(std::move(status));
}
};
class RequestUrlAuthQuery final : public Td::ResultHandler { class RequestUrlAuthQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> promise_; Promise<td_api::object_ptr<td_api::LoginUrlInfo>> promise_;
string url_; string url_;
@ -955,7 +1005,7 @@ unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_message_dra
} else { } else {
full_text.text = url.str(); full_text.text = url.str();
} }
if (fix_formatted_text(full_text.text, full_text.entities, false, false, false, true).is_error()) { if (fix_formatted_text(full_text.text, full_text.entities, false, false, false, true, true).is_error()) {
return nullptr; return nullptr;
} }
if (full_text.text[0] == '@') { if (full_text.text[0] == '@') {
@ -1008,6 +1058,22 @@ void LinkManager::update_autologin_domains(string autologin_token, vector<string
} }
} }
void LinkManager::get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise) {
Slice link_scheme("tg:");
if (begins_with(link, link_scheme)) {
link.remove_prefix(link_scheme.size());
if (begins_with(link, "//")) {
link.remove_prefix(2);
}
}
size_t pos = 0;
while (pos < link.size() && link[pos] != '/' && link[pos] != '?' && link[pos] != '#') {
pos++;
}
link.truncate(pos);
td_->create_handler<GetDeepLinkInfoQuery>(std::move(promise))->send(link);
}
void LinkManager::get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) { void LinkManager::get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
auto default_result = td_api::make_object<td_api::loginUrlInfoOpen>(link, false); auto default_result = td_api::make_object<td_api::loginUrlInfoOpen>(link, false);
if (G()->close_flag()) { if (G()->close_flag()) {

View File

@ -54,6 +54,8 @@ class LinkManager final : public Actor {
void update_autologin_domains(string autologin_token, vector<string> autologin_domains, void update_autologin_domains(string autologin_token, vector<string> autologin_domains,
vector<string> url_auth_domains); vector<string> url_auth_domains);
void get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise);
void get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise); void get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_login_url_info(FullMessageId full_message_id, int32 button_id, void get_login_url_info(FullMessageId full_message_id, int32 button_id,

View File

@ -1030,7 +1030,7 @@ static void parse_caption(FormattedText &caption, ParserT &parser) {
if (!check_utf8(caption.text)) { if (!check_utf8(caption.text)) {
caption.text.clear(); caption.text.clear();
} }
caption.entities = find_entities(caption.text, false); caption.entities = find_entities(caption.text, false, true);
} }
} }
@ -1461,7 +1461,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id,
auto inline_message = move_tl_object_as<telegram_api::botInlineMessageText>(bot_inline_message); auto inline_message = move_tl_object_as<telegram_api::botInlineMessageText>(bot_inline_message);
auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_), auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_),
"botInlineMessageText"); "botInlineMessageText");
auto status = fix_formatted_text(inline_message->message_, entities, false, true, true, false); auto status = fix_formatted_text(inline_message->message_, entities, false, true, true, false, false);
if (status.is_error()) { if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText " << inline_message->message_; LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText " << inline_message->message_;
break; break;
@ -1525,7 +1525,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id,
auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaAuto>(bot_inline_message); auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaAuto>(bot_inline_message);
auto caption = auto caption =
get_message_text(td->contacts_manager_.get(), inline_message->message_, std::move(inline_message->entities_), get_message_text(td->contacts_manager_.get(), inline_message->message_, std::move(inline_message->entities_),
true, 0, false, "create_inline_message_content"); true, false, 0, false, "create_inline_message_content");
if (allowed_media_content_id == td_api::inputMessageAnimation::ID) { if (allowed_media_content_id == td_api::inputMessageAnimation::ID) {
result.message_content = make_unique<MessageAnimation>(file_id, std::move(caption)); result.message_content = make_unique<MessageAnimation>(file_id, std::move(caption));
} else if (allowed_media_content_id == td_api::inputMessageAudio::ID) { } else if (allowed_media_content_id == td_api::inputMessageAudio::ID) {
@ -2911,12 +2911,14 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
case MessageContentType::Text: { case MessageContentType::Text: {
auto old_ = static_cast<const MessageText *>(old_content); auto old_ = static_cast<const MessageText *>(old_content);
auto new_ = static_cast<const MessageText *>(new_content); auto new_ = static_cast<const MessageText *>(new_content);
auto get_content_object = [td, dialog_id](const MessageContent *content) {
return to_string(
get_message_content_object(content, td, dialog_id, -1, false, false, std::numeric_limits<int32>::max()));
};
if (old_->text.text != new_->text.text) { if (old_->text.text != new_->text.text) {
if (need_message_changed_warning && need_message_text_changed_warning(old_, new_)) { if (need_message_changed_warning && need_message_text_changed_warning(old_, new_)) {
LOG(ERROR) << "Message text has changed from " LOG(ERROR) << "Message text has changed in " << get_content_object(old_content) << ". New content is "
<< to_string(get_message_content_object(old_content, td, dialog_id, -1, false, false)) << get_content_object(new_content);
<< ". New content is "
<< to_string(get_message_content_object(new_content, td, dialog_id, -1, false, false));
} }
need_update = true; need_update = true;
} }
@ -2925,10 +2927,8 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
if (need_message_changed_warning && need_message_text_changed_warning(old_, new_) && if (need_message_changed_warning && need_message_text_changed_warning(old_, new_) &&
old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT && old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT &&
need_message_entities_changed_warning(old_->text.entities, new_->text.entities)) { need_message_entities_changed_warning(old_->text.entities, new_->text.entities)) {
LOG(WARNING) << "Entities has changed from " LOG(WARNING) << "Entities has changed in " << get_content_object(old_content) << ". New content is "
<< to_string(get_message_content_object(old_content, td, dialog_id, -1, false, false)) << get_content_object(new_content);
<< ". New content is "
<< to_string(get_message_content_object(new_content, td, dialog_id, -1, false, false));
} }
need_update = true; need_update = true;
} }
@ -3825,14 +3825,14 @@ unique_ptr<MessageContent> get_secret_message_content(
} }
auto entities = get_message_entities(std::move(secret_entities)); auto entities = get_message_entities(std::move(secret_entities));
auto status = fix_formatted_text(message_text, entities, true, false, true, false); auto status = fix_formatted_text(message_text, entities, true, false, true, td->auth_manager_->is_bot(), false);
if (status.is_error()) { if (status.is_error()) {
LOG(WARNING) << "Receive error " << status << " while parsing secret message \"" << message_text LOG(WARNING) << "Receive error " << status << " while parsing secret message \"" << message_text
<< "\" with entities " << format::as_array(entities); << "\" with entities " << format::as_array(entities);
if (!clean_input_string(message_text)) { if (!clean_input_string(message_text)) {
message_text.clear(); message_text.clear();
} }
entities = find_entities(message_text, true); entities = find_entities(message_text, true, td->auth_manager_->is_bot());
} }
// support of old layer and old constructions // support of old layer and old constructions
@ -4649,19 +4649,21 @@ unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td, tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td,
DialogId dialog_id, int32 message_date, DialogId dialog_id, int32 message_date,
bool is_content_secret, bool skip_bot_commands) { bool is_content_secret, bool skip_bot_commands,
int32 max_media_timestamp) {
CHECK(content != nullptr); CHECK(content != nullptr);
switch (content->get_type()) { switch (content->get_type()) {
case MessageContentType::Animation: { case MessageContentType::Animation: {
const MessageAnimation *m = static_cast<const MessageAnimation *>(content); const MessageAnimation *m = static_cast<const MessageAnimation *>(content);
return make_tl_object<td_api::messageAnimation>( return make_tl_object<td_api::messageAnimation>(
td->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"), td->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"),
get_formatted_text_object(m->caption, skip_bot_commands), is_content_secret); get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
} }
case MessageContentType::Audio: { case MessageContentType::Audio: {
const MessageAudio *m = static_cast<const MessageAudio *>(content); const MessageAudio *m = static_cast<const MessageAudio *>(content);
return make_tl_object<td_api::messageAudio>(td->audios_manager_->get_audio_object(m->file_id), return make_tl_object<td_api::messageAudio>(
get_formatted_text_object(m->caption, skip_bot_commands)); td->audios_manager_->get_audio_object(m->file_id),
get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp));
} }
case MessageContentType::Contact: { case MessageContentType::Contact: {
const MessageContact *m = static_cast<const MessageContact *>(content); const MessageContact *m = static_cast<const MessageContact *>(content);
@ -4671,7 +4673,7 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
const MessageDocument *m = static_cast<const MessageDocument *>(content); const MessageDocument *m = static_cast<const MessageDocument *>(content);
return make_tl_object<td_api::messageDocument>( return make_tl_object<td_api::messageDocument>(
td->documents_manager_->get_document_object(m->file_id, PhotoFormat::Jpeg), td->documents_manager_->get_document_object(m->file_id, PhotoFormat::Jpeg),
get_formatted_text_object(m->caption, skip_bot_commands)); get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp));
} }
case MessageContentType::Game: { case MessageContentType::Game: {
const MessageGame *m = static_cast<const MessageGame *>(content); const MessageGame *m = static_cast<const MessageGame *>(content);
@ -4696,9 +4698,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
} }
case MessageContentType::Photo: { case MessageContentType::Photo: {
const MessagePhoto *m = static_cast<const MessagePhoto *>(content); const MessagePhoto *m = static_cast<const MessagePhoto *>(content);
return make_tl_object<td_api::messagePhoto>(get_photo_object(td->file_manager_.get(), m->photo), return make_tl_object<td_api::messagePhoto>(
get_formatted_text_object(m->caption, skip_bot_commands), get_photo_object(td->file_manager_.get(), m->photo),
is_content_secret); get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
} }
case MessageContentType::Sticker: { case MessageContentType::Sticker: {
const MessageSticker *m = static_cast<const MessageSticker *>(content); const MessageSticker *m = static_cast<const MessageSticker *>(content);
@ -4706,8 +4708,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
} }
case MessageContentType::Text: { case MessageContentType::Text: {
const MessageText *m = static_cast<const MessageText *>(content); const MessageText *m = static_cast<const MessageText *>(content);
return make_tl_object<td_api::messageText>(get_formatted_text_object(m->text, skip_bot_commands), return make_tl_object<td_api::messageText>(
td->web_pages_manager_->get_web_page_object(m->web_page_id)); get_formatted_text_object(m->text, skip_bot_commands, max_media_timestamp),
td->web_pages_manager_->get_web_page_object(m->web_page_id));
} }
case MessageContentType::Unsupported: case MessageContentType::Unsupported:
return make_tl_object<td_api::messageUnsupported>(); return make_tl_object<td_api::messageUnsupported>();
@ -4717,9 +4720,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
} }
case MessageContentType::Video: { case MessageContentType::Video: {
const MessageVideo *m = static_cast<const MessageVideo *>(content); const MessageVideo *m = static_cast<const MessageVideo *>(content);
return make_tl_object<td_api::messageVideo>(td->videos_manager_->get_video_object(m->file_id), return make_tl_object<td_api::messageVideo>(
get_formatted_text_object(m->caption, skip_bot_commands), td->videos_manager_->get_video_object(m->file_id),
is_content_secret); get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
} }
case MessageContentType::VideoNote: { case MessageContentType::VideoNote: {
const MessageVideoNote *m = static_cast<const MessageVideoNote *>(content); const MessageVideoNote *m = static_cast<const MessageVideoNote *>(content);
@ -4728,9 +4731,9 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
} }
case MessageContentType::VoiceNote: { case MessageContentType::VoiceNote: {
const MessageVoiceNote *m = static_cast<const MessageVoiceNote *>(content); const MessageVoiceNote *m = static_cast<const MessageVoiceNote *>(content);
return make_tl_object<td_api::messageVoiceNote>(td->voice_notes_manager_->get_voice_note_object(m->file_id), return make_tl_object<td_api::messageVoiceNote>(
get_formatted_text_object(m->caption, skip_bot_commands), td->voice_notes_manager_->get_voice_note_object(m->file_id),
m->is_listened); get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), m->is_listened);
} }
case MessageContentType::ChatCreate: { case MessageContentType::ChatCreate: {
const MessageChatCreate *m = static_cast<const MessageChatCreate *>(content); const MessageChatCreate *m = static_cast<const MessageChatCreate *>(content);
@ -4879,6 +4882,10 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
return nullptr; return nullptr;
} }
FormattedText *get_message_content_text_mutable(MessageContent *content) {
return const_cast<FormattedText *>(get_message_content_text(content));
}
const FormattedText *get_message_content_text(const MessageContent *content) { const FormattedText *get_message_content_text(const MessageContent *content) {
switch (content->get_type()) { switch (content->get_type()) {
case MessageContentType::Text: case MessageContentType::Text:
@ -4920,10 +4927,6 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td)
auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id; auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
return td->audios_manager_->get_audio_duration(audio_file_id); return td->audios_manager_->get_audio_duration(audio_file_id);
} }
case MessageContentType::Text: {
auto web_page_id = static_cast<const MessageText *>(content)->web_page_id;
return td->web_pages_manager_->get_web_page_duration(web_page_id);
}
case MessageContentType::Video: { case MessageContentType::Video: {
auto video_file_id = static_cast<const MessageVideo *>(content)->file_id; auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
return td->videos_manager_->get_video_duration(video_file_id); return td->videos_manager_->get_video_duration(video_file_id);
@ -4941,6 +4944,34 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td)
} }
} }
int32 get_message_content_media_duration(const MessageContent *content, const Td *td) {
CHECK(content != nullptr);
switch (content->get_type()) {
case MessageContentType::Audio: {
auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
return td->audios_manager_->get_audio_duration(audio_file_id);
}
case MessageContentType::Text: {
auto web_page_id = static_cast<const MessageText *>(content)->web_page_id;
return td->web_pages_manager_->get_web_page_media_duration(web_page_id);
}
case MessageContentType::Video: {
auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
return td->videos_manager_->get_video_duration(video_file_id);
}
case MessageContentType::VideoNote: {
auto video_note_file_id = static_cast<const MessageVideoNote *>(content)->file_id;
return td->video_notes_manager_->get_video_note_duration(video_note_file_id);
}
case MessageContentType::VoiceNote: {
auto voice_file_id = static_cast<const MessageVoiceNote *>(content)->file_id;
return td->voice_notes_manager_->get_voice_note_duration(voice_file_id);
}
default:
return -1;
}
}
FileId get_message_content_upload_file_id(const MessageContent *content) { FileId get_message_content_upload_file_id(const MessageContent *content) {
switch (content->get_type()) { switch (content->get_type()) {
case MessageContentType::Animation: case MessageContentType::Animation:

View File

@ -198,7 +198,10 @@ unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td, tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td,
DialogId dialog_id, int32 message_date, DialogId dialog_id, int32 message_date,
bool is_content_secret, bool skip_bot_commands); bool is_content_secret, bool skip_bot_commands,
int32 max_media_timestamp);
FormattedText *get_message_content_text_mutable(MessageContent *content);
const FormattedText *get_message_content_text(const MessageContent *content); const FormattedText *get_message_content_text(const MessageContent *content);
@ -206,6 +209,8 @@ const FormattedText *get_message_content_caption(const MessageContent *content);
int32 get_message_content_duration(const MessageContent *content, const Td *td); int32 get_message_content_duration(const MessageContent *content, const Td *td);
int32 get_message_content_media_duration(const MessageContent *content, const Td *td);
FileId get_message_content_upload_file_id(const MessageContent *content); FileId get_message_content_upload_file_id(const MessageContent *content);
FileId get_message_content_any_file_id(const MessageContent *content); FileId get_message_content_any_file_id(const MessageContent *content);

View File

@ -29,7 +29,7 @@
namespace td { namespace td {
int MessageEntity::get_type_priority(Type type) { 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 const int types[] = {50, 50, 50, 50, 50, 90, 91, 20, 11, 10, 49, 49, 50, 50, 92, 93, 0, 50, 50};
static_assert(sizeof(types) / sizeof(types[0]) == static_cast<size_t>(MessageEntity::Type::Size), ""); static_assert(sizeof(types) / sizeof(types[0]) == static_cast<size_t>(MessageEntity::Type::Size), "");
return types[static_cast<int32>(type)]; return types[static_cast<int32>(type)];
} }
@ -72,6 +72,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Ty
return string_builder << "PhoneNumber"; return string_builder << "PhoneNumber";
case MessageEntity::Type::BankCardNumber: case MessageEntity::Type::BankCardNumber:
return string_builder << "BankCardNumber"; return string_builder << "BankCardNumber";
case MessageEntity::Type::MediaTimestamp:
return string_builder << "MediaTimestamp";
default: default:
UNREACHABLE(); UNREACHABLE();
return string_builder << "Impossible"; return string_builder << "Impossible";
@ -81,6 +83,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Ty
StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity) { StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity) {
string_builder << '[' << message_entity.type << ", offset = " << message_entity.offset string_builder << '[' << message_entity.type << ", offset = " << message_entity.offset
<< ", length = " << message_entity.length; << ", length = " << message_entity.length;
if (message_entity.media_timestamp >= 0) {
string_builder << ", media_timestamp = \"" << message_entity.media_timestamp << "\"";
}
if (!message_entity.argument.empty()) { if (!message_entity.argument.empty()) {
string_builder << ", argument = \"" << message_entity.argument << "\""; string_builder << ", argument = \"" << message_entity.argument << "\"";
} }
@ -130,6 +135,8 @@ tl_object_ptr<td_api::TextEntityType> MessageEntity::get_text_entity_type_object
return make_tl_object<td_api::textEntityTypePhoneNumber>(); return make_tl_object<td_api::textEntityTypePhoneNumber>();
case MessageEntity::Type::BankCardNumber: case MessageEntity::Type::BankCardNumber:
return make_tl_object<td_api::textEntityTypeBankCardNumber>(); return make_tl_object<td_api::textEntityTypeBankCardNumber>();
case MessageEntity::Type::MediaTimestamp:
return make_tl_object<td_api::textEntityTypeMediaTimestamp>(media_timestamp);
default: default:
UNREACHABLE(); UNREACHABLE();
return nullptr; return nullptr;
@ -141,7 +148,7 @@ tl_object_ptr<td_api::textEntity> MessageEntity::get_text_entity_object() const
} }
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities, vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
bool skip_bot_commands) { bool skip_bot_commands, int32 max_media_timestamp) {
vector<tl_object_ptr<td_api::textEntity>> result; vector<tl_object_ptr<td_api::textEntity>> result;
result.reserve(entities.size()); result.reserve(entities.size());
@ -149,6 +156,9 @@ vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<
if (skip_bot_commands && entity.type == MessageEntity::Type::BotCommand) { if (skip_bot_commands && entity.type == MessageEntity::Type::BotCommand) {
continue; continue;
} }
if (entity.type == MessageEntity::Type::MediaTimestamp && max_media_timestamp < entity.media_timestamp) {
continue;
}
auto entity_object = entity.get_text_entity_object(); auto entity_object = entity.get_text_entity_object();
if (entity_object->type_ != nullptr) { if (entity_object->type_ != nullptr) {
result.push_back(std::move(entity_object)); result.push_back(std::move(entity_object));
@ -162,9 +172,10 @@ StringBuilder &operator<<(StringBuilder &string_builder, const FormattedText &te
return string_builder << '"' << text.text << "\" with entities " << text.entities; return string_builder << '"' << text.text << "\" with entities " << text.entities;
} }
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands) { td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands,
return td_api::make_object<td_api::formattedText>(text.text, int32 max_media_timestamp) {
get_text_entities_object(text.entities, skip_bot_commands)); return td_api::make_object<td_api::formattedText>(
text.text, get_text_entities_object(text.entities, skip_bot_commands, max_media_timestamp));
} }
static bool is_word_character(uint32 code) { static bool is_word_character(uint32 code) {
@ -430,6 +441,57 @@ static vector<Slice> match_cashtags(Slice str) {
return result; return result;
} }
static vector<Slice> match_media_timestamps(Slice str) {
vector<Slice> result;
const unsigned char *begin = str.ubegin();
const unsigned char *end = str.uend();
const unsigned char *ptr = begin;
while (true) {
ptr = static_cast<const unsigned char *>(std::memchr(ptr, ':', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
auto media_timestamp_begin = ptr;
while (media_timestamp_begin != begin &&
(media_timestamp_begin[-1] == ':' || is_digit(media_timestamp_begin[-1]))) {
media_timestamp_begin--;
}
auto media_timestamp_end = ptr;
while (media_timestamp_end + 1 != end && (media_timestamp_end[1] == ':' || is_digit(media_timestamp_end[1]))) {
media_timestamp_end++;
}
media_timestamp_end++;
if (media_timestamp_begin != ptr && media_timestamp_end != ptr + 1 && is_digit(ptr[1])) {
ptr = media_timestamp_end;
if (media_timestamp_begin != begin) {
uint32 prev;
next_utf8_unsafe(prev_utf8_unsafe(media_timestamp_begin), &prev, "match_media_timestamps 1");
if (is_word_character(prev)) {
continue;
}
}
if (media_timestamp_end != end) {
uint32 next;
next_utf8_unsafe(media_timestamp_end, &next, "match_media_timestamps 2");
if (is_word_character(next)) {
continue;
}
}
result.emplace_back(media_timestamp_begin, media_timestamp_end);
} else {
ptr = media_timestamp_end;
}
}
return result;
}
static vector<Slice> match_bank_card_numbers(Slice str) { static vector<Slice> match_bank_card_numbers(Slice str) {
vector<Slice> result; vector<Slice> result;
const unsigned char *begin = str.ubegin(); const unsigned char *begin = str.ubegin();
@ -1247,6 +1309,42 @@ vector<std::pair<Slice, bool>> find_urls(Slice str) {
return result; return result;
} }
vector<std::pair<Slice, int32>> find_media_timestamps(Slice str) {
vector<std::pair<Slice, int32>> result;
for (auto media_timestamp : match_media_timestamps(str)) {
vector<Slice> parts = full_split(media_timestamp, ':');
CHECK(parts.size() >= 2);
if (parts.size() > 3 || parts.back().size() != 2) {
continue;
}
auto seconds = to_integer<int32>(parts.back());
if (seconds >= 60) {
continue;
}
if (parts.size() == 2) {
if (parts[0].size() > 4 || parts[0].empty()) {
continue;
}
auto minutes = to_integer<int32>(parts[0]);
result.emplace_back(media_timestamp, minutes * 60 + seconds);
continue;
} else {
if (parts[0].size() > 2 || parts[1].size() > 2 || parts[0].empty() || parts[1].empty()) {
continue;
}
auto minutes = to_integer<int32>(parts[1]);
if (minutes >= 60) {
continue;
}
auto hours = to_integer<int32>(parts[0]);
result.emplace_back(media_timestamp, hours * 3600 + minutes * 60 + seconds);
}
}
return result;
}
static int32 text_length(Slice text) { static int32 text_length(Slice text) {
return narrow_cast<int32>(utf8_utf16_length(text)); return narrow_cast<int32>(utf8_utf16_length(text));
} }
@ -1291,7 +1389,8 @@ static constexpr int32 get_continuous_entities_mask() {
get_entity_type_mask(MessageEntity::Type::EmailAddress) | get_entity_type_mask(MessageEntity::Type::TextUrl) | get_entity_type_mask(MessageEntity::Type::EmailAddress) | get_entity_type_mask(MessageEntity::Type::TextUrl) |
get_entity_type_mask(MessageEntity::Type::MentionName) | get_entity_type_mask(MessageEntity::Type::Cashtag) | get_entity_type_mask(MessageEntity::Type::MentionName) | get_entity_type_mask(MessageEntity::Type::Cashtag) |
get_entity_type_mask(MessageEntity::Type::PhoneNumber) | get_entity_type_mask(MessageEntity::Type::PhoneNumber) |
get_entity_type_mask(MessageEntity::Type::BankCardNumber); get_entity_type_mask(MessageEntity::Type::BankCardNumber) |
get_entity_type_mask(MessageEntity::Type::MediaTimestamp);
} }
static constexpr int32 get_pre_entities_mask() { static constexpr int32 get_pre_entities_mask() {
@ -1440,7 +1539,7 @@ static void remove_entities_intersecting_blockquote(vector<MessageEntity> &entit
while (blockquote_it != blockquote_entities.end() && while (blockquote_it != blockquote_entities.end() &&
(blockquote_it->type != MessageEntity::Type::BlockQuote || (blockquote_it->type != MessageEntity::Type::BlockQuote ||
blockquote_it->offset + blockquote_it->length <= entities[i].offset)) { blockquote_it->offset + blockquote_it->length <= entities[i].offset)) {
blockquote_it++; ++blockquote_it;
} }
if (blockquote_it != blockquote_entities.end() && if (blockquote_it != blockquote_entities.end() &&
(blockquote_it->offset + blockquote_it->length < entities[i].offset + entities[i].length || (blockquote_it->offset + blockquote_it->length < entities[i].offset + entities[i].length ||
@ -1456,7 +1555,52 @@ static void remove_entities_intersecting_blockquote(vector<MessageEntity> &entit
entities.erase(entities.begin() + left_entities, entities.end()); entities.erase(entities.begin() + left_entities, entities.end());
} }
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands) { // keeps only non-intersecting entities
// fixes entity offsets from UTF-8 to UTF-16 offsets
static void fix_entity_offsets(Slice text, vector<MessageEntity> &entities) {
if (entities.empty()) {
return;
}
sort_entities(entities);
remove_intersecting_entities(entities);
const unsigned char *begin = text.ubegin();
const unsigned char *ptr = begin;
const unsigned char *end = text.uend();
int32 utf16_pos = 0;
for (auto &entity : entities) {
int cnt = 2;
auto entity_begin = entity.offset;
auto entity_end = entity.offset + entity.length;
int32 pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
}
while (ptr != end && cnt > 0) {
unsigned char c = ptr[0];
utf16_pos += 1 + (c >= 0xf0);
ptr = next_utf8_unsafe(ptr, nullptr, "fix_entity_offsets");
pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
} else if (entity_end == pos) {
cnt--;
entity.length = utf16_pos - entity.offset;
}
}
CHECK(cnt == 0);
}
}
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps) {
vector<MessageEntity> entities; vector<MessageEntity> entities;
auto add_entities = [&entities, &text](MessageEntity::Type type, vector<Slice> (*find_entities_f)(Slice)) mutable { auto add_entities = [&entities, &text](MessageEntity::Type type, vector<Slice> (*find_entities_f)(Slice)) mutable {
@ -1485,48 +1629,32 @@ vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands) {
entities.emplace_back(type, offset, length); entities.emplace_back(type, offset, length);
} }
if (entities.empty()) { if (!skip_media_timestamps) {
return entities; auto media_timestamps = find_media_timestamps(text);
for (auto &entity : media_timestamps) {
auto offset = narrow_cast<int32>(entity.first.begin() - text.begin());
auto length = narrow_cast<int32>(entity.first.size());
entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity.second);
}
} }
sort_entities(entities); fix_entity_offsets(text, entities);
remove_intersecting_entities(entities); return entities;
}
// fix offsets to UTF-16 offsets static vector<MessageEntity> find_media_timestamp_entities(Slice text) {
const unsigned char *begin = text.ubegin(); vector<MessageEntity> entities;
const unsigned char *ptr = begin;
const unsigned char *end = text.uend();
int32 utf16_pos = 0; auto media_timestamps = find_media_timestamps(text);
for (auto &entity : entities) { for (auto &entity : media_timestamps) {
int cnt = 2; auto offset = narrow_cast<int32>(entity.first.begin() - text.begin());
auto entity_begin = entity.offset; auto length = narrow_cast<int32>(entity.first.size());
auto entity_end = entity.offset + entity.length; entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity.second);
int32 pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
}
while (ptr != end && cnt > 0) {
unsigned char c = ptr[0];
utf16_pos += 1 + (c >= 0xf0);
ptr = next_utf8_unsafe(ptr, nullptr, "find_entities");
pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
} else if (entity_end == pos) {
cnt--;
entity.length = utf16_pos - entity.offset;
}
}
CHECK(cnt == 0);
} }
fix_entity_offsets(text, entities);
return entities; return entities;
} }
@ -1546,17 +1674,17 @@ static vector<MessageEntity> merge_entities(vector<MessageEntity> old_entities,
for (auto &old_entity : old_entities) { for (auto &old_entity : old_entities) {
while (new_it != new_end && new_it->offset + new_it->length <= old_entity.offset) { while (new_it != new_end && new_it->offset + new_it->length <= old_entity.offset) {
result.push_back(std::move(*new_it)); result.push_back(std::move(*new_it));
new_it++; ++new_it;
} }
auto old_entity_end = old_entity.offset + old_entity.length; auto old_entity_end = old_entity.offset + old_entity.length;
result.push_back(std::move(old_entity)); result.push_back(std::move(old_entity));
while (new_it != new_end && new_it->offset < old_entity_end) { while (new_it != new_end && new_it->offset < old_entity_end) {
new_it++; ++new_it;
} }
} }
while (new_it != new_end) { while (new_it != new_end) {
result.push_back(std::move(*new_it)); result.push_back(std::move(*new_it));
new_it++; ++new_it;
} }
return result; return result;
@ -1616,6 +1744,8 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
break; break;
case MessageEntity::Type::BankCardNumber: case MessageEntity::Type::BankCardNumber:
break; break;
case MessageEntity::Type::MediaTimestamp:
break;
default: default:
UNREACHABLE(); UNREACHABLE();
} }
@ -2239,7 +2369,7 @@ static vector<MessageEntity> find_splittable_entities_v3(Slice text, const vecto
} }
} }
auto found_entities = find_entities(text, false); auto found_entities = find_entities(text, false, true);
td::remove_if(found_entities, [](const auto &entity) { td::remove_if(found_entities, [](const auto &entity) {
return entity.type == MessageEntity::Type::EmailAddress || entity.type == MessageEntity::Type::Url; return entity.type == MessageEntity::Type::EmailAddress || entity.type == MessageEntity::Type::Url;
}); });
@ -3030,6 +3160,8 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
break; break;
case MessageEntity::Type::MentionName: case MessageEntity::Type::MentionName:
break; break;
case MessageEntity::Type::MediaTimestamp:
break;
default: default:
UNREACHABLE(); UNREACHABLE();
} }
@ -3121,6 +3253,15 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
entities.emplace_back(entity->offset_, entity->length_, user_id); entities.emplace_back(entity->offset_, entity->length_, user_id);
break; break;
} }
case td_api::textEntityTypeMediaTimestamp::ID: {
auto entity_media_timestamp = static_cast<td_api::textEntityTypeMediaTimestamp *>(entity->type_.get());
if (entity_media_timestamp->media_timestamp_ < 0) {
return Status::Error(400, "Invalid media timestamp specified");
}
entities.emplace_back(MessageEntity::Type::MediaTimestamp, entity->offset_, entity->length_,
entity_media_timestamp->media_timestamp_);
break;
}
default: default:
UNREACHABLE(); UNREACHABLE();
} }
@ -3759,7 +3900,7 @@ static void merge_new_entities(vector<MessageEntity> &entities, vector<MessageEn
} }
Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities, Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
bool skip_bot_commands, bool for_draft) { bool skip_bot_commands, bool skip_media_timestamps, bool for_draft) {
string result; string result;
if (entities.empty()) { if (entities.empty()) {
// fast path // fast path
@ -3867,7 +4008,9 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
} }
if (!skip_new_entities) { if (!skip_new_entities) {
merge_new_entities(entities, find_entities(text, skip_bot_commands)); merge_new_entities(entities, find_entities(text, skip_bot_commands, skip_media_timestamps));
} else if (!skip_media_timestamps) {
merge_new_entities(entities, find_media_timestamp_entities(text));
} }
// new whitespace-only entities could be added after splitting of entities // new whitespace-only entities could be added after splitting of entities
@ -3880,11 +4023,12 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text, FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities, vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
bool skip_new_entities, int32 send_date, bool from_album, const char *source) { bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album,
const char *source) {
auto entities = get_message_entities(contacts_manager, std::move(server_entities), source); auto entities = get_message_entities(contacts_manager, std::move(server_entities), source);
auto debug_message_text = message_text; auto debug_message_text = message_text;
auto debug_entities = entities; auto debug_entities = entities;
auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, false); auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, skip_media_timestamps, false);
if (status.is_error()) { if (status.is_error()) {
// message entities in media albums can be wrong because of a long time ago fixed server-side bug // message entities in media albums can be wrong because of a long time ago fixed server-side bug
if (!from_album && (send_date == 0 || send_date > 1600340000)) { // approximate fix date if (!from_album && (send_date == 0 || send_date > 1600340000)) { // approximate fix date
@ -3895,7 +4039,7 @@ FormattedText get_message_text(const ContactsManager *contacts_manager, string m
if (!clean_input_string(message_text)) { if (!clean_input_string(message_text)) {
message_text.clear(); message_text.clear();
} }
entities = find_entities(message_text, false); entities = find_entities(message_text, false, skip_media_timestamps);
} }
return FormattedText{std::move(message_text), std::move(entities)}; return FormattedText{std::move(message_text), std::move(entities)};
} }
@ -3939,7 +4083,7 @@ Result<FormattedText> process_input_caption(const ContactsManager *contacts_mana
} }
TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(caption->entities_))); TRY_RESULT(entities, get_message_entities(contacts_manager, std::move(caption->entities_)));
TRY_STATUS(fix_formatted_text(caption->text_, entities, true, false, TRY_STATUS(fix_formatted_text(caption->text_, entities, true, false,
need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot), false)); need_always_skip_bot_commands(contacts_manager, dialog_id, is_bot), is_bot, false));
return FormattedText{std::move(caption->text_), std::move(entities)}; return FormattedText{std::move(caption->text_), std::move(entities)};
} }
@ -3954,6 +4098,19 @@ void add_formatted_text_dependencies(Dependencies &dependencies, const Formatted
} }
} }
bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, int32 max_media_timestamp) {
if (text == nullptr) {
return false;
}
for (auto &entity : text->entities) {
if (entity.type == MessageEntity::Type::MediaTimestamp && min_media_timestamp <= entity.media_timestamp &&
entity.media_timestamp <= max_media_timestamp) {
return true;
}
}
return false;
}
bool has_bot_commands(const FormattedText *text) { bool has_bot_commands(const FormattedText *text) {
if (text == nullptr) { if (text == nullptr) {
return false; return false;

View File

@ -48,28 +48,34 @@ class MessageEntity {
Strikethrough, Strikethrough,
BlockQuote, BlockQuote,
BankCardNumber, BankCardNumber,
MediaTimestamp,
Size Size
}; };
Type type; Type type = Type::Size;
int32 offset; int32 offset = -1;
int32 length; int32 length = -1;
int32 media_timestamp = -1;
string argument; string argument;
UserId user_id; UserId user_id;
MessageEntity() = default; MessageEntity() = default;
MessageEntity(Type type, int32 offset, int32 length, string argument = "") MessageEntity(Type type, int32 offset, int32 length, string argument = "")
: type(type), offset(offset), length(length), argument(std::move(argument)), user_id() { : type(type), offset(offset), length(length), argument(std::move(argument)) {
} }
MessageEntity(int32 offset, int32 length, UserId user_id) MessageEntity(int32 offset, int32 length, UserId user_id)
: type(Type::MentionName), offset(offset), length(length), argument(), user_id(user_id) { : type(Type::MentionName), offset(offset), length(length), user_id(user_id) {
}
MessageEntity(Type type, int32 offset, int32 length, int32 media_timestamp)
: type(type), offset(offset), length(length), media_timestamp(media_timestamp) {
CHECK(type == Type::MediaTimestamp);
} }
tl_object_ptr<td_api::textEntity> get_text_entity_object() const; tl_object_ptr<td_api::textEntity> get_text_entity_object() const;
bool operator==(const MessageEntity &other) const { bool operator==(const MessageEntity &other) const {
return offset == other.offset && length == other.length && type == other.type && argument == other.argument && return offset == other.offset && length == other.length && type == other.type &&
user_id == other.user_id; media_timestamp == other.media_timestamp && argument == other.argument && user_id == other.user_id;
} }
bool operator<(const MessageEntity &other) const { bool operator<(const MessageEntity &other) const {
@ -132,11 +138,12 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
bool allow_all = false); bool allow_all = false);
vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities, vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
bool skip_bot_commands); bool skip_bot_commands, int32 max_media_timestamp);
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands); td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands,
int32 max_media_timestamp);
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands); vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps);
vector<Slice> find_mentions(Slice str); vector<Slice> find_mentions(Slice str);
vector<Slice> find_bot_commands(Slice str); vector<Slice> find_bot_commands(Slice str);
@ -145,7 +152,8 @@ vector<Slice> find_cashtags(Slice str);
vector<Slice> find_bank_card_numbers(Slice str); vector<Slice> find_bank_card_numbers(Slice str);
vector<Slice> find_tg_urls(Slice str); vector<Slice> find_tg_urls(Slice str);
bool is_email_address(Slice str); bool is_email_address(Slice str);
vector<std::pair<Slice, bool>> find_urls(Slice str); // slice + is_email_address vector<std::pair<Slice, bool>> find_urls(Slice str); // slice + is_email_address
vector<std::pair<Slice, int32>> find_media_timestamps(Slice str); // slice + media_timestamp
string get_first_url(Slice text, const vector<MessageEntity> &entities); string get_first_url(Slice text, const vector<MessageEntity> &entities);
@ -178,11 +186,12 @@ vector<MessageEntity> get_message_entities(vector<tl_object_ptr<secret_api::Mess
// like clean_input_string but also validates entities // like clean_input_string but also validates entities
Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities, Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
bool skip_bot_commands, bool for_draft) TD_WARN_UNUSED_RESULT; bool skip_bot_commands, bool skip_media_timestamps, bool for_draft) TD_WARN_UNUSED_RESULT;
FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text, FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities, vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
bool skip_new_entities, int32 send_date, bool from_album, const char *source); bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album,
const char *source);
td_api::object_ptr<td_api::formattedText> extract_input_caption( td_api::object_ptr<td_api::formattedText> extract_input_caption(
tl_object_ptr<td_api::InputMessageContent> &input_message_content); tl_object_ptr<td_api::InputMessageContent> &input_message_content);
@ -192,6 +201,8 @@ Result<FormattedText> process_input_caption(const ContactsManager *contacts_mana
void add_formatted_text_dependencies(Dependencies &dependencies, const FormattedText *text); void add_formatted_text_dependencies(Dependencies &dependencies, const FormattedText *text);
bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, int32 max_media_timestamp);
bool has_bot_commands(const FormattedText *text); bool has_bot_commands(const FormattedText *text);
bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot); bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot);

View File

@ -24,6 +24,9 @@ void MessageEntity::store(StorerT &storer) const {
if (type == Type::MentionName) { if (type == Type::MentionName) {
store(user_id, storer); store(user_id, storer);
} }
if (type == Type::MediaTimestamp) {
store(media_timestamp, storer);
}
} }
template <class ParserT> template <class ParserT>
@ -38,6 +41,9 @@ void MessageEntity::parse(ParserT &parser) {
if (type == Type::MentionName) { if (type == Type::MentionName) {
parse(user_id, parser); parse(user_id, parser);
} }
if (type == Type::MediaTimestamp) {
parse(media_timestamp, parser);
}
} }
template <class StorerT> template <class StorerT>

View File

@ -0,0 +1,21 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/DialogId.h"
#include "td/telegram/MessageId.h"
#include "td/utils/common.h"
namespace td {
struct MessageThreadInfo {
DialogId dialog_id;
vector<MessageId> message_ids;
};
} // namespace td

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@
#include "td/telegram/MessageId.h" #include "td/telegram/MessageId.h"
#include "td/telegram/MessageLinkInfo.h" #include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageReplyInfo.h" #include "td/telegram/MessageReplyInfo.h"
#include "td/telegram/MessageThreadInfo.h"
#include "td/telegram/MessagesDb.h" #include "td/telegram/MessagesDb.h"
#include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessageSearchFilter.h"
#include "td/telegram/MessageTtlSetting.h" #include "td/telegram/MessageTtlSetting.h"
@ -573,17 +574,13 @@ class MessagesManager final : public Actor {
void get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise, const char *source, void get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise, const char *source,
tl_object_ptr<telegram_api::InputMessage> input_message = nullptr); tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);
struct MessageThreadInfo {
DialogId dialog_id;
vector<MessageId> message_ids;
};
void get_message_thread(DialogId dialog_id, MessageId message_id, Promise<MessageThreadInfo> &&promise); void get_message_thread(DialogId dialog_id, MessageId message_id, Promise<MessageThreadInfo> &&promise);
td_api::object_ptr<td_api::messageThreadInfo> get_message_thread_info_object(const MessageThreadInfo &info); td_api::object_ptr<td_api::messageThreadInfo> get_message_thread_info_object(const MessageThreadInfo &info);
void process_discussion_message(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result, void process_discussion_message(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result,
DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id,
MessageId expected_message_id, Promise<vector<FullMessageId>> promise); MessageId expected_message_id, Promise<MessageThreadInfo> promise);
bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds); bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds);
@ -1044,6 +1041,7 @@ class MessagesManager final : public Actor {
bool is_mention_notification_disabled = false; bool is_mention_notification_disabled = false;
bool is_from_scheduled = false; bool is_from_scheduled = false;
bool is_pinned = false; bool is_pinned = false;
bool are_media_timestamp_entities_found = false;
bool is_copy = false; // for send_message bool is_copy = false; // for send_message
bool from_background = false; // for send_message bool from_background = false; // for send_message
@ -1065,6 +1063,9 @@ class MessagesManager final : public Actor {
NotificationId notification_id; NotificationId notification_id;
NotificationId removed_notification_id; NotificationId removed_notification_id;
int32 max_reply_media_timestamp = -1;
int32 max_own_media_timestamp = -2; // to update replied messages on the first load
int32 view_count = 0; int32 view_count = 0;
int32 forward_count = 0; int32 forward_count = 0;
MessageReplyInfo reply_info; MessageReplyInfo reply_info;
@ -1101,6 +1102,7 @@ class MessagesManager final : public Actor {
unique_ptr<Message> right; unique_ptr<Message> right;
mutable int32 last_access_date = 0; mutable int32 last_access_date = 0;
mutable bool is_update_sent = false; // whether the message is known to the app
mutable uint64 send_message_log_event_id = 0; mutable uint64 send_message_log_event_id = 0;
@ -1784,6 +1786,8 @@ class MessagesManager final : public Actor {
Status can_pin_messages(DialogId dialog_id) const; Status can_pin_messages(DialogId dialog_id) const;
static Status can_get_media_timestamp_link(DialogId dialog_id, const Message *m);
void cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message); void cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message);
void on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id, void on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id,
@ -2142,13 +2146,26 @@ class MessagesManager final : public Actor {
void attach_message_to_next(Dialog *d, MessageId message_id, const char *source); void attach_message_to_next(Dialog *d, MessageId message_id, const char *source);
bool update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message, bool *need_update_dialog_pos); bool update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message, bool *need_update_dialog_pos,
bool is_message_in_dialog);
static bool need_message_changed_warning(const Message *old_message); static bool need_message_changed_warning(const Message *old_message);
bool update_message_content(DialogId dialog_id, Message *old_message, unique_ptr<MessageContent> new_content, bool update_message_content(DialogId dialog_id, Message *old_message, unique_ptr<MessageContent> new_content,
bool need_send_update_message_content, bool need_merge_files, bool is_message_in_dialog); bool need_send_update_message_content, bool need_merge_files, bool is_message_in_dialog);
void update_message_max_reply_media_timestamp(const Dialog *d, Message *m, bool need_send_update_message_content);
void update_message_max_own_media_timestamp(const Dialog *d, Message *m);
void update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id, MessageId reply_to_message_id);
void register_message_reply(const Dialog *d, const Message *m);
void reregister_message_reply(const Dialog *d, const Message *m);
void unregister_message_reply(const Dialog *d, const Message *m);
void send_update_new_message(const Dialog *d, const Message *m); void send_update_new_message(const Dialog *d, const Message *m);
static bool is_from_mention_notification_group(const Dialog *d, const Message *m); static bool is_from_mention_notification_group(const Dialog *d, const Message *m);
@ -2205,7 +2222,9 @@ class MessagesManager final : public Actor {
void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const; void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const;
void send_update_message_content(DialogId dialog_id, const Message *m, const char *source); void send_update_message_content(DialogId dialog_id, Message *m, bool is_message_in_dialog, const char *source);
void send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog, const char *source);
void send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const; void send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const;
@ -2608,6 +2627,10 @@ class MessagesManager final : public Actor {
bool has_message_sender_user_id(DialogId dialog_id, const Message *m) const; bool has_message_sender_user_id(DialogId dialog_id, const Message *m) const;
int32 get_message_own_max_media_timestamp(const Message *m) const;
static int32 get_message_max_media_timestamp(const Message *m);
static bool get_message_disable_web_page_preview(const Message *m); static bool get_message_disable_web_page_preview(const Message *m);
static int32 get_message_flags(const Message *m); static int32 get_message_flags(const Message *m);
@ -2656,9 +2679,9 @@ class MessagesManager final : public Actor {
void process_discussion_message_impl(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result, void process_discussion_message_impl(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result,
DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id,
MessageId expected_message_id, Promise<vector<FullMessageId>> promise); MessageId expected_message_id, Promise<MessageThreadInfo> promise);
void on_get_discussion_message(DialogId dialog_id, MessageId message_id, vector<FullMessageId> full_message_ids, void on_get_discussion_message(DialogId dialog_id, MessageId message_id, MessageThreadInfo &&message_thread_info,
Promise<MessageThreadInfo> &&promise); Promise<MessageThreadInfo> &&promise);
static MessageId get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter); static MessageId get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter);
@ -3207,10 +3230,10 @@ class MessagesManager final : public Actor {
std::unordered_map<int64, FoundMessages> found_fts_messages_; // random_id -> FoundMessages std::unordered_map<int64, FoundMessages> found_fts_messages_; // random_id -> FoundMessages
std::unordered_map<int64, FoundMessages> found_message_public_forwards_; // random_id -> FoundMessages std::unordered_map<int64, FoundMessages> found_message_public_forwards_; // random_id -> FoundMessages
struct PublicMessageLinks { struct MessageEmbeddingCodes {
std::unordered_map<MessageId, std::pair<string, string>, MessageIdHash> links_; std::unordered_map<MessageId, string, MessageIdHash> embedding_codes_;
}; };
std::unordered_map<DialogId, PublicMessageLinks, DialogIdHash> public_message_links_[2]; std::unordered_map<DialogId, MessageEmbeddingCodes, DialogIdHash> message_embedding_codes_[2];
std::unordered_map<int64, tl_object_ptr<td_api::chatEvents>> chat_events_; // random_id -> chat events std::unordered_map<int64, tl_object_ptr<td_api::chatEvents>> chat_events_; // random_id -> chat events
@ -3223,6 +3246,10 @@ class MessagesManager final : public Actor {
std::unordered_map<FullMessageId, int32, FullMessageIdHash> replied_by_yet_unsent_messages_; std::unordered_map<FullMessageId, int32, FullMessageIdHash> replied_by_yet_unsent_messages_;
// full_message_id -> replies with media timestamps
std::unordered_map<FullMessageId, std::unordered_set<MessageId, MessageIdHash>, FullMessageIdHash>
replied_by_media_timestamp_messages_;
struct ActiveDialogAction { struct ActiveDialogAction {
MessageId top_thread_message_id; MessageId top_thread_message_id;
UserId user_id; UserId user_id;

View File

@ -109,9 +109,7 @@ void parse(PhotoSizeSource::FullLegacy &source, ParserT &parser) {
parse(source.volume_id, parser); parse(source.volume_id, parser);
parse(source.secret, parser); parse(source.secret, parser);
parse(source.local_id, parser); parse(source.local_id, parser);
if (source.local_id < 0) { // source.local_id can be negative in secret chat thumbnails
parser.set_error("Wrong local_id");
}
} }
template <class StorerT> template <class StorerT>

View File

@ -575,7 +575,7 @@ td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id, co
auto correct_option_id = is_local_poll_id(poll_id) ? -1 : poll->correct_option_id; auto correct_option_id = is_local_poll_id(poll_id) ? -1 : poll->correct_option_id;
poll_type = td_api::make_object<td_api::pollTypeQuiz>( poll_type = td_api::make_object<td_api::pollTypeQuiz>(
correct_option_id, correct_option_id,
get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation, true)); get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation, true, -1));
} else { } else {
poll_type = td_api::make_object<td_api::pollTypeRegular>(poll->allow_multiple_answers); poll_type = td_api::make_object<td_api::pollTypeRegular>(poll->allow_multiple_answers);
} }
@ -1581,12 +1581,12 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
auto entities = auto entities =
get_message_entities(td_->contacts_manager_.get(), std::move(poll_results->solution_entities_), "on_get_poll"); get_message_entities(td_->contacts_manager_.get(), std::move(poll_results->solution_entities_), "on_get_poll");
auto status = fix_formatted_text(poll_results->solution_, entities, true, true, true, false); auto status = fix_formatted_text(poll_results->solution_, entities, true, true, true, true, false);
if (status.is_error()) { if (status.is_error()) {
if (!clean_input_string(poll_results->solution_)) { if (!clean_input_string(poll_results->solution_)) {
poll_results->solution_.clear(); poll_results->solution_.clear();
} }
entities = find_entities(poll_results->solution_, true); entities = find_entities(poll_results->solution_, true, true);
} }
FormattedText explanation{std::move(poll_results->solution_), std::move(entities)}; FormattedText explanation{std::move(poll_results->solution_), std::move(entities)};

View File

@ -30,7 +30,6 @@
#include "td/utils/format.h" #include "td/utils/format.h"
#include "td/utils/logging.h" #include "td/utils/logging.h"
#include "td/utils/misc.h" #include "td/utils/misc.h"
#include "td/utils/overloaded.h"
#include "td/utils/Random.h" #include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h" #include "td/utils/ScopeGuard.h"
#include "td/utils/SliceBuilder.h" #include "td/utils/SliceBuilder.h"

View File

@ -441,8 +441,8 @@ void SetSecureValue::start_up() {
for (auto it = secure_value_.files.begin(); it != secure_value_.files.end();) { for (auto it = secure_value_.files.begin(); it != secure_value_.files.end();) {
auto file_id = file_manager->get_file_view(it->file_id).file_id(); auto file_id = file_manager->get_file_view(it->file_id).file_id();
bool is_duplicate = false; bool is_duplicate = false;
for (auto pit = secure_value_.files.begin(); pit != it; pit++) { for (auto other_it = secure_value_.files.begin(); other_it != it; ++other_it) {
if (file_id == file_manager->get_file_view(pit->file_id).file_id()) { if (file_id == file_manager->get_file_view(other_it->file_id).file_id()) {
is_duplicate = true; is_duplicate = true;
break; break;
} }
@ -458,8 +458,8 @@ void SetSecureValue::start_up() {
for (auto it = secure_value_.translations.begin(); it != secure_value_.translations.end();) { for (auto it = secure_value_.translations.begin(); it != secure_value_.translations.end();) {
auto file_id = file_manager->get_file_view(it->file_id).file_id(); auto file_id = file_manager->get_file_view(it->file_id).file_id();
bool is_duplicate = file_id == front_side_file_id || file_id == reverse_side_file_id || file_id == selfie_file_id; bool is_duplicate = file_id == front_side_file_id || file_id == reverse_side_file_id || file_id == selfie_file_id;
for (auto pit = secure_value_.translations.begin(); pit != it; pit++) { for (auto other_it = secure_value_.translations.begin(); other_it != it; ++other_it) {
if (file_id == file_manager->get_file_view(pit->file_id).file_id()) { if (file_id == file_manager->get_file_view(other_it->file_id).file_id()) {
is_duplicate = true; is_duplicate = true;
break; break;
} }

View File

@ -182,10 +182,6 @@ struct EncryptedValue {
BufferSlice data; BufferSlice data;
ValueHash hash; ValueHash hash;
}; };
struct EncryptedFile {
std::string path;
ValueHash hash;
};
Result<EncryptedValue> encrypt_value(const Secret &secret, Slice data); Result<EncryptedValue> encrypt_value(const Secret &secret, Slice data);
Result<ValueHash> encrypt_file(const Secret &secret, std::string src, std::string dest); Result<ValueHash> encrypt_file(const Secret &secret, std::string src, std::string dest);

View File

@ -136,8 +136,8 @@ void update_suggested_actions(vector<SuggestedAction> &suggested_actions,
} else if (old_it == suggested_actions.end() || *new_it < *old_it) { } else if (old_it == suggested_actions.end() || *new_it < *old_it) {
added_actions.push_back(*new_it++); added_actions.push_back(*new_it++);
} else { } else {
old_it++; ++old_it;
new_it++; ++new_it;
} }
} }
CHECK(!added_actions.empty() || !removed_actions.empty()); CHECK(!added_actions.empty() || !removed_actions.empty());

View File

@ -59,6 +59,7 @@
#include "td/telegram/MessageLinkInfo.h" #include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessageSearchFilter.h"
#include "td/telegram/MessagesManager.h" #include "td/telegram/MessagesManager.h"
#include "td/telegram/MessageThreadInfo.h"
#include "td/telegram/misc.h" #include "td/telegram/misc.h"
#include "td/telegram/net/ConnectionCreator.h" #include "td/telegram/net/ConnectionCreator.h"
#include "td/telegram/net/DcId.h" #include "td/telegram/net/DcId.h"
@ -440,67 +441,6 @@ class GetInviteTextQuery final : public Td::ResultHandler {
} }
}; };
class GetDeepLinkInfoQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::deepLinkInfo>> promise_;
public:
explicit GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise)
: promise_(std::move(promise)) {
}
void send(Slice link) {
Slice link_scheme("tg:");
if (begins_with(link, link_scheme)) {
link.remove_prefix(link_scheme.size());
if (begins_with(link, "//")) {
link.remove_prefix(2);
}
}
size_t pos = 0;
while (pos < link.size() && link[pos] != '/' && link[pos] != '?' && link[pos] != '#') {
pos++;
}
link.truncate(pos);
send_query(G()->net_query_creator().create_unauth(telegram_api::help_getDeepLinkInfo(link.str())));
}
void on_result(uint64 id, BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_getDeepLinkInfo>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
switch (result->get_id()) {
case telegram_api::help_deepLinkInfoEmpty::ID:
return promise_.set_value(nullptr);
case telegram_api::help_deepLinkInfo::ID: {
auto info = telegram_api::move_object_as<telegram_api::help_deepLinkInfo>(result);
bool need_update = (info->flags_ & telegram_api::help_deepLinkInfo::UPDATE_APP_MASK) != 0;
auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery");
auto status = fix_formatted_text(info->message_, entities, true, true, true, true);
if (status.is_error()) {
LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_;
if (!clean_input_string(info->message_)) {
info->message_.clear();
}
entities = find_entities(info->message_, true);
}
FormattedText text{std::move(info->message_), std::move(entities)};
return promise_.set_value(
td_api::make_object<td_api::deepLinkInfo>(get_formatted_text_object(text, true), need_update));
}
default:
UNREACHABLE();
}
}
void on_error(uint64 id, Status status) final {
promise_.set_error(std::move(status));
}
};
class SaveAppLogQuery final : public Td::ResultHandler { class SaveAppLogQuery final : public Td::ResultHandler {
Promise<Unit> promise_; Promise<Unit> promise_;
@ -1067,13 +1007,13 @@ class GetRepliedMessageRequest final : public RequestOnceActor {
} }
}; };
class GetMessageThreadRequest final : public RequestActor<MessagesManager::MessageThreadInfo> { class GetMessageThreadRequest final : public RequestActor<MessageThreadInfo> {
DialogId dialog_id_; DialogId dialog_id_;
MessageId message_id_; MessageId message_id_;
MessagesManager::MessageThreadInfo message_thread_info_; MessageThreadInfo message_thread_info_;
void do_run(Promise<MessagesManager::MessageThreadInfo> &&promise) final { void do_run(Promise<MessageThreadInfo> &&promise) final {
if (get_tries() < 2) { if (get_tries() < 2) {
promise.set_value(std::move(message_thread_info_)); promise.set_value(std::move(message_thread_info_));
return; return;
@ -1081,7 +1021,7 @@ class GetMessageThreadRequest final : public RequestActor<MessagesManager::Messa
td->messages_manager_->get_message_thread(dialog_id_, message_id_, std::move(promise)); td->messages_manager_->get_message_thread(dialog_id_, message_id_, std::move(promise));
} }
void do_set_result(MessagesManager::MessageThreadInfo &&result) final { void do_set_result(MessageThreadInfo &&result) final {
message_thread_info_ = std::move(result); message_thread_info_ = std::move(result);
} }
@ -8347,7 +8287,7 @@ void Td::on_request(uint64 id, const td_api::getApplicationDownloadLink &request
void Td::on_request(uint64 id, td_api::getDeepLinkInfo &request) { void Td::on_request(uint64 id, td_api::getDeepLinkInfo &request) {
CLEAN_INPUT_STRING(request.link_); CLEAN_INPUT_STRING(request.link_);
CREATE_REQUEST_PROMISE(); CREATE_REQUEST_PROMISE();
create_handler<GetDeepLinkInfoQuery>(std::move(promise))->send(request.link_); link_manager_->get_deep_link_info(request.link_, std::move(promise));
} }
void Td::on_request(uint64 id, const td_api::getApplicationConfig &request) { void Td::on_request(uint64 id, const td_api::getApplicationConfig &request) {
@ -8510,8 +8450,9 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getTextEn
if (!check_utf8(request.text_)) { if (!check_utf8(request.text_)) {
return make_error(400, "Text must be encoded in UTF-8"); return make_error(400, "Text must be encoded in UTF-8");
} }
auto text_entities = find_entities(request.text_, false); auto text_entities = find_entities(request.text_, false, false);
return make_tl_object<td_api::textEntities>(get_text_entities_object(text_entities, false)); return make_tl_object<td_api::textEntities>(
get_text_entities_object(text_entities, false, std::numeric_limits<int32>::max()));
} }
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntities &request) { td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntities &request) {
@ -8546,7 +8487,7 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntiti
} }
return make_tl_object<td_api::formattedText>(std::move(request.text_), return make_tl_object<td_api::formattedText>(std::move(request.text_),
get_text_entities_object(r_entities.ok(), false)); get_text_entities_object(r_entities.ok(), false, -1));
} }
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &request) { td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &request) {
@ -8559,14 +8500,14 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &
return make_error(400, r_entities.error().message()); return make_error(400, r_entities.error().message());
} }
auto entities = r_entities.move_as_ok(); auto entities = r_entities.move_as_ok();
auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true); auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true);
if (status.is_error()) { if (status.is_error()) {
return make_error(400, status.error().message()); return make_error(400, status.error().message());
} }
auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)}); auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)});
fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).ensure(); fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).ensure();
return get_formatted_text_object(parsed_text, true); return get_formatted_text_object(parsed_text, false, std::numeric_limits<int32>::max());
} }
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getMarkdownText &request) { td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getMarkdownText &request) {
@ -8579,12 +8520,13 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getMarkdownText
return make_error(400, r_entities.error().message()); return make_error(400, r_entities.error().message());
} }
auto entities = r_entities.move_as_ok(); auto entities = r_entities.move_as_ok();
auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true); auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true);
if (status.is_error()) { if (status.is_error()) {
return make_error(400, status.error().message()); return make_error(400, status.error().message());
} }
return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), true); return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), false,
std::numeric_limits<int32>::max());
} }
td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getFileMimeType &request) { td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getFileMimeType &request) {

View File

@ -95,12 +95,12 @@ TermsOfService::TermsOfService(telegram_api::object_ptr<telegram_api::help_terms
id_ = std::move(terms->id_->data_); id_ = std::move(terms->id_->data_);
auto entities = get_message_entities(nullptr, std::move(terms->entities_), "TermsOfService"); auto entities = get_message_entities(nullptr, std::move(terms->entities_), "TermsOfService");
auto status = fix_formatted_text(terms->text_, entities, true, true, true, false); auto status = fix_formatted_text(terms->text_, entities, true, true, true, true, false);
if (status.is_error()) { if (status.is_error()) {
if (!clean_input_string(terms->text_)) { if (!clean_input_string(terms->text_)) {
terms->text_.clear(); terms->text_.clear();
} }
entities = find_entities(terms->text_, true); entities = find_entities(terms->text_, true, true);
} }
if (terms->text_.empty()) { if (terms->text_.empty()) {
id_.clear(); id_.clear();

View File

@ -42,7 +42,7 @@ class TermsOfService {
return nullptr; return nullptr;
} }
return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_, true), min_user_age_, return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_, true, -1), min_user_age_,
show_popup_); show_popup_);
} }

View File

@ -854,7 +854,7 @@ void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updat
auto updates = move_tl_object_as<telegram_api::updatesCombined>(updates_ptr); auto updates = move_tl_object_as<telegram_api::updatesCombined>(updates_ptr);
td_->contacts_manager_->on_get_users(std::move(updates->users_), "updatesCombined"); td_->contacts_manager_->on_get_users(std::move(updates->users_), "updatesCombined");
td_->contacts_manager_->on_get_chats(std::move(updates->chats_), "updatesCombined"); td_->contacts_manager_->on_get_chats(std::move(updates->chats_), "updatesCombined");
on_pending_updates(std::move(updates->updates_), updates->seq_start_, updates->seq_, updates->date_, on_pending_updates(std::move(updates->updates_), updates->seq_start_, updates->seq_, updates->date_, Time::now(),
std::move(promise), "telegram_api::updatesCombined"); std::move(promise), "telegram_api::updatesCombined");
break; break;
} }
@ -862,8 +862,8 @@ void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updat
auto updates = move_tl_object_as<telegram_api::updates>(updates_ptr); auto updates = move_tl_object_as<telegram_api::updates>(updates_ptr);
td_->contacts_manager_->on_get_users(std::move(updates->users_), "updates"); td_->contacts_manager_->on_get_users(std::move(updates->users_), "updates");
td_->contacts_manager_->on_get_chats(std::move(updates->chats_), "updates"); td_->contacts_manager_->on_get_chats(std::move(updates->chats_), "updates");
on_pending_updates(std::move(updates->updates_), updates->seq_, updates->seq_, updates->date_, std::move(promise), on_pending_updates(std::move(updates->updates_), updates->seq_, updates->seq_, updates->date_, Time::now(),
"telegram_api::updates"); std::move(promise), "telegram_api::updates");
break; break;
} }
case telegram_api::updateShortSentMessage::ID: case telegram_api::updateShortSentMessage::ID:
@ -1356,9 +1356,9 @@ void UpdatesManager::on_get_difference(tl_object_ptr<telegram_api::updates_Diffe
auto state = std::move(difference->intermediate_state_); auto state = std::move(difference->intermediate_state_);
if (get_pts() != std::numeric_limits<int32>::max() && state->date_ == get_date() && if (get_pts() != std::numeric_limits<int32>::max() && state->date_ == get_date() &&
(state->pts_ == get_pts() || (state->pts_ == get_pts() ||
(min_postponed_update_pts_ != 0 && state->pts_ - 1000 >= min_postponed_update_pts_)) && (min_postponed_update_pts_ != 0 && state->pts_ - 500 >= min_postponed_update_pts_)) &&
(state->qts_ == get_qts() || (state->qts_ == get_qts() ||
(min_postponed_update_qts_ != 0 && state->qts_ - 1000 >= min_postponed_update_qts_))) { (min_postponed_update_qts_ != 0 && state->qts_ - 500 >= min_postponed_update_qts_))) {
on_get_updates_state(std::move(state), "get difference final slice"); on_get_updates_state(std::move(state), "get difference final slice");
VLOG(get_difference) << "Trying to switch back from getDifference to update processing"; VLOG(get_difference) << "Trying to switch back from getDifference to update processing";
break; break;
@ -1401,7 +1401,7 @@ void UpdatesManager::after_get_difference() {
return; return;
} }
if (postponed_updates_.size()) { if (!postponed_updates_.empty()) {
VLOG(get_difference) << "Begin to apply " << postponed_updates_.size() << " postponed update chunks"; VLOG(get_difference) << "Begin to apply " << postponed_updates_.size() << " postponed update chunks";
size_t total_update_count = 0; size_t total_update_count = 0;
while (!postponed_updates_.empty()) { while (!postponed_updates_.empty()) {
@ -1409,11 +1409,12 @@ void UpdatesManager::after_get_difference() {
auto updates = std::move(it->second.updates); auto updates = std::move(it->second.updates);
auto updates_seq_begin = it->second.seq_begin; auto updates_seq_begin = it->second.seq_begin;
auto updates_seq_end = it->second.seq_end; auto updates_seq_end = it->second.seq_end;
auto receive_time = it->second.receive_time;
auto promise = std::move(it->second.promise); auto promise = std::move(it->second.promise);
// ignore it->second.date, because it may be too old // ignore it->second.date, because it may be too old
postponed_updates_.erase(it); postponed_updates_.erase(it);
auto update_count = updates.size(); auto update_count = updates.size();
on_pending_updates(std::move(updates), updates_seq_begin, updates_seq_end, 0, std::move(promise), on_pending_updates(std::move(updates), updates_seq_begin, updates_seq_end, 0, receive_time, std::move(promise),
"postponed updates"); "postponed updates");
if (running_get_difference_) { if (running_get_difference_) {
VLOG(get_difference) << "Finish to apply postponed updates with " << postponed_updates_.size() VLOG(get_difference) << "Finish to apply postponed updates with " << postponed_updates_.size()
@ -1426,15 +1427,15 @@ void UpdatesManager::after_get_difference() {
VLOG(get_difference) << "Finish to apply " << total_update_count << " postponed updates"; VLOG(get_difference) << "Finish to apply " << total_update_count << " postponed updates";
} }
if (postponed_pts_updates_.size()) { // must be before td_->messages_manager_->after_get_difference() if (!postponed_pts_updates_.empty()) { // must be before td_->messages_manager_->after_get_difference()
auto postponed_updates = std::move(postponed_pts_updates_); auto postponed_updates = std::move(postponed_pts_updates_);
postponed_pts_updates_.clear(); postponed_pts_updates_.clear();
LOG(INFO) << "Begin to apply " << postponed_updates.size() << " postponed pts updates"; LOG(INFO) << "Begin to apply " << postponed_updates.size() << " postponed pts updates";
for (auto &postponed_update : postponed_updates) { for (auto &postponed_update : postponed_updates) {
auto &update = postponed_update.second; auto &update = postponed_update.second;
add_pending_pts_update(std::move(update.update), update.pts, update.pts_count, std::move(update.promise), add_pending_pts_update(std::move(update.update), update.pts, update.pts_count, update.receive_time,
"after get difference"); std::move(update.promise), "after get difference");
CHECK(!running_get_difference_); CHECK(!running_get_difference_);
} }
LOG(INFO) << "Finish to apply postponed pts updates, have " << postponed_pts_updates_.size() LOG(INFO) << "Finish to apply postponed pts updates, have " << postponed_pts_updates_.size()
@ -1451,7 +1452,8 @@ void UpdatesManager::after_get_difference() {
} }
void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin, void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin,
int32 seq_end, int32 date, Promise<Unit> &&promise, const char *source) { int32 seq_end, int32 date, double receive_time, Promise<Unit> &&promise,
const char *source) {
if (get_pts() == -1) { if (get_pts() == -1) {
init_state(); init_state();
} }
@ -1485,8 +1487,8 @@ void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Updat
min_postponed_update_qts_ = qts; min_postponed_update_qts_ = qts;
} }
} }
postponed_updates_.emplace(seq_begin, postponed_updates_.emplace(
PendingSeqUpdates(seq_begin, seq_end, date, std::move(updates), std::move(promise))); seq_begin, PendingSeqUpdates(seq_begin, seq_end, date, receive_time, std::move(updates), std::move(promise)));
return; return;
} }
@ -1631,8 +1633,8 @@ void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Updat
if (running_get_difference_) { if (running_get_difference_) {
LOG(ERROR) << "Postpone " << updates.size() << " updates [" << seq_begin << ", " << seq_end LOG(ERROR) << "Postpone " << updates.size() << " updates [" << seq_begin << ", " << seq_end
<< "] with date = " << date << " from " << source; << "] with date = " << date << " from " << source;
postponed_updates_.emplace(seq_begin, postponed_updates_.emplace(
PendingSeqUpdates(seq_begin, seq_end, date, std::move(updates), mpas.get_promise())); seq_begin, PendingSeqUpdates(seq_begin, seq_end, date, receive_time, std::move(updates), mpas.get_promise()));
return lock.set_value(Unit()); return lock.set_value(Unit());
} }
@ -1668,9 +1670,9 @@ void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Updat
LOG_IF(WARNING, pending_seq_updates_.find(seq_begin) != pending_seq_updates_.end()) LOG_IF(WARNING, pending_seq_updates_.find(seq_begin) != pending_seq_updates_.end())
<< "Already have pending updates with seq = " << seq_begin << ", but receive it again from " << source; << "Already have pending updates with seq = " << seq_begin << ", but receive it again from " << source;
pending_seq_updates_.emplace(seq_begin, pending_seq_updates_.emplace(
PendingSeqUpdates(seq_begin, seq_end, date, std::move(updates), mpas.get_promise())); seq_begin, PendingSeqUpdates(seq_begin, seq_end, date, receive_time, std::move(updates), mpas.get_promise()));
set_seq_gap_timeout(MAX_UNFILLED_GAP_TIME); set_seq_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
lock.set_value(Unit()); lock.set_value(Unit());
} }
@ -1711,6 +1713,8 @@ void UpdatesManager::add_pending_qts_update(tl_object_ptr<telegram_api::Update>
auto &pending_update = pending_qts_updates_[qts]; auto &pending_update = pending_qts_updates_[qts];
if (pending_update.update != nullptr) { if (pending_update.update != nullptr) {
LOG(WARNING) << "Receive duplicate update with qts = " << qts; LOG(WARNING) << "Receive duplicate update with qts = " << qts;
} else {
pending_update.receive_time = Time::now();
} }
pending_update.update = std::move(update); pending_update.update = std::move(update);
pending_update.promises.push_back(std::move(promise)); pending_update.promises.push_back(std::move(promise));
@ -1849,7 +1853,8 @@ void UpdatesManager::process_pts_update(tl_object_ptr<telegram_api::Update> &&up
} }
void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
int32 pts_count, Promise<Unit> &&promise, const char *source) { int32 pts_count, double receive_time, Promise<Unit> &&promise,
const char *source) {
// do not try to run getDifference from this function // do not try to run getDifference from this function
CHECK(update != nullptr); CHECK(update != nullptr);
CHECK(source != nullptr); CHECK(source != nullptr);
@ -1905,7 +1910,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update>
if (running_get_difference_) { if (running_get_difference_) {
CHECK(update->get_id() == dummyUpdate::ID || update->get_id() == updateSentMessage::ID); CHECK(update->get_id() == dummyUpdate::ID || update->get_id() == updateSentMessage::ID);
} }
postpone_pts_update(std::move(update), new_pts, pts_count, std::move(promise)); postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
return; return;
} }
@ -1913,7 +1918,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update>
LOG(WARNING) << "Have old_pts (= " << old_pts << ") + pts_count (= " << pts_count << ") > new_pts (= " << new_pts LOG(WARNING) << "Have old_pts (= " << old_pts << ") + pts_count (= " << pts_count << ") > new_pts (= " << new_pts
<< "). Logged in " << G()->shared_config().get_option_integer("authorization_date") << ". Update from " << "). Logged in " << G()->shared_config().get_option_integer("authorization_date") << ". Update from "
<< source << " = " << oneline(to_string(update)); << source << " = " << oneline(to_string(update));
postpone_pts_update(std::move(update), new_pts, pts_count, std::move(promise)); postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
set_pts_gap_timeout(0.001); set_pts_gap_timeout(0.001);
return; return;
} }
@ -1929,7 +1934,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update>
<< ", pts_count = " << pts_count << ". Logged in " << ", pts_count = " << pts_count << ". Logged in "
<< G()->shared_config().get_option_integer("authorization_date") << ". Update from " << source << " = " << G()->shared_config().get_option_integer("authorization_date") << ". Update from " << source << " = "
<< oneline(to_string(update)); << oneline(to_string(update));
postpone_pts_update(std::move(update), new_pts, pts_count, std::move(promise)); postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
set_pts_gap_timeout(0.001); set_pts_gap_timeout(0.001);
return; return;
} }
@ -1950,11 +1955,11 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update>
return; return;
} }
pending_pts_updates_.emplace(new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, std::move(promise))); pending_pts_updates_.emplace(
new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, receive_time, std::move(promise)));
if (old_pts < accumulated_pts_ - accumulated_pts_count_) { if (old_pts < accumulated_pts_ - accumulated_pts_count_) {
set_pts_gap_timeout(MAX_UNFILLED_GAP_TIME); set_pts_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
last_pts_gap_time_ = Time::now();
return; return;
} }
@ -1963,8 +1968,9 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update>
} }
void UpdatesManager::postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, void UpdatesManager::postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count,
Promise<Unit> &&promise) { double receive_time, Promise<Unit> &&promise) {
postponed_pts_updates_.emplace(pts, PendingPtsUpdate(std::move(update), pts, pts_count, std::move(promise))); postponed_pts_updates_.emplace(pts,
PendingPtsUpdate(std::move(update), pts, pts_count, receive_time, std::move(promise)));
} }
void UpdatesManager::process_seq_updates(int32 seq_end, int32 date, void UpdatesManager::process_seq_updates(int32 seq_end, int32 date,
@ -2075,16 +2081,17 @@ void UpdatesManager::process_pending_seq_updates() {
while (!pending_seq_updates_.empty() && !running_get_difference_) { while (!pending_seq_updates_.empty() && !running_get_difference_) {
auto update_it = pending_seq_updates_.begin(); auto update_it = pending_seq_updates_.begin();
auto seq_begin = update_it->second.seq_begin; auto seq_begin = update_it->second.seq_begin;
if (seq_begin > seq_ + 1) { if (seq_begin - 1 > seq_ && seq_begin - 500000000 <= seq_) {
// the updates will be applied later
break; break;
} }
if (seq_begin == seq_ + 1) { if (seq_begin - 1 == seq_) {
process_seq_updates(update_it->second.seq_end, update_it->second.date, std::move(update_it->second.updates), process_seq_updates(update_it->second.seq_end, update_it->second.date, std::move(update_it->second.updates),
std::move(update_it->second.promise)); std::move(update_it->second.promise));
} else { } else {
// old update // old update
CHECK(seq_begin != 0); CHECK(seq_begin != 0);
LOG_IF(ERROR, update_it->second.seq_end > seq_) LOG_IF(ERROR, update_it->second.seq_end > seq_ && seq_begin - 1 < seq_)
<< "Strange updates coming with seq_begin = " << seq_begin << ", seq_end = " << update_it->second.seq_end << "Strange updates coming with seq_begin = " << seq_begin << ", seq_end = " << update_it->second.seq_end
<< ", but seq = " << seq_; << ", but seq = " << seq_;
update_it->second.promise.set_value(Unit()); update_it->second.promise.set_value(Unit());
@ -2095,7 +2102,15 @@ void UpdatesManager::process_pending_seq_updates() {
seq_gap_timeout_.cancel_timeout(); seq_gap_timeout_.cancel_timeout();
} else { } else {
// if after getDifference still have a gap // if after getDifference still have a gap
set_seq_gap_timeout(MAX_UNFILLED_GAP_TIME); auto update_it = pending_seq_updates_.begin();
double receive_time = update_it->second.receive_time;
for (size_t i = 0; i < 10; i++) {
if (++update_it == pending_seq_updates_.end()) {
break;
}
receive_time = min(receive_time, update_it->second.receive_time);
}
set_seq_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
} }
} }
@ -2103,12 +2118,15 @@ void UpdatesManager::process_pending_qts_updates() {
if (pending_qts_updates_.empty()) { if (pending_qts_updates_.empty()) {
return; return;
} }
LOG(DEBUG) << "Process " << pending_qts_updates_.size() << " pending qts updates"; LOG(DEBUG) << "Process " << pending_qts_updates_.size() << " pending qts updates";
while (!pending_qts_updates_.empty()) { while (!pending_qts_updates_.empty()) {
CHECK(!running_get_difference_); CHECK(!running_get_difference_);
auto update_it = pending_qts_updates_.begin(); auto update_it = pending_qts_updates_.begin();
auto qts = update_it->first; auto qts = update_it->first;
if (qts > get_qts() + 1) { auto old_qts = get_qts();
if (qts - 1 > old_qts && qts - 500000000 <= old_qts) {
// the update will be applied later
break; break;
} }
auto promise = PromiseCreator::lambda([promises = std::move(update_it->second.promises)](Unit) mutable { auto promise = PromiseCreator::lambda([promises = std::move(update_it->second.promises)](Unit) mutable {
@ -2116,31 +2134,41 @@ void UpdatesManager::process_pending_qts_updates() {
promise.set_value(Unit()); promise.set_value(Unit());
} }
}); });
if (qts == get_qts() + 1) { if (qts == old_qts + 1) {
process_qts_update(std::move(update_it->second.update), qts, std::move(promise)); process_qts_update(std::move(update_it->second.update), qts, std::move(promise));
} else { } else {
promise.set_value(Unit()); promise.set_value(Unit());
} }
pending_qts_updates_.erase(update_it); pending_qts_updates_.erase(update_it);
} }
if (pending_qts_updates_.empty()) { if (pending_qts_updates_.empty()) {
qts_gap_timeout_.cancel_timeout(); qts_gap_timeout_.cancel_timeout();
} else { } else {
// if after getDifference still have a gap // if after getDifference still have a gap
set_qts_gap_timeout(MAX_UNFILLED_GAP_TIME); auto update_it = pending_qts_updates_.begin();
double receive_time = update_it->second.receive_time;
for (size_t i = 0; i < 10; i++) {
if (++update_it == pending_qts_updates_.end()) {
break;
}
receive_time = min(receive_time, update_it->second.receive_time);
}
set_qts_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
} }
} }
void UpdatesManager::set_pts_gap_timeout(double timeout) { void UpdatesManager::set_pts_gap_timeout(double timeout) {
if (!pts_gap_timeout_.has_timeout()) { if (!pts_gap_timeout_.has_timeout() || timeout < pts_gap_timeout_.get_timeout()) {
pts_gap_timeout_.set_callback(std::move(fill_pts_gap)); pts_gap_timeout_.set_callback(std::move(fill_pts_gap));
pts_gap_timeout_.set_callback_data(static_cast<void *>(td_)); pts_gap_timeout_.set_callback_data(static_cast<void *>(td_));
pts_gap_timeout_.set_timeout_in(timeout); pts_gap_timeout_.set_timeout_in(timeout);
last_pts_gap_time_ = Time::now();
} }
} }
void UpdatesManager::set_seq_gap_timeout(double timeout) { void UpdatesManager::set_seq_gap_timeout(double timeout) {
if (!seq_gap_timeout_.has_timeout()) { if (!seq_gap_timeout_.has_timeout() || timeout < seq_gap_timeout_.get_timeout()) {
seq_gap_timeout_.set_callback(std::move(fill_seq_gap)); seq_gap_timeout_.set_callback(std::move(fill_seq_gap));
seq_gap_timeout_.set_callback_data(static_cast<void *>(td_)); seq_gap_timeout_.set_callback_data(static_cast<void *>(td_));
seq_gap_timeout_.set_timeout_in(timeout); seq_gap_timeout_.set_timeout_in(timeout);
@ -2148,7 +2176,7 @@ void UpdatesManager::set_seq_gap_timeout(double timeout) {
} }
void UpdatesManager::set_qts_gap_timeout(double timeout) { void UpdatesManager::set_qts_gap_timeout(double timeout) {
if (!qts_gap_timeout_.has_timeout()) { if (!qts_gap_timeout_.has_timeout() || timeout < qts_gap_timeout_.get_timeout()) {
qts_gap_timeout_.set_callback(std::move(fill_qts_gap)); qts_gap_timeout_.set_callback(std::move(fill_qts_gap));
qts_gap_timeout_.set_callback_data(static_cast<void *>(td_)); qts_gap_timeout_.set_callback_data(static_cast<void *>(td_));
qts_gap_timeout_.set_timeout_in(timeout); qts_gap_timeout_.set_timeout_in(timeout);
@ -2159,13 +2187,13 @@ void UpdatesManager::on_pending_update(tl_object_ptr<telegram_api::Update> updat
const char *source) { const char *source) {
vector<tl_object_ptr<telegram_api::Update>> updates; vector<tl_object_ptr<telegram_api::Update>> updates;
updates.push_back(std::move(update)); updates.push_back(std::move(update));
on_pending_updates(std::move(updates), seq, seq, 0, std::move(promise), source); on_pending_updates(std::move(updates), seq, seq, 0, Time::now(), std::move(promise), source);
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewMessage> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewMessage> update, Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updateNewMessage"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise), "updateNewMessage");
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, Promise<Unit> &&promise) {
@ -2184,36 +2212,41 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadMessagesCon
Promise<Unit> &&promise) { Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updateReadMessagesContents"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
"updateReadMessagesContents");
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEditMessage> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEditMessage> update, Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updateEditMessage"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise), "updateEditMessage");
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteMessages> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteMessages> update, Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
if (update->messages_.empty()) { if (update->messages_.empty()) {
add_pending_pts_update(make_tl_object<dummyUpdate>(), new_pts, pts_count, Promise<Unit>(), "updateDeleteMessages"); add_pending_pts_update(make_tl_object<dummyUpdate>(), new_pts, pts_count, Time::now(), Promise<Unit>(),
"updateDeleteMessages");
promise.set_value(Unit()); promise.set_value(Unit());
} else { } else {
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updateDeleteMessages"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
"updateDeleteMessages");
} }
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryInbox> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryInbox> update, Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updateReadHistoryInbox"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
"updateReadHistoryInbox");
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryOutbox> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryOutbox> update, Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updateReadHistoryOutbox"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
"updateReadHistoryOutbox");
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateServiceNotification> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateServiceNotification> update, Promise<Unit> &&promise) {
@ -2325,7 +2358,8 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelDisc
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedMessages> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedMessages> update, Promise<Unit> &&promise) {
int new_pts = update->pts_; int new_pts = update->pts_;
int pts_count = update->pts_count_; int pts_count = update->pts_count_;
add_pending_pts_update(std::move(update), new_pts, pts_count, std::move(promise), "updatePinnedMessages"); add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
"updatePinnedMessages");
} }
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedChannelMessages> update, void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedChannelMessages> update,
@ -2388,7 +2422,7 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePeerLocated> up
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateWebPage> update, Promise<Unit> &&promise) { void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateWebPage> update, Promise<Unit> &&promise) {
td_->web_pages_manager_->on_get_web_page(std::move(update->webpage_), DialogId()); td_->web_pages_manager_->on_get_web_page(std::move(update->webpage_), DialogId());
add_pending_pts_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_, Promise<Unit>(), add_pending_pts_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_, Time::now(), Promise<Unit>(),
"updateWebPage"); "updateWebPage");
promise.set_value(Unit()); promise.set_value(Unit());
} }
@ -2409,8 +2443,8 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateFolderPeers> up
} }
if (update->pts_ > 0) { if (update->pts_ > 0) {
add_pending_pts_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_, Promise<Unit>(), add_pending_pts_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_, Time::now(),
"updateFolderPeers"); Promise<Unit>(), "updateFolderPeers");
} }
promise.set_value(Unit()); promise.set_value(Unit());
} }

View File

@ -95,7 +95,7 @@ class UpdatesManager final : public Actor {
void on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr, Promise<Unit> &&promise); void on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr, Promise<Unit> &&promise);
void add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count, void add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count,
Promise<Unit> &&promise, const char *source); double receive_time, Promise<Unit> &&promise, const char *source);
static std::unordered_set<int64> get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr); static std::unordered_set<int64> get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr);
@ -134,10 +134,16 @@ class UpdatesManager final : public Actor {
tl_object_ptr<telegram_api::Update> update; tl_object_ptr<telegram_api::Update> update;
int32 pts; int32 pts;
int32 pts_count; int32 pts_count;
double receive_time;
Promise<Unit> promise; Promise<Unit> promise;
PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, Promise<Unit> &&promise) PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, double receive_time,
: update(std::move(update)), pts(pts), pts_count(pts_count), promise(std::move(promise)) { Promise<Unit> &&promise)
: update(std::move(update))
, pts(pts)
, pts_count(pts_count)
, receive_time(receive_time)
, promise(std::move(promise)) {
} }
}; };
@ -146,17 +152,24 @@ class UpdatesManager final : public Actor {
int32 seq_begin; int32 seq_begin;
int32 seq_end; int32 seq_end;
int32 date; int32 date;
double receive_time;
vector<tl_object_ptr<telegram_api::Update>> updates; vector<tl_object_ptr<telegram_api::Update>> updates;
Promise<Unit> promise; Promise<Unit> promise;
PendingSeqUpdates(int32 seq_begin, int32 seq_end, int32 date, vector<tl_object_ptr<telegram_api::Update>> &&updates, PendingSeqUpdates(int32 seq_begin, int32 seq_end, int32 date, double receive_time,
Promise<Unit> &&promise) vector<tl_object_ptr<telegram_api::Update>> &&updates, Promise<Unit> &&promise)
: seq_begin(seq_begin), seq_end(seq_end), date(date), updates(std::move(updates)), promise(std::move(promise)) { : seq_begin(seq_begin)
, seq_end(seq_end)
, date(date)
, receive_time(receive_time)
, updates(std::move(updates))
, promise(std::move(promise)) {
} }
}; };
class PendingQtsUpdate { class PendingQtsUpdate {
public: public:
double receive_time;
tl_object_ptr<telegram_api::Update> update; tl_object_ptr<telegram_api::Update> update;
vector<Promise<Unit>> promises; vector<Promise<Unit>> promises;
}; };
@ -243,13 +256,13 @@ class UpdatesManager final : public Actor {
void add_pending_qts_update(tl_object_ptr<telegram_api::Update> &&update, int32 qts, Promise<Unit> &&promise); void add_pending_qts_update(tl_object_ptr<telegram_api::Update> &&update, int32 qts, Promise<Unit> &&promise);
void on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin, int32 seq_end, void on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin, int32 seq_end,
int32 date, Promise<Unit> &&promise, const char *source); int32 date, double receive_time, Promise<Unit> &&promise, const char *source);
void process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply, void process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply,
Promise<Unit> &&promise); Promise<Unit> &&promise);
void postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, void postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count,
Promise<Unit> &&promise); double receive_time, Promise<Unit> &&promise);
void process_pts_update(tl_object_ptr<telegram_api::Update> &&update); void process_pts_update(tl_object_ptr<telegram_api::Update> &&update);

View File

@ -825,7 +825,7 @@ int64 WebPagesManager::get_web_page_preview(td_api::object_ptr<td_api::formatted
} }
auto entities = r_entities.move_as_ok(); auto entities = r_entities.move_as_ok();
auto result = fix_formatted_text(text->text_, entities, true, false, true, false); auto result = fix_formatted_text(text->text_, entities, true, false, true, true, false);
if (result.is_error() || text->text_.empty()) { if (result.is_error() || text->text_.empty()) {
promise.set_value(Unit()); promise.set_value(Unit());
return 0; return 0;
@ -1225,7 +1225,7 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
FormattedText description; FormattedText description;
description.text = web_page->description; description.text = web_page->description;
description.entities = find_entities(web_page->description, true); description.entities = find_entities(web_page->description, true, false);
auto r_url = parse_url(web_page->display_url); auto r_url = parse_url(web_page->display_url);
if (r_url.is_ok()) { if (r_url.is_ok()) {
@ -1294,11 +1294,12 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
} }
} }
auto duration = get_web_page_media_duration(web_page);
return make_tl_object<td_api::webPage>( return make_tl_object<td_api::webPage>(
web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title, web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title,
get_formatted_text_object(description, true), get_photo_object(td_->file_manager_.get(), web_page->photo), get_formatted_text_object(description, true, duration == 0 ? std::numeric_limits<int32>::max() : duration),
web_page->embed_url, web_page->embed_type, web_page->embed_dimensions.width, web_page->embed_dimensions.height, get_photo_object(td_->file_manager_.get(), web_page->photo), web_page->embed_url, web_page->embed_type,
web_page->duration, web_page->author, web_page->embed_dimensions.width, web_page->embed_dimensions.height, web_page->duration, web_page->author,
web_page->document.type == Document::Type::Animation web_page->document.type == Document::Type::Animation
? td_->animations_manager_->get_animation_object(web_page->document.file_id, "get_web_page_object") ? td_->animations_manager_->get_animation_object(web_page->document.file_id, "get_web_page_object")
: nullptr, : nullptr,
@ -1751,12 +1752,21 @@ string WebPagesManager::get_web_page_search_text(WebPageId web_page_id) const {
return PSTRING() << web_page->title + " " + web_page->description; return PSTRING() << web_page->title + " " + web_page->description;
} }
int32 WebPagesManager::get_web_page_duration(WebPageId web_page_id) const { int32 WebPagesManager::get_web_page_media_duration(WebPageId web_page_id) const {
const WebPage *web_page = get_web_page(web_page_id); const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) { if (web_page == nullptr) {
return 0; return -1;
} }
return web_page->duration; return get_web_page_media_duration(web_page);
}
int32 WebPagesManager::get_web_page_media_duration(const WebPage *web_page) {
if (web_page->document.type == Document::Type::Audio || web_page->document.type == Document::Type::Video ||
web_page->document.type == Document::Type::VideoNote || web_page->document.type == Document::Type::VoiceNote || web_page->embed_type == "iframe") {
return web_page->duration;
}
return -1;
} }
vector<FileId> WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const { vector<FileId> WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const {

View File

@ -91,7 +91,7 @@ class WebPagesManager final : public Actor {
string get_web_page_search_text(WebPageId web_page_id) const; string get_web_page_search_text(WebPageId web_page_id) const;
int32 get_web_page_duration(WebPageId web_page_id) const; int32 get_web_page_media_duration(WebPageId web_page_id) const;
private: private:
static constexpr int32 WEBPAGE_FLAG_HAS_TYPE = 1 << 0; static constexpr int32 WEBPAGE_FLAG_HAS_TYPE = 1 << 0;
@ -174,6 +174,8 @@ class WebPagesManager final : public Actor {
void tear_down() final; void tear_down() final;
static int32 get_web_page_media_duration(const WebPage *web_page);
FileSourceId get_web_page_file_source_id(WebPage *web_page); FileSourceId get_web_page_file_source_id(WebPage *web_page);
vector<FileId> get_web_page_file_ids(const WebPage *web_page) const; vector<FileId> get_web_page_file_ids(const WebPage *web_page) const;

View File

@ -19,6 +19,7 @@
#include "td/utils/port/Clocks.h" #include "td/utils/port/Clocks.h"
#include "td/utils/port/FileFd.h" #include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h" #include "td/utils/port/path.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Random.h" #include "td/utils/Random.h"
#include "td/utils/SliceBuilder.h" #include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h" #include "td/utils/StringBuilder.h"

View File

@ -35,6 +35,9 @@ class Timeout final : public Actor {
bool has_timeout() const { bool has_timeout() const {
return Actor::has_timeout(); return Actor::has_timeout();
} }
double get_timeout() const {
return Actor::get_timeout();
}
void set_timeout_in(double timeout) { void set_timeout_in(double timeout) {
Actor::set_timeout_in(timeout); Actor::set_timeout_in(timeout);
} }

View File

@ -67,6 +67,7 @@ class Actor : public ObserverBase {
void stop(); void stop();
void do_stop(); void do_stop();
bool has_timeout() const; bool has_timeout() const;
double get_timeout() const;
void set_timeout_in(double timeout_in); void set_timeout_in(double timeout_in);
void set_timeout_at(double timeout_at); void set_timeout_at(double timeout_at);
void cancel_timeout(); void cancel_timeout();

View File

@ -54,6 +54,9 @@ inline void Actor::do_stop() {
inline bool Actor::has_timeout() const { inline bool Actor::has_timeout() const {
return Scheduler::instance()->has_actor_timeout(this); return Scheduler::instance()->has_actor_timeout(this);
} }
inline double Actor::get_timeout() const {
return Scheduler::instance()->get_actor_timeout(this);
}
inline void Actor::set_timeout_in(double timeout_in) { inline void Actor::set_timeout_in(double timeout_in) {
Scheduler::instance()->set_actor_timeout_in(this, timeout_in); Scheduler::instance()->set_actor_timeout_in(this, timeout_in);
} }

View File

@ -126,6 +126,7 @@ class Scheduler {
void finish_migrate_actor(Actor *actor); void finish_migrate_actor(Actor *actor);
bool has_actor_timeout(const Actor *actor) const; bool has_actor_timeout(const Actor *actor) const;
double get_actor_timeout(const Actor *actor) const;
void set_actor_timeout_in(Actor *actor, double timeout); void set_actor_timeout_in(Actor *actor, double timeout);
void set_actor_timeout_at(Actor *actor, double timeout_at); void set_actor_timeout_at(Actor *actor, double timeout_at);
void cancel_actor_timeout(Actor *actor); void cancel_actor_timeout(Actor *actor);
@ -176,6 +177,7 @@ class Scheduler {
void start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id); void start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
bool has_actor_timeout(const ActorInfo *actor_info) const; bool has_actor_timeout(const ActorInfo *actor_info) const;
double get_actor_timeout(const ActorInfo *actor_info) const;
void set_actor_timeout_in(ActorInfo *actor_info, double timeout); void set_actor_timeout_in(ActorInfo *actor_info, double timeout);
void set_actor_timeout_at(ActorInfo *actor_info, double timeout_at); void set_actor_timeout_at(ActorInfo *actor_info, double timeout_at);
void cancel_actor_timeout(ActorInfo *actor_info); void cancel_actor_timeout(ActorInfo *actor_info);

View File

@ -392,6 +392,7 @@ void Scheduler::do_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
void Scheduler::start_migrate_actor(Actor *actor, int32 dest_sched_id) { void Scheduler::start_migrate_actor(Actor *actor, int32 dest_sched_id) {
start_migrate_actor(actor->get_info(), dest_sched_id); start_migrate_actor(actor->get_info(), dest_sched_id);
} }
void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) { void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
VLOG(actor) << "Start migrate actor: " << tag("name", actor_info) << tag("ptr", actor_info) VLOG(actor) << "Start migrate actor: " << tag("name", actor_info) << tag("ptr", actor_info)
<< tag("actor_count", actor_count_); << tag("actor_count", actor_count_);
@ -406,6 +407,11 @@ void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id)
cancel_actor_timeout(actor_info); cancel_actor_timeout(actor_info);
} }
double Scheduler::get_actor_timeout(const ActorInfo *actor_info) const {
const HeapNode *heap_node = actor_info->get_heap_node();
return heap_node->in_heap() ? timeout_queue_.get_key(heap_node) - Time::now() : 0.0;
}
void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) { void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) {
if (timeout > 1e10) { if (timeout > 1e10) {
timeout = 1e10; timeout = 1e10;

View File

@ -302,6 +302,9 @@ inline void Scheduler::finish_migrate_actor(Actor *actor) {
inline bool Scheduler::has_actor_timeout(const Actor *actor) const { inline bool Scheduler::has_actor_timeout(const Actor *actor) const {
return has_actor_timeout(actor->get_info()); return has_actor_timeout(actor->get_info());
} }
inline double Scheduler::get_actor_timeout(const Actor *actor) const {
return get_actor_timeout(actor->get_info());
}
inline void Scheduler::set_actor_timeout_in(Actor *actor, double timeout) { inline void Scheduler::set_actor_timeout_in(Actor *actor, double timeout) {
set_actor_timeout_in(actor->get_info(), timeout); set_actor_timeout_in(actor->get_info(), timeout);
} }

View File

@ -20,7 +20,7 @@ struct HeapNode {
void remove() { void remove() {
pos_ = -1; pos_ = -1;
} }
int pos_ = -1; int32 pos_ = -1;
}; };
template <class KeyT, int K = 4> template <class KeyT, int K = 4>
@ -37,6 +37,12 @@ class KHeap {
return array_[0].key_; return array_[0].key_;
} }
KeyT get_key(const HeapNode *node) const {
size_t pos = static_cast<size_t>(node->pos_);
CHECK(pos < array_.size());
return array_[pos].key_;
}
const HeapNode *top() const { const HeapNode *top() const {
return array_[0].node_; return array_[0].node_;
} }
@ -45,19 +51,19 @@ class KHeap {
CHECK(!empty()); CHECK(!empty());
HeapNode *result = array_[0].node_; HeapNode *result = array_[0].node_;
result->remove(); result->remove();
erase(0); erase(static_cast<size_t>(0));
return result; return result;
} }
void insert(KeyT key, HeapNode *node) { void insert(KeyT key, HeapNode *node) {
CHECK(!node->in_heap()); CHECK(!node->in_heap());
array_.push_back({key, node}); array_.push_back({key, node});
fix_up(static_cast<int>(array_.size()) - 1); fix_up(array_.size() - 1);
} }
void fix(KeyT key, HeapNode *node) { void fix(KeyT key, HeapNode *node) {
CHECK(node->in_heap()); size_t pos = static_cast<size_t>(node->pos_);
int pos = node->pos_; CHECK(pos < array_.size());
KeyT old_key = array_[pos].key_; KeyT old_key = array_[pos].key_;
array_[pos].key_ = key; array_[pos].key_ = key;
if (key < old_key) { if (key < old_key) {
@ -68,9 +74,9 @@ class KHeap {
} }
void erase(HeapNode *node) { void erase(HeapNode *node) {
CHECK(node->in_heap()); size_t pos = static_cast<size_t>(node->pos_);
int pos = node->pos_;
node->remove(); node->remove();
CHECK(pos < array_.size());
erase(pos); erase(pos);
} }
@ -103,34 +109,34 @@ class KHeap {
}; };
vector<Item> array_; vector<Item> array_;
void fix_up(int pos) { void fix_up(size_t pos) {
auto item = array_[pos]; auto item = array_[pos];
while (pos) { while (pos) {
int parent_pos = (pos - 1) / K; auto parent_pos = (pos - 1) / K;
auto parent_item = array_[parent_pos]; auto parent_item = array_[parent_pos];
if (parent_item.key_ < item.key_) { if (parent_item.key_ < item.key_) {
break; break;
} }
parent_item.node_->pos_ = pos; parent_item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = parent_item; array_[pos] = parent_item;
pos = parent_pos; pos = parent_pos;
} }
item.node_->pos_ = pos; item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = item; array_[pos] = item;
} }
void fix_down(int pos) { void fix_down(size_t pos) {
auto item = array_[pos]; auto item = array_[pos];
while (true) { while (true) {
int left_pos = pos * K + 1; auto left_pos = pos * K + 1;
int right_pos = min(left_pos + K, static_cast<int>(array_.size())); auto right_pos = min(left_pos + K, array_.size());
int next_pos = pos; auto next_pos = pos;
KeyT next_key = item.key_; KeyT next_key = item.key_;
for (int i = left_pos; i < right_pos; i++) { for (auto i = left_pos; i < right_pos; i++) {
KeyT i_key = array_[i].key_; KeyT i_key = array_[i].key_;
if (i_key < next_key) { if (i_key < next_key) {
next_key = i_key; next_key = i_key;
@ -141,21 +147,24 @@ class KHeap {
break; break;
} }
array_[pos] = array_[next_pos]; array_[pos] = array_[next_pos];
array_[pos].node_->pos_ = pos; array_[pos].node_->pos_ = static_cast<int32>(pos);
pos = next_pos; pos = next_pos;
} }
item.node_->pos_ = pos; item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = item; array_[pos] = item;
} }
void erase(int pos) { void erase(size_t pos) {
array_[pos] = array_.back(); array_[pos] = array_.back();
array_.pop_back(); array_.pop_back();
if (pos < static_cast<int>(array_.size())) { if (pos < array_.size()) {
fix_down(pos); fix_down(pos);
fix_up(pos); fix_up(pos);
} }
if (array_.capacity() > 50 && array_.size() < array_.capacity() / 4) {
array_.shrink_to_fit();
}
} }
}; };

View File

@ -6,6 +6,7 @@
// //
#include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.h"
#include "td/utils/algorithm.h"
#include "td/utils/common.h" #include "td/utils/common.h"
#include "td/utils/format.h" #include "td/utils/format.h"
#include "td/utils/logging.h" #include "td/utils/logging.h"
@ -17,6 +18,7 @@
#include "td/utils/utf8.h" #include "td/utils/utf8.h"
#include <algorithm> #include <algorithm>
#include <utility>
static void check_mention(const td::string &str, const td::vector<td::string> &expected) { static void check_mention(const td::string &str, const td::vector<td::string> &expected) {
auto result_slice = td::find_mentions(str); auto result_slice = td::find_mentions(str);
@ -172,6 +174,48 @@ TEST(MessageEntities, cashtag) {
check_cashtag(u8"\u2122$ABC\u2122", {"$ABC"}); check_cashtag(u8"\u2122$ABC\u2122", {"$ABC"});
} }
static void check_media_timestamp(const td::string &str, const td::vector<std::pair<td::string, td::int32>> &expected) {
auto result = td::transform(td::find_media_timestamps(str),
[](auto &&entity) { return std::make_pair(entity.first.str(), entity.second); });
if (result != expected) {
LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
<< td::tag("expected", td::format::as_array(expected));
}
}
TEST(MessageEntities, media_timestamp) {
check_media_timestamp("", {});
check_media_timestamp(":", {});
check_media_timestamp(":1", {});
check_media_timestamp("a:1", {});
check_media_timestamp("01", {});
check_media_timestamp("01:", {});
check_media_timestamp("01::", {});
check_media_timestamp("01::", {});
check_media_timestamp("a1:1a", {});
check_media_timestamp("a1::01a", {});
check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
check_media_timestamp("0:00", {{"0:00", 0}});
check_media_timestamp("+0:00", {{"0:00", 0}});
check_media_timestamp("0:00+", {{"0:00", 0}});
check_media_timestamp("a0:00", {});
check_media_timestamp("0:00a", {});
check_media_timestamp("б0:00", {});
check_media_timestamp("0:00б", {});
check_media_timestamp("_0:00", {});
check_media_timestamp("0:00_", {});
check_media_timestamp("00:00:00:00", {});
check_media_timestamp("1:1:01 1:1:1", {{"1:1:01", 3661}});
check_media_timestamp("0:0:00 00:00 000:00 0000:00 00000:00 00:00:00 000:00:00 00:000:00 00:00:000",
{{"0:0:00", 0}, {"00:00", 0}, {"000:00", 0}, {"0000:00", 0}, {"00:00:00", 0}});
check_media_timestamp("00:0:00 0:00:00 00::00 :00:00 00:00: 00:00:0 00:00:", {{"00:0:00", 0}, {"0:00:00", 0}});
check_media_timestamp("1:1:59 1:1:-1 1:1:60", {{"1:1:59", 3719}});
check_media_timestamp("1:59:00 1:-1:00 1:60:00", {{"1:59:00", 7140}, {"1:00", 60}});
check_media_timestamp("59:59 60:00", {{"59:59", 3599}, {"60:00", 3600}});
check_media_timestamp("9999:59 99:59:59 99:60:59", {{"9999:59", 599999}, {"99:59:59", 360000 - 1}});
check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
}
static void check_bank_card_number(const td::string &str, const td::vector<td::string> &expected) { static void check_bank_card_number(const td::string &str, const td::vector<td::string> &expected) {
auto result_slice = td::find_bank_card_numbers(str); auto result_slice = td::find_bank_card_numbers(str);
td::vector<td::string> result; td::vector<td::string> result;
@ -655,16 +699,16 @@ static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntit
const td::vector<td::MessageEntity> &expected_entities, bool allow_empty = true, const td::vector<td::MessageEntity> &expected_entities, bool allow_empty = true,
bool skip_new_entities = false, bool skip_bot_commands = false, bool skip_new_entities = false, bool skip_bot_commands = false,
bool for_draft = true) { bool for_draft = true) {
ASSERT_TRUE( ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, for_draft).is_ok()); .is_ok());
ASSERT_STREQ(expected_str, str); ASSERT_STREQ(expected_str, str);
ASSERT_EQ(expected_entities, entities); ASSERT_EQ(expected_entities, entities);
} }
static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities, bool allow_empty, static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities, bool allow_empty,
bool skip_new_entities, bool skip_bot_commands, bool for_draft) { bool skip_new_entities, bool skip_bot_commands, bool for_draft) {
ASSERT_TRUE( ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, for_draft).is_error()); .is_error());
} }
TEST(MessageEntities, fix_formatted_text) { TEST(MessageEntities, fix_formatted_text) {
@ -1064,7 +1108,7 @@ TEST(MessageEntities, fix_formatted_text) {
return result; return result;
}; };
auto old_type_mask = get_type_mask(str.size(), entities); auto old_type_mask = get_type_mask(str.size(), entities);
ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, false).is_ok()); ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, true, false).is_ok());
auto new_type_mask = get_type_mask(str.size(), entities); auto new_type_mask = get_type_mask(str.size(), entities);
auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15); auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15);
auto pre_mask = (1 << 7) | (1 << 8) | (1 << 9); auto pre_mask = (1 << 7) | (1 << 8) | (1 << 9);
@ -1384,7 +1428,7 @@ static void check_parse_markdown_v3(td::string text, td::vector<td::MessageEntit
bool fix = false) { bool fix = false) {
auto parsed_text = td::parse_markdown_v3({std::move(text), std::move(entities)}); auto parsed_text = td::parse_markdown_v3({std::move(text), std::move(entities)});
if (fix) { if (fix) {
ASSERT_TRUE(fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).is_ok()); ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
} }
ASSERT_STREQ(result_text, parsed_text.text); ASSERT_STREQ(result_text, parsed_text.text);
ASSERT_EQ(result_entities, parsed_text.entities); ASSERT_EQ(result_entities, parsed_text.entities);
@ -1676,9 +1720,9 @@ TEST(MessageEntities, parse_markdown_v3) {
td::FormattedText text{std::move(str), std::move(entities)}; td::FormattedText text{std::move(str), std::move(entities)};
while (true) { while (true) {
ASSERT_TRUE(fix_formatted_text(text.text, text.entities, true, true, true, true).is_ok()); ASSERT_TRUE(td::fix_formatted_text(text.text, text.entities, true, true, true, true, true).is_ok());
auto parsed_text = td::parse_markdown_v3(text); auto parsed_text = td::parse_markdown_v3(text);
ASSERT_TRUE(fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true).is_ok()); ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
if (parsed_text == text) { if (parsed_text == text) {
break; break;
} }