diff --git a/CMakeLists.txt b/CMakeLists.txt index b9f541d6..a135d0c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) -project(TDLib VERSION 1.0.6 LANGUAGES CXX C) +project(TDLib VERSION 1.0.7 LANGUAGES CXX C) option(TD_ENABLE_JNI "Use \"ON\" to enable JNI-compatible TDLib API.") diff --git a/README.md b/README.md index 71bfdc41..d1e8057b 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,9 @@ cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/ .. C:\src\vcpkg> .\vcpkg install openssl zlib ``` * Build `TDLib` with CMake as explained in [building](#building), but instead of `cmake -DCMAKE_BUILD_TYPE=Release ..` use - ```cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=C:\src\vcpkg\scripts\buildsystems\vcpkg.cmake ..```. + ``` + cmake -DCMAKE_TOOLCHAIN_FILE=C:\src\vcpkg\scripts\buildsystems\vcpkg.cmake .. + ``` #### Linux * Install all dependencies using your package manager. diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index c66d3236..b9e6fe3c 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.0.6 REQUIRED) +find_package(Td 1.0.7 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/cpp/td_example.cpp b/example/cpp/td_example.cpp index 4724d769..36e3ca3a 100644 --- a/example/cpp/td_example.cpp +++ b/example/cpp/td_example.cpp @@ -100,7 +100,8 @@ class TdExample { auto send_message = td_api::make_object(); send_message->chat_id_ = chat_id; auto message_content = td_api::make_object(); - message_content->text_ = std::move(text); + message_content->text_ = td_api::make_object(); + message_content->text_->text_ = std::move(text); send_message->input_message_content_ = std::move(message_content); send_query(std::move(send_message), {}); @@ -194,7 +195,7 @@ class TdExample { auto sender_user_name = get_user_name(update_new_message.message_->sender_user_id_); std::string text; if (update_new_message.message_->content_->get_id() == td_api::messageText::ID) { - text = static_cast(*update_new_message.message_->content_).text_; + text = static_cast(*update_new_message.message_->content_).text_->text_; } std::cerr << "Got message: [chat_id:" << chat_id << "] [from:" << sender_user_name << "] [" << text << "]" << std::endl; diff --git a/example/java/org/drinkless/tdlib/example/Example.java b/example/java/org/drinkless/tdlib/example/Example.java index af689b7c..bf5b30f9 100644 --- a/example/java/org/drinkless/tdlib/example/Example.java +++ b/example/java/org/drinkless/tdlib/example/Example.java @@ -258,7 +258,7 @@ public final class Example { TdApi.InlineKeyboardButton[] row = {new TdApi.InlineKeyboardButton("https://telegram.org?1", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?2", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?3", new TdApi.InlineKeyboardButtonTypeUrl())}; TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][]{row, row, row}); - TdApi.InputMessageContent content = new TdApi.InputMessageText(message, false, true, null, null); + TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), false, true); client.send(new TdApi.SendMessage(chatId, 0, false, false, replyMarkup, content), defaultHandler); } diff --git a/td/generate/DoxygenTlDocumentationGenerator.php b/td/generate/DoxygenTlDocumentationGenerator.php index 0e7332f9..11cefcbc 100644 --- a/td/generate/DoxygenTlDocumentationGenerator.php +++ b/td/generate/DoxygenTlDocumentationGenerator.php @@ -180,8 +180,10 @@ EOT * Usage example: * \\code * auto get_authorization_state_request = td::td_api::make_object(); + * auto message_text = td::td_api::make_object("Hello, world!!!", + * std::vector>()); * auto send_message_request = td::td_api::make_object(chat_id, 0, false, false, nullptr, - * td::td_api::make_object("Hello, world!!!", false, true, {}, nullptr)); + * td::td_api::make_object(std::move(message_text), false, true)); * \\endcode * * \\tparam Type Type of an object to construct. diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 27027e5c..344040b2 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -176,6 +176,12 @@ maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPo //@description Represents a part of the text that needs to be formatted in some unusual way @offset Offset of the entity in UTF-16 code points @length Length of the entity, in UTF-16 code points @type Type of the entity textEntity offset:int32 length:int32 type:TextEntityType = TextEntity; +//@description Contains a list of text entities @entities List of text entities +textEntities entities:vector = TextEntities; + +//@description A text with some entities @text The text @entities Entities contained in the text +formattedText text:string entities:vector = FormattedText; + //@description Describes an animation file. The animation must be encoded in GIF or MPEG4 format @duration Duration of the animation, in seconds; as defined by the sender @width Width of the animation @height Height of the animation //@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file, usually "image/gif" or "video/mp4" @thumbnail Animation thumbnail; may be null @animation File containing the animation @@ -218,8 +224,8 @@ location latitude:double longitude:double = Location; venue location:location title:string address:string provider:string id:string = Venue; //@description Describes a game @id Game ID @short_name Game short name. To share a game use the URL https://t.me/{bot_username}?game={game_short_name} @title Game title @text Game text, usually containing scoreboards for a game -//@text_entities Entities contained in the text @param_description Game description @photo Game photo @animation Game animation; may be null -game id:int64 short_name:string title:string text:string text_entities:vector description:string photo:photo animation:animation = Game; +//@param_description Game description @photo Game photo @animation Game animation; may be null +game id:int64 short_name:string title:string text:formattedText description:string photo:photo animation:animation = Game; //@description Describes a user profile photo @id Photo identifier; 0 for an empty photo. Can be used to find a photo in a list of userProfilePhotos @@ -527,7 +533,7 @@ chatTypeSecret secret_chat_id:int32 user_id:int32 = ChatType; //@unread_mention_count Number of unread messages with a mention/reply in the chat //@notification_settings Notification settings for this chat //@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat -//@draft_message A draft of a message in the chat; may be null. parse_mode in input_message_text will always be null +//@draft_message A draft of a message in the chat; may be null //@client_data Contains client-specific data associated with the chat. (For example, the chat position or local chat notification settings can be stored here.) Persistent if a message database is used chat id:int53 type:ChatType title:string photo:chatPhoto last_message:message order:int64 is_pinned:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:notificationSettings reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; @@ -791,20 +797,20 @@ paymentReceipt date:int32 payments_provider_user_id:int32 invoice:invoice order_ //@class MessageContent @description Contains the content of a message -//@description A text message @text Text of the message @entities Entities contained in the text @web_page A preview of the web page that's mentioned in the text; may be null -messageText text:string entities:vector web_page:webPage = MessageContent; +//@description A text message @text Text of the message @web_page A preview of the web page that's mentioned in the text; may be null +messageText text:formattedText web_page:webPage = MessageContent; //@description An animation message (GIF-style). @animation Message content @caption Animation caption -messageAnimation animation:animation caption:string = MessageContent; +messageAnimation animation:animation caption:formattedText = MessageContent; //@description An audio message @audio Message content @caption Audio caption -messageAudio audio:audio caption:string = MessageContent; +messageAudio audio:audio caption:formattedText = MessageContent; //@description A document message (general file) @document Message content @caption Document caption -messageDocument document:document caption:string = MessageContent; +messageDocument document:document caption:formattedText = MessageContent; //@description A photo message @photo Message content @caption Photo caption -messagePhoto photo:photo caption:string = MessageContent; +messagePhoto photo:photo caption:formattedText = MessageContent; //@description An expired photo message (self-destructed after TTL has elapsed) messageExpiredPhoto = MessageContent; @@ -813,7 +819,7 @@ messageExpiredPhoto = MessageContent; messageSticker sticker:sticker = MessageContent; //@description A video message @video Message content @caption Video caption -messageVideo video:video caption:string = MessageContent; +messageVideo video:video caption:formattedText = MessageContent; //@description An expired video message (self-destructed after TTL has elapsed) messageExpiredVideo = MessageContent; @@ -822,7 +828,7 @@ messageExpiredVideo = MessageContent; messageVideoNote video_note:videoNote is_viewed:Bool = MessageContent; //@description A voice note message @voice_note Message content @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note -messageVoiceNote voice_note:voiceNote caption:string is_listened:Bool = MessageContent; +messageVoiceNote voice_note:voiceNote caption:formattedText is_listened:Bool = MessageContent; //@description A message with a location @location Message content @live_period Time relative to the message sent date until which the location can be updated, in seconds messageLocation location:location live_period:int32 = MessageContent; @@ -943,19 +949,6 @@ textEntityTypeTextUrl url:string = TextEntityType; textEntityTypeMentionName user_id:int32 = TextEntityType; -//@class TextParseMode @description Describes the way the text should be parsed for MessageEntities. By default, text is treated as-is - -//@description The text should be parsed in markdown-style -textParseModeMarkdown = TextParseMode; - -//@description The text should be parsed in HTML-style -textParseModeHTML = TextParseMode; - - -//@description Contains a list of text entities @entities List of text entities -textEntities entities:vector = TextEntities; - - //@description A thumbnail to be sent along with a file; should 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 90. Use 0 if unknown @height Thumbnail height, usually shouldn't exceed 90. Use 0 if unknown inputThumbnail thumbnail:InputFile width:int32 height:int32 = InputThumbnail; @@ -963,36 +956,36 @@ inputThumbnail thumbnail:InputFile width:int32 height:int32 = InputThumbnail; //@class InputMessageContent @description The content of a message to send -//@description A text message @text Text to be sent @disable_web_page_preview True, if rich web page previews for URLs in the message text should be disabled @clear_draft True, if a chat message draft should be deleted -//@entities Bold, Italic, Code, Pre, PreCode and TextUrl entities contained in the text. Can't be used with a non-null parse_mode @parse_mode Text parse mode; may be null. Can't be used in combination with enitities -inputMessageText text:string disable_web_page_preview:Bool clear_draft:Bool entities:vector parse_mode:TextParseMode = InputMessageContent; +//@description A text message @text Formatted text to be sent. Only Bold, Italic, Code, Pre, PreCode and TextUrl entities are allowed to be specified manually +//@disable_web_page_preview True, if rich web page previews for URLs in the message text should be disabled @clear_draft True, if a chat message draft should be deleted +inputMessageText text:formattedText disable_web_page_preview:Bool clear_draft:Bool = InputMessageContent; //@description An animation message (GIF-style). @animation Animation file to be sent @thumbnail Animation thumbnail, if available @duration Duration of the animation, in seconds @width Width of the animation; may be replaced by the server @height Height of the animation; may be replaced by the server @caption Animation caption; 0-200 characters -inputMessageAnimation animation:InputFile thumbnail:inputThumbnail duration:int32 width:int32 height:int32 caption:string = InputMessageContent; +inputMessageAnimation animation:InputFile thumbnail:inputThumbnail duration:int32 width:int32 height:int32 caption:formattedText = InputMessageContent; //@description An audio message @audio Audio file to be sent @album_cover_thumbnail Thumbnail of the cover for the album, if available @duration Duration of the audio, in seconds; may be replaced by the server @title Title of the audio; 0-64 characters; may be replaced by the server //@performer Performer of the audio; 0-64 characters, may be replaced by the server @caption Audio caption; 0-200 characters -inputMessageAudio audio:InputFile album_cover_thumbnail:inputThumbnail duration:int32 title:string performer:string caption:string = InputMessageContent; +inputMessageAudio audio:InputFile album_cover_thumbnail:inputThumbnail duration:int32 title:string performer:string caption:formattedText = InputMessageContent; //@description A document message (general file) @document Document to be sent @thumbnail Document thumbnail, if available @caption Document caption; 0-200 characters -inputMessageDocument document:InputFile thumbnail:inputThumbnail caption:string = InputMessageContent; +inputMessageDocument document:InputFile thumbnail:inputThumbnail caption:formattedText = InputMessageContent; //@description A photo message @photo Photo to send @thumbnail Photo thumbnail to be sent, this is sent to the other party in secret chats only @added_sticker_file_ids File identifiers of the stickers added to the photo, if applicable @width Photo width @height Photo height @caption Photo caption; 0-200 characters //@ttl Photo TTL (Time To Live), in seconds (0-60). A non-zero TTL can be specified only in private chats -inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector width:int32 height:int32 caption:string ttl:int32 = InputMessageContent; +inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector width:int32 height:int32 caption:formattedText ttl:int32 = InputMessageContent; //@description A sticker message @sticker Sticker to be sent @thumbnail Sticker thumbnail, if available @width Sticker width @height Sticker height inputMessageSticker sticker:InputFile thumbnail:inputThumbnail width:int32 height:int32 = InputMessageContent; //@description A video message @video Video to be sent @thumbnail Video thumbnail, if available @added_sticker_file_ids File identifiers of the stickers added to the video, if applicable @duration Duration of the video, in seconds @width Video width @height Video height @caption Video caption; 0-200 characters //@ttl Video TTL (Time To Live), in seconds (0-60). A non-zero TTL can be specified only in private chats -inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 caption:string ttl:int32 = InputMessageContent; +inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 caption:formattedText ttl:int32 = InputMessageContent; //@description A video note message @video_note Video note to be sent @thumbnail Video thumbnail, if available @duration Duration of the video, in seconds @length Video width and height; must be positive and not greater than 640 inputMessageVideoNote video_note:InputFile thumbnail:inputThumbnail duration:int32 length:int32 = InputMessageContent; //@description A voice note message @voice_note Voice note to be sent @duration Duration of the voice note, in seconds @waveform Waveform representation of the voice note, in 5-bit format @caption Voice note caption; 0-200 characters -inputMessageVoiceNote voice_note:InputFile duration:int32 waveform:bytes caption:string = InputMessageContent; +inputMessageVoiceNote voice_note:InputFile duration:int32 waveform:bytes caption:formattedText = InputMessageContent; //@description A message with a location @location Location to be sent @live_period Period for which the location can be updated, in seconds; should bebetween 60 and 86400 for a live location and 0 otherwise inputMessageLocation location:location live_period:int32 = InputMessageContent; @@ -1711,6 +1704,15 @@ count count:int32 = Count; text text:string = Text; +//@class TextParseMode @description Describes the way the text should be parsed for TextEntities + +//@description The text should be parsed in markdown-style +textParseModeMarkdown = TextParseMode; + +//@description The text should be parsed in HTML-style +textParseModeHTML = TextParseMode; + + //@class Proxy @description Contains information about a proxy server //@description An empty proxy server @@ -2169,7 +2171,7 @@ editMessageLiveLocation chat_id:int53 message_id:int53 reply_markup:ReplyMarkup //@description Edits the message content caption. Non-bots can edit messages for a limited period of time. Returns the edited message after the edit is completed server-side //@chat_id The chat the message belongs to @message_id Identifier of the message @reply_markup The new message reply markup; for bots only @caption New message content caption; 0-200 characters -editMessageCaption chat_id:int53 message_id:int53 reply_markup:ReplyMarkup caption:string = Message; +editMessageCaption chat_id:int53 message_id:int53 reply_markup:ReplyMarkup caption:formattedText = Message; //@description Edits the message reply markup; for bots only. Returns the edited message after the edit is completed server-side //@chat_id The chat the message belongs to @message_id Identifier of the message @reply_markup New message reply markup @@ -2182,7 +2184,7 @@ editInlineMessageText inline_message_id:string reply_markup:ReplyMarkup input_me editInlineMessageLiveLocation inline_message_id:string reply_markup:ReplyMarkup location:location = Ok; //@description Edits the caption of an inline message sent via a bot; for bots only @inline_message_id Inline message identifier @reply_markup New message reply markup @caption New message content caption; 0-200 characters -editInlineMessageCaption inline_message_id:string reply_markup:ReplyMarkup caption:string = Ok; +editInlineMessageCaption inline_message_id:string reply_markup:ReplyMarkup caption:formattedText = Ok; //@description Edits the reply markup of an inline message sent via a bot; for bots only @inline_message_id Inline message identifier @reply_markup New message reply markup editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = Ok; @@ -2191,6 +2193,9 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = //@description Returns all entities (mentions, hashtags, bot commands, URLs, and email addresses) contained in the text. This is an offline method. May be called before authorization. Can be called synchronously @text The text in which to look for entites getTextEntities text:string = TextEntities; +//@description Parses Bold, Italic, Code, Pre, PreCode and TextUrl entities contained in the text. This is an offline method. May be called before authorization. Can be called synchronously @text The text which should be parsed @parse_mode Text parse mode +parseTextEntities text:string parse_mode:TextParseMode = FormattedText; + //@description Returns the MIME type of a file, guessed by its extension. Returns an empty string on failure. This is an offline method. May be called before authorization. Can be called synchronously @file_name The name of the file or path to the file getFileMimeType file_name:string = Text; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 53e52e8e..7102ad0b 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index a1718867..c9a66f89 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -40,16 +40,16 @@ inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile; inputMediaEmpty#9664f57f = InputMedia; -inputMediaUploadedPhoto#2f37e231 flags:# file:InputFile caption:string stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaPhoto#81fa373a flags:# id:InputPhoto caption:string ttl_seconds:flags.0?int = InputMedia; +inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia; -inputMediaUploadedDocument#e39621fd flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector caption:string stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaDocument#5acb668e flags:# id:InputDocument caption:string ttl_seconds:flags.0?int = InputMedia; +inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; -inputMediaPhotoExternal#922aec1 flags:# url:string caption:string ttl_seconds:flags.0?int = InputMedia; -inputMediaDocumentExternal#b6f74335 flags:# url:string caption:string ttl_seconds:flags.0?int = InputMedia; +inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#7b1a118f geo_point:InputGeoPoint period:int = InputMedia; @@ -125,11 +125,11 @@ message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:fl messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; -messageMediaPhoto#b5223b0f flags:# photo:flags.0?Photo caption:flags.1?string ttl_seconds:flags.2?int = MessageMedia; +messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#7c4414d3 flags:# document:flags.0?Document caption:flags.1?string ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; @@ -570,7 +570,7 @@ messages.foundGifs#450a1c0a next_offset:int results:Vector = messages. messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; -inputBotInlineMessageMediaAuto#292fed13 flags:# caption:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; @@ -582,7 +582,7 @@ inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_m inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; -botInlineMessageMediaAuto#a74b15b flags:# caption:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; @@ -820,7 +820,7 @@ recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl; help.recentMeUrls#e0310d7 urls:Vector chats:Vector users:Vector = help.RecentMeUrls; -inputSingleMedia#5eaa7809 media:InputMedia random_id:long = InputSingleMedia; +inputSingleMedia#31bc3d25 media:InputMedia flags:# random_id:long message:string entities:flags.0?Vector = InputSingleMedia; ---functions--- @@ -905,7 +905,7 @@ messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = me messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; -messages.sendMedia#c8f16791 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long reply_markup:flags.2?ReplyMarkup = Updates; +messages.sendMedia#b8d1262b flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.hideReportSpam#a8f1709b peer:InputPeer = Bool; @@ -917,7 +917,6 @@ messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates; messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates; messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates; messages.createChat#9cb126e users:Vector title:string = Updates; -messages.forwardMessage#33963bf9 peer:InputPeer id:int random_id:long = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; @@ -930,8 +929,9 @@ messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long da messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages; +messages.getStickers#ae22e045 emoticon:string hash:string = messages.Stickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; -messages.getWebPagePreview#25223e24 message:string = MessageMedia; +messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; diff --git a/td/generate/scheme/telegram_api.tlo b/td/generate/scheme/telegram_api.tlo index 34955d68..8b9807d8 100644 Binary files a/td/generate/scheme/telegram_api.tlo and b/td/generate/scheme/telegram_api.tlo differ diff --git a/td/telegram/AnimationsManager.cpp b/td/telegram/AnimationsManager.cpp index 75ac3ad2..ef5ec58f 100644 --- a/td/telegram/AnimationsManager.cpp +++ b/td/telegram/AnimationsManager.cpp @@ -257,17 +257,16 @@ void AnimationsManager::create_animation(FileId file_id, PhotoSize thumbnail, st tl_object_ptr AnimationsManager::get_input_media( FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, const string &caption) const { + tl_object_ptr input_thumbnail) const { auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return nullptr; } if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return make_tl_object(0, file_view.remote_location().as_input_document(), caption, - 0); + return make_tl_object(0, file_view.remote_location().as_input_document(), 0); } if (file_view.has_url()) { - return make_tl_object(0, file_view.url(), caption, 0); + return make_tl_object(0, file_view.url(), 0); } CHECK(!file_view.has_remote_location()); @@ -296,7 +295,7 @@ tl_object_ptr AnimationsManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes), - caption, vector>(), 0); + vector>(), 0); } return nullptr; diff --git a/td/telegram/AnimationsManager.h b/td/telegram/AnimationsManager.h index 816b6b71..17fce985 100644 --- a/td/telegram/AnimationsManager.h +++ b/td/telegram/AnimationsManager.h @@ -42,8 +42,7 @@ class AnimationsManager : public Actor { tl_object_ptr get_input_media(FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, - const string &caption) const; + tl_object_ptr input_thumbnail) const; SecretInputMedia get_secret_input_media(FileId animation_file_id, tl_object_ptr input_file, diff --git a/td/telegram/AudiosManager.cpp b/td/telegram/AudiosManager.cpp index d539bb8d..9fa5df4d 100644 --- a/td/telegram/AudiosManager.cpp +++ b/td/telegram/AudiosManager.cpp @@ -220,17 +220,16 @@ SecretInputMedia AudiosManager::get_secret_input_media(FileId audio_file_id, tl_object_ptr AudiosManager::get_input_media( FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, const string &caption) const { + tl_object_ptr input_thumbnail) const { auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return nullptr; } if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return make_tl_object(0, file_view.remote_location().as_input_document(), caption, - 0); + return make_tl_object(0, file_view.remote_location().as_input_document(), 0); } if (file_view.has_url()) { - return make_tl_object(0, file_view.url(), caption, 0); + return make_tl_object(0, file_view.url(), 0); } CHECK(!file_view.has_remote_location()); @@ -255,7 +254,7 @@ tl_object_ptr AudiosManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes), - caption, vector>(), 0); + vector>(), 0); } return nullptr; diff --git a/td/telegram/AudiosManager.h b/td/telegram/AudiosManager.h index 369a7fca..68624cf4 100644 --- a/td/telegram/AudiosManager.h +++ b/td/telegram/AudiosManager.h @@ -37,8 +37,7 @@ class AudiosManager { tl_object_ptr get_input_media(FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, - const string &caption) const; + tl_object_ptr input_thumbnail) const; SecretInputMedia get_secret_input_media(FileId audio_file_id, tl_object_ptr input_file, diff --git a/td/telegram/DocumentsManager.cpp b/td/telegram/DocumentsManager.cpp index 1c597997..16c8d399 100644 --- a/td/telegram/DocumentsManager.cpp +++ b/td/telegram/DocumentsManager.cpp @@ -375,17 +375,16 @@ SecretInputMedia DocumentsManager::get_secret_input_media(FileId document_file_i tl_object_ptr DocumentsManager::get_input_media( FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, const string &caption) const { + tl_object_ptr input_thumbnail) const { auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return nullptr; } if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return make_tl_object(0, file_view.remote_location().as_input_document(), caption, - 0); + return make_tl_object(0, file_view.remote_location().as_input_document(), 0); } if (file_view.has_url()) { - return make_tl_object(0, file_view.url(), caption, 0); + return make_tl_object(0, file_view.url(), 0); } CHECK(!file_view.has_remote_location()); @@ -403,7 +402,7 @@ tl_object_ptr DocumentsManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), document->mime_type, - std::move(attributes), caption, vector>(), 0); + std::move(attributes), vector>(), 0); } return nullptr; diff --git a/td/telegram/DocumentsManager.h b/td/telegram/DocumentsManager.h index 3e0eba72..f93831c1 100644 --- a/td/telegram/DocumentsManager.h +++ b/td/telegram/DocumentsManager.h @@ -75,8 +75,7 @@ class DocumentsManager { tl_object_ptr get_input_media(FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, - const string &caption) const; + tl_object_ptr input_thumbnail) const; FileId get_document_thumbnail_file_id(FileId file_id) const; diff --git a/td/telegram/Game.cpp b/td/telegram/Game.cpp index 6de5d8d3..000b4575 100644 --- a/td/telegram/Game.cpp +++ b/td/telegram/Game.cpp @@ -83,14 +83,13 @@ UserId Game::get_bot_user_id() const { return bot_user_id_; } -void Game::set_message_text(string text, vector &&entities) { +void Game::set_message_text(FormattedText &&text) { text_ = std::move(text); - entities_ = std::move(entities); } tl_object_ptr Game::get_game_object(const Td *td) const { return make_tl_object( - id_, short_name_, title_, text_, get_text_entities_object(entities_), description_, + id_, short_name_, title_, get_formatted_text_object(text_), description_, get_photo_object(td->file_manager_.get(), &photo_), td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object")); } @@ -105,8 +104,7 @@ tl_object_ptr Game::get_input_media_game(const Td bool operator==(const Game &lhs, const Game &rhs) { return lhs.id_ == rhs.id_ && lhs.access_hash_ == rhs.access_hash_ && lhs.bot_user_id_ == rhs.bot_user_id_ && lhs.short_name_ == rhs.short_name_ && lhs.title_ == rhs.title_ && lhs.description_ == rhs.description_ && - lhs.photo_ == rhs.photo_ && lhs.animation_file_id_ == rhs.animation_file_id_ && lhs.text_ == rhs.text_ && - lhs.entities_ == rhs.entities_; + lhs.photo_ == rhs.photo_ && lhs.animation_file_id_ == rhs.animation_file_id_ && lhs.text_ == rhs.text_; } bool operator!=(const Game &lhs, const Game &rhs) { diff --git a/td/telegram/Game.h b/td/telegram/Game.h index 91751d93..4afe3031 100644 --- a/td/telegram/Game.h +++ b/td/telegram/Game.h @@ -32,8 +32,7 @@ class Game { Photo photo_; FileId animation_file_id_; - string text_; - vector entities_; + FormattedText text_; friend bool operator==(const Game &lhs, const Game &rhs); friend bool operator!=(const Game &lhs, const Game &rhs); @@ -56,7 +55,7 @@ class Game { UserId get_bot_user_id() const; - void set_message_text(string text, vector &&entities); + void set_message_text(FormattedText &&text); tl_object_ptr get_game_object(const Td *td) const; diff --git a/td/telegram/Game.hpp b/td/telegram/Game.hpp index 9402f22c..ba5b773d 100644 --- a/td/telegram/Game.hpp +++ b/td/telegram/Game.hpp @@ -33,7 +33,6 @@ void Game::store(StorerT &storer) const { storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(animation_file_id_, storer); } store(text_, storer); - store(entities_, storer); } template @@ -56,7 +55,6 @@ void Game::parse(ParserT &parser) { animation_file_id_ = parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser); } parse(text_, parser); - parse(entities_, parser); } } // namespace td diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index 720302cc..9167d843 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -5,7 +5,6 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/InlineQueriesManager.h" - #include "td/telegram/td_api.h" #include "td/telegram/td_api.hpp" #include "td/telegram/telegram_api.h" @@ -210,6 +209,27 @@ string InlineQueriesManager::get_inline_message_id( return base64url_encode(serialize(*input_bot_inline_message_id)); } +Result InlineQueriesManager::process_input_caption( + td_api::object_ptr &&caption) const { + return td_->messages_manager_->process_input_caption(DialogId(), std::move(caption), true); +} + +tl_object_ptr +InlineQueriesManager::get_input_bot_inline_message_media_auto( + const FormattedText &caption, tl_object_ptr &&input_reply_markup) const { + int32 flags = 0; + if (input_reply_markup != nullptr) { + flags |= telegram_api::inputBotInlineMessageText::REPLY_MARKUP_MASK; + } + auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities); + if (!entities.empty()) { + flags |= telegram_api::inputBotInlineMessageText::ENTITIES_MASK; + } + + return make_tl_object(flags, caption.text, std::move(entities), + std::move(input_reply_markup)); +} + Result> InlineQueriesManager::get_inline_message( tl_object_ptr &&input_message_content, tl_object_ptr &&reply_markup_ptr, int32 allowed_media_content_id) const { @@ -231,12 +251,12 @@ Result> InlineQueriesManager: if (input_message_text.disable_web_page_preview) { flags |= telegram_api::inputBotInlineMessageText::NO_WEBPAGE_MASK; } - if (!input_message_text.entities.empty()) { + if (!input_message_text.text.entities.empty()) { flags |= telegram_api::inputBotInlineMessageText::ENTITIES_MASK; } return make_tl_object( - flags, false /*ignored*/, std::move(input_message_text.text), - get_input_message_entities(td_->contacts_manager_.get(), input_message_text.entities), + flags, false /*ignored*/, std::move(input_message_text.text.text), + get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities), std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageContact::ID) { @@ -255,61 +275,38 @@ Result> InlineQueriesManager: if (constructor_id == allowed_media_content_id) { if (constructor_id == td_api::inputMessageAnimation::ID) { auto input_message_animation = static_cast(input_message_content.get()); - if (!clean_input_string(input_message_animation->caption_)) { - return Status::Error(400, "Animation caption must be encoded in UTF-8"); - } - return make_tl_object( - flags, strip_empty_characters(input_message_animation->caption_, MAX_CAPTION_LENGTH), - std::move(input_reply_markup)); + TRY_RESULT(caption, process_input_caption(std::move(input_message_animation->caption_))); + return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageAudio::ID) { auto input_message_audio = static_cast(input_message_content.get()); - if (!clean_input_string(input_message_audio->caption_)) { - return Status::Error(400, "Audio caption must be encoded in UTF-8"); - } - return make_tl_object( - flags, strip_empty_characters(input_message_audio->caption_, MAX_CAPTION_LENGTH), - std::move(input_reply_markup)); + TRY_RESULT(caption, process_input_caption(std::move(input_message_audio->caption_))); + return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageDocument::ID) { auto input_message_document = static_cast(input_message_content.get()); - if (!clean_input_string(input_message_document->caption_)) { - return Status::Error(400, "Document caption must be encoded in UTF-8"); - } - return make_tl_object( - flags, strip_empty_characters(input_message_document->caption_, MAX_CAPTION_LENGTH), - std::move(input_reply_markup)); + TRY_RESULT(caption, process_input_caption(std::move(input_message_document->caption_))); + return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessagePhoto::ID) { auto input_message_photo = static_cast(input_message_content.get()); - if (!clean_input_string(input_message_photo->caption_)) { - return Status::Error(400, "Photo caption must be encoded in UTF-8"); - } - return make_tl_object( - flags, strip_empty_characters(input_message_photo->caption_, MAX_CAPTION_LENGTH), - std::move(input_reply_markup)); + TRY_RESULT(caption, process_input_caption(std::move(input_message_photo->caption_))); + return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageSticker::ID) { // auto input_message_sticker = static_cast(input_message_content.get()); - return make_tl_object(flags, "", std::move(input_reply_markup)); + return make_tl_object(flags, "", Auto(), + std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageVideo::ID) { auto input_message_video = static_cast(input_message_content.get()); - if (!clean_input_string(input_message_video->caption_)) { - return Status::Error(400, "Video caption must be encoded in UTF-8"); - } - return make_tl_object( - flags, strip_empty_characters(input_message_video->caption_, MAX_CAPTION_LENGTH), - std::move(input_reply_markup)); + TRY_RESULT(caption, process_input_caption(std::move(input_message_video->caption_))); + return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageVoiceNote::ID) { auto input_message_voice_note = static_cast(input_message_content.get()); - if (!clean_input_string(input_message_voice_note->caption_)) { - return Status::Error(400, "Voice note caption must be encoded in UTF-8"); - } - return make_tl_object( - flags, strip_empty_characters(input_message_voice_note->caption_, MAX_CAPTION_LENGTH), - std::move(input_reply_markup)); + TRY_RESULT(caption, process_input_caption(std::move(input_message_voice_note->caption_))); + return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup)); } } return Status::Error(400, "Unallowed inline message content type"); @@ -334,7 +331,7 @@ bool InlineQueriesManager::register_inline_message_content( auto inline_message_text = move_tl_object_as(inline_message); auto entities = get_message_entities(td_->contacts_manager_.get(), std::move(inline_message_text->entities_)); auto status = - MessagesManager::fix_text_message(inline_message_text->message_, entities, nullptr, false, true, true, false); + MessagesManager::fix_text_message(inline_message_text->message_, entities, false, true, true, false); if (status.is_error()) { LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText " << inline_message_text->message_; @@ -348,8 +345,8 @@ bool InlineQueriesManager::register_inline_message_content( web_page_id = td_->web_pages_manager_->get_web_page_by_url(get_first_url(inline_message_text->message_, entities)); } - message_content = - make_unique(std::move(inline_message_text->message_), std::move(entities), web_page_id); + message_content = make_unique( + FormattedText{std::move(inline_message_text->message_), std::move(entities)}, web_page_id); reply_markup = std::move(inline_message_text->reply_markup_); break; } @@ -382,40 +379,33 @@ bool InlineQueriesManager::register_inline_message_content( break; } case telegram_api::botInlineMessageMediaAuto::ID: { + auto input_message_media_auto = move_tl_object_as(inline_message); + auto caption = td_->messages_manager_->get_message_text(input_message_media_auto->message_, + std::move(input_message_media_auto->entities_), 0); + reply_markup = std::move(input_message_media_auto->reply_markup_); + if (allowed_media_content_id == td_api::inputMessageAnimation::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); - message_content = make_unique(file_id, std::move(input_message_media_auto->caption_)); - reply_markup = std::move(input_message_media_auto->reply_markup_); + message_content = make_unique(file_id, std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageAudio::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); - message_content = make_unique(file_id, std::move(input_message_media_auto->caption_)); - reply_markup = std::move(input_message_media_auto->reply_markup_); + message_content = make_unique(file_id, std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageDocument::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); - message_content = make_unique(file_id, std::move(input_message_media_auto->caption_)); - reply_markup = std::move(input_message_media_auto->reply_markup_); + message_content = make_unique(file_id, std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageGame::ID) { CHECK(game != nullptr); - auto input_message_media_auto = move_tl_object_as(inline_message); - // TODO game->set_short_name(std::move(input_message_media_auto->caption_)); + // TODO game->set_short_name(std::move(caption)); message_content = make_unique(std::move(*game)); - reply_markup = std::move(input_message_media_auto->reply_markup_); } else if (allowed_media_content_id == td_api::inputMessagePhoto::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); - message_content = make_unique(std::move(*photo), std::move(input_message_media_auto->caption_)); - reply_markup = std::move(input_message_media_auto->reply_markup_); + message_content = make_unique(std::move(*photo), std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageSticker::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); message_content = make_unique(file_id); - reply_markup = std::move(input_message_media_auto->reply_markup_); } else if (allowed_media_content_id == td_api::inputMessageVideo::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); - message_content = make_unique(file_id, std::move(input_message_media_auto->caption_)); - reply_markup = std::move(input_message_media_auto->reply_markup_); + message_content = make_unique(file_id, std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageVoiceNote::ID) { - auto input_message_media_auto = move_tl_object_as(inline_message); - message_content = make_unique(file_id, std::move(input_message_media_auto->caption_), true); - reply_markup = std::move(input_message_media_auto->reply_markup_); + message_content = make_unique(file_id, std::move(caption), true); + } else { + input_message_media_auto->reply_markup_ = std::move(reply_markup); + input_message_media_auto->message_ = std::move(caption.text); + inline_message = std::move(input_message_media_auto); } break; } @@ -1055,11 +1045,16 @@ tl_object_ptr copy(const td_api::venue &obj) { return make_tl_object(copy(obj.location_), obj.title_, obj.address_, obj.provider_, obj.id_); } +template <> +tl_object_ptr copy(const td_api::formattedText &obj) { + // there is no entities in the game text + return make_tl_object(obj.text_, vector>()); +} + template <> tl_object_ptr copy(const td_api::game &obj) { - return make_tl_object(obj.id_, obj.short_name_, obj.title_, obj.text_, - vector>(), obj.description_, copy(obj.photo_), - copy(obj.animation_)); + return make_tl_object(obj.id_, obj.short_name_, obj.title_, copy(obj.text_), obj.description_, + copy(obj.photo_), copy(obj.animation_)); } template <> diff --git a/td/telegram/InlineQueriesManager.h b/td/telegram/InlineQueriesManager.h index 1dd4897e..c7cc2eb1 100644 --- a/td/telegram/InlineQueriesManager.h +++ b/td/telegram/InlineQueriesManager.h @@ -16,6 +16,7 @@ #include "td/telegram/DialogId.h" #include "td/telegram/files/FileId.h" #include "td/telegram/Location.h" +#include "td/telegram/MessageEntity.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/Photo.h" #include "td/telegram/ReplyMarkup.h" @@ -78,7 +79,6 @@ class InlineQueriesManager : public Actor { private: static constexpr int32 MAX_RECENT_INLINE_BOTS = 20; // some reasonable value - static constexpr int32 MAX_CAPTION_LENGTH = 200; // server side limit static constexpr int32 INLINE_QUERY_DELAY_MS = 400; // server side limit static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_PHOTO = 1 << 0; @@ -86,6 +86,11 @@ class InlineQueriesManager : public Actor { static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_TITLE = 1 << 2; static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_DESCRIPTION = 1 << 3; + Result process_input_caption(td_api::object_ptr &&caption) const; + + tl_object_ptr get_input_bot_inline_message_media_auto( + const FormattedText &caption, tl_object_ptr &&input_reply_markup) const; + Result> get_inline_message( tl_object_ptr &&input_message_content, tl_object_ptr &&reply_markup_ptr, diff --git a/td/telegram/MessageEntity.cpp b/td/telegram/MessageEntity.cpp index 91889326..78ae9438 100644 --- a/td/telegram/MessageEntity.cpp +++ b/td/telegram/MessageEntity.cpp @@ -128,8 +128,6 @@ vector> get_text_entities_object(const vector< return result; } -// 0 means nothing - static bool is_word_character(uint32 a) { switch (get_unicode_simple_category(a)) { case UnicodeSimpleCategory::Letter: @@ -141,6 +139,10 @@ static bool is_word_character(uint32 a) { } } +td_api::object_ptr get_formatted_text_object(const FormattedText &text) { + return td_api::make_object(text.text, get_text_entities_object(text.entities)); +} + /* static bool is_word_boundary(uint32 a, uint32 b) { return is_word_character(a) ^ is_word_character(b); @@ -1573,6 +1575,75 @@ vector> get_input_secret_message_entiti return result; } +Result> get_message_entities(const ContactsManager *contacts_manager, + const vector> &input_entities) { + vector entities; + for (auto &entity : input_entities) { + if (entity == nullptr || entity->type_ == nullptr) { + continue; + } + + switch (entity->type_->get_id()) { + case td_api::textEntityTypeMention::ID: + return Status::Error(400, "EntityMention can't be used in outgoing messages"); + case td_api::textEntityTypeHashtag::ID: + return Status::Error(400, "EntityHashtag can't be used in outgoing messages"); + case td_api::textEntityTypeBotCommand::ID: + return Status::Error(400, "EntityBotCommand can't be used in outgoing messages"); + case td_api::textEntityTypeUrl::ID: + return Status::Error(400, "EntityUrl can't be used in outgoing messages"); + case td_api::textEntityTypeEmailAddress::ID: + return Status::Error(400, "EntityEmailAddress can't be used in outgoing messages"); + case td_api::textEntityTypeBold::ID: + entities.emplace_back(MessageEntity::Type::Bold, entity->offset_, entity->length_); + break; + case td_api::textEntityTypeItalic::ID: + entities.emplace_back(MessageEntity::Type::Italic, entity->offset_, entity->length_); + break; + case td_api::textEntityTypeCode::ID: + entities.emplace_back(MessageEntity::Type::Code, entity->offset_, entity->length_); + break; + case td_api::textEntityTypePre::ID: + entities.emplace_back(MessageEntity::Type::Pre, entity->offset_, entity->length_); + break; + case td_api::textEntityTypePreCode::ID: { + auto entity_pre_code = static_cast(entity->type_.get()); + if (!clean_input_string(entity_pre_code->language_)) { + return Status::Error(400, "MessageEntityPreCode.language must be encoded in UTF-8"); + } + entities.emplace_back(MessageEntity::Type::PreCode, entity->offset_, entity->length_, + entity_pre_code->language_); + break; + } + case td_api::textEntityTypeTextUrl::ID: { + auto entity_text_url = static_cast(entity->type_.get()); + if (!clean_input_string(entity_text_url->url_)) { + return Status::Error(400, "MessageEntityTextUrl.url must be encoded in UTF-8"); + } + auto r_http_url = parse_url(entity_text_url->url_); + if (r_http_url.is_error()) { + return Status::Error(400, PSTRING() << "Wrong message entity: " << r_http_url.error().message()); + } + entities.emplace_back(MessageEntity::Type::TextUrl, entity->offset_, entity->length_, + r_http_url.ok().get_url()); + break; + } + case td_api::textEntityTypeMentionName::ID: { + auto entity_mention_name = static_cast(entity->type_.get()); + UserId user_id(entity_mention_name->user_id_); + if (!contacts_manager->have_input_user(user_id)) { + return Status::Error(7, "Have no access to the user"); + } + entities.emplace_back(entity->offset_, entity->length_, user_id); + break; + } + default: + UNREACHABLE(); + } + } + return entities; +} + vector get_message_entities(const ContactsManager *contacts_manager, vector> &&server_entities) { vector entities; diff --git a/td/telegram/MessageEntity.h b/td/telegram/MessageEntity.h index bcebd03b..4e203405 100644 --- a/td/telegram/MessageEntity.h +++ b/td/telegram/MessageEntity.h @@ -24,6 +24,8 @@ namespace td { +class ContactsManager; + class MessageEntity { tl_object_ptr get_text_entity_type_object() const; @@ -104,10 +106,40 @@ class MessageEntity { StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity); +struct FormattedText { + string text; + vector entities; + + template + void store(StorerT &storer) const { + td::store(text, storer); + td::store(entities, storer); + } + + template + void parse(ParserT &parser) { + td::parse(text, parser); + td::parse(entities, parser); + } +}; + +inline bool operator==(const FormattedText &lhs, const FormattedText &rhs) { + return lhs.text == rhs.text && lhs.entities == rhs.entities; +} + +inline bool operator!=(const FormattedText &lhs, const FormattedText &rhs) { + return !(lhs == rhs); +} + const std::unordered_set &get_valid_short_usernames(); +Result> get_message_entities(const ContactsManager *contacts_manager, + const vector> &input_entities); + vector> get_text_entities_object(const vector &entities); +td_api::object_ptr get_formatted_text_object(const FormattedText &text); + // sorts entities, removes intersecting and empty entities void fix_entities(vector &entities); @@ -127,8 +159,6 @@ Result> parse_markdown(string &text); Result> parse_html(string &text); -class ContactsManager; - vector> get_input_message_entities(const ContactsManager *contacts_manager, const vector &entities); diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 76ed17fc..cd8029a8 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -5,7 +5,6 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/MessagesManager.h" - #include "td/telegram/secret_api.hpp" #include "td/telegram/td_api.hpp" #include "td/telegram/telegram_api.h" @@ -773,7 +772,7 @@ class SaveDraftMessageQuery : public Td::ResultHandler { if (draft_message->input_message_text.disable_web_page_preview) { flags |= MessagesManager::SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } - if (draft_message->input_message_text.entities.size()) { + if (draft_message->input_message_text.text.entities.size()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES; } } @@ -781,10 +780,10 @@ class SaveDraftMessageQuery : public Td::ResultHandler { dialog_id_ = dialog_id; send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_saveDraft( flags, false /*ignored*/, reply_to_message_id.get(), std::move(input_peer), - draft_message == nullptr ? "" : draft_message->input_message_text.text, - draft_message == nullptr - ? vector>() - : get_input_message_entities(td->contacts_manager_.get(), draft_message->input_message_text.entities))))); + draft_message == nullptr ? "" : draft_message->input_message_text.text.text, + draft_message == nullptr ? vector>() + : get_input_message_entities(td->contacts_manager_.get(), + draft_message->input_message_text.text.entities))))); } void on_result(uint64 id, BufferSlice packet) override { @@ -2084,8 +2083,10 @@ class SendMediaActor : public NetActorOnce { public: void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id, MessageId reply_to_message_id, - tl_object_ptr &&reply_markup, tl_object_ptr &&message, - int64 random_id, NetQueryRef *send_query_ref, uint64 sequence_dispatcher_id) { + tl_object_ptr &&reply_markup, + vector> &&entities, const string &message, + tl_object_ptr &&input_media, int64 random_id, NetQueryRef *send_query_ref, + uint64 sequence_dispatcher_id) { random_id_ = random_id; file_id_ = file_id; thumbnail_file_id_ = thumbnail_file_id; @@ -2096,10 +2097,14 @@ class SendMediaActor : public NetActorOnce { on_error(0, Status::Error(400, "Have no write access to the chat")); return; } + if (!entities.empty()) { + flags |= telegram_api::messages_sendMedia::ENTITIES_MASK; + } telegram_api::messages_sendMedia request(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), reply_to_message_id.get_server_message_id().get(), - std::move(message), random_id, std::move(reply_markup)); + std::move(input_media), message, random_id, std::move(reply_markup), + std::move(entities)); LOG(INFO) << "Send media: " << to_string(request); auto query = G()->net_query_creator().create(create_storer(request)); if (G()->shared_config().get_option_boolean("use_quick_ack")) { @@ -3206,7 +3211,7 @@ class GetChannelAdminLogQuery : public Td::ResultHandler { bool operator==(const InputMessageText &lhs, const InputMessageText &rhs) { return lhs.text == rhs.text && lhs.disable_web_page_preview == rhs.disable_web_page_preview && - lhs.clear_draft == rhs.clear_draft && lhs.entities == rhs.entities; + lhs.clear_draft == rhs.clear_draft; } bool operator!=(const InputMessageText &lhs, const InputMessageText &rhs) { @@ -3337,7 +3342,6 @@ static void store(const MessageContent *content, StorerT &storer) { case MessageText::ID: { auto m = static_cast(content); store(m->text, storer); - store(m->entities, storer); store(m->web_page_id, storer); break; } @@ -3494,6 +3498,16 @@ static void store(const MessageContent *content, StorerT &storer) { } } +template +static void parse_caption(FormattedText &caption, ParserT &parser) { + parse(caption.text, parser); + if (parser.version() >= static_cast(Version::AddCaptionEntities)) { + parse(caption.entities, parser); + } else { + caption.entities = find_entities(caption.text, false); + } +} + template static void parse(unique_ptr &content, ParserT &parser) { Td *td = parser.context()->td().get_actor_unsafe(); @@ -3507,7 +3521,7 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageAnimation::ID: { auto m = make_unique(); m->file_id = td->animations_manager_->parse_animation(parser); - parse(m->caption, parser); + parse_caption(m->caption, parser); is_bad = !m->file_id.is_valid(); content = std::move(m); break; @@ -3515,7 +3529,7 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageAudio::ID: { auto m = make_unique(); m->file_id = td->audios_manager_->parse_audio(parser); - parse(m->caption, parser); + parse_caption(m->caption, parser); bool legacy_is_listened; parse(legacy_is_listened, parser); is_bad = !m->file_id.is_valid(); @@ -3531,7 +3545,7 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageDocument::ID: { auto m = make_unique(); m->file_id = td->documents_manager_->parse_document(parser); - parse(m->caption, parser); + parse_caption(m->caption, parser); is_bad = !m->file_id.is_valid(); content = std::move(m); break; @@ -3582,7 +3596,7 @@ static void parse(unique_ptr &content, ParserT &parser) { is_bad = true; } } - parse(m->caption, parser); + parse_caption(m->caption, parser); content = std::move(m); break; } @@ -3596,7 +3610,6 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageText::ID: { auto m = make_unique(); parse(m->text, parser); - parse(m->entities, parser); parse(m->web_page_id, parser); content = std::move(m); break; @@ -3613,7 +3626,7 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageVideo::ID: { auto m = make_unique(); m->file_id = td->videos_manager_->parse_video(parser); - parse(m->caption, parser); + parse_caption(m->caption, parser); is_bad = !m->file_id.is_valid(); content = std::move(m); break; @@ -3629,7 +3642,7 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageVoiceNote::ID: { auto m = make_unique(); m->file_id = td->voice_notes_manager_->parse_voice_note(parser); - parse(m->caption, parser); + parse_caption(m->caption, parser); parse(m->is_listened, parser); is_bad = !m->file_id.is_valid(); content = std::move(m); @@ -4054,7 +4067,6 @@ void store(const InputMessageText &input_message_text, StorerT &storer) { STORE_FLAG(input_message_text.clear_draft); END_STORE_FLAGS(); store(input_message_text.text, storer); - store(input_message_text.entities, storer); } template @@ -4064,7 +4076,6 @@ void parse(InputMessageText &input_message_text, ParserT &parser) { PARSE_FLAG(input_message_text.clear_draft); END_PARSE_FLAGS(); parse(input_message_text.text, parser); - parse(input_message_text.entities, parser); } template @@ -4576,7 +4587,7 @@ int32 MessagesManager::get_message_content_index_mask(const MessageContent *cont return search_messages_filter_index_mask(SearchMessagesFilter::Photo) | search_messages_filter_index_mask(SearchMessagesFilter::PhotoAndVideo); case MessageText::ID: - for (auto &entity : static_cast(content)->entities) { + for (auto &entity : static_cast(content)->text.entities) { if (entity.type == MessageEntity::Type::Url || entity.type == MessageEntity::Type::EmailAddress) { return search_messages_filter_index_mask(SearchMessagesFilter::Url); } @@ -5065,10 +5076,10 @@ MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() { void MessagesManager::on_update_service_notification(tl_object_ptr &&update) { int32 ttl = 0; - auto content = - get_message_content(std::move(update->message_), std::move(update->media_), std::move(update->entities_), - td_->auth_manager_->is_bot() ? DialogId() : get_service_notifications_dialog()->dialog_id, - false, UserId(), &ttl, update->inbox_date_); + auto content = get_message_content( + get_message_text(std::move(update->message_), std::move(update->entities_), update->inbox_date_), + std::move(update->media_), + td_->auth_manager_->is_bot() ? DialogId() : get_service_notifications_dialog()->dialog_id, false, UserId(), &ttl); if ((update->flags_ & telegram_api::updateServiceNotification::POPUP_MASK) != 0) { send_closure( G()->td(), &Td::send_update, @@ -6881,31 +6892,32 @@ string MessagesManager::get_search_text(const Message *m) { case MessageText::ID: { auto *text = static_cast(m->content.get()); if (!text->web_page_id.is_valid()) { - return text->text; + return text->text.text; } - return PSTRING() << text->text << " " << td_->web_pages_manager_->get_web_page_search_text(text->web_page_id); + return PSTRING() << text->text.text << " " + << td_->web_pages_manager_->get_web_page_search_text(text->web_page_id); } case MessageAnimation::ID: { auto animation = static_cast(m->content.get()); return PSTRING() << td_->animations_manager_->get_animation_search_text(animation->file_id) << " " - << animation->caption; + << animation->caption.text; } case MessageAudio::ID: { auto audio = static_cast(m->content.get()); - return PSTRING() << td_->audios_manager_->get_audio_search_text(audio->file_id) << " " << audio->caption; + return PSTRING() << td_->audios_manager_->get_audio_search_text(audio->file_id) << " " << audio->caption.text; } case MessageDocument::ID: { auto document = static_cast(m->content.get()); return PSTRING() << td_->documents_manager_->get_document_search_text(document->file_id) << " " - << document->caption; + << document->caption.text; } case MessagePhoto::ID: { auto photo = static_cast(m->content.get()); - return PSTRING() << photo->caption; + return PSTRING() << photo->caption.text; } case MessageVideo::ID: { auto video = static_cast(m->content.get()); - return PSTRING() << td_->videos_manager_->get_video_search_text(video->file_id) << " " << video->caption; + return PSTRING() << td_->videos_manager_->get_video_search_text(video->file_id) << " " << video->caption.text; } case MessageContact::ID: case MessageGame::ID: @@ -8085,11 +8097,13 @@ tl_object_ptr MessagesManager::get_message_content_objec case MessageAnimation::ID: { const MessageAnimation *m = static_cast(content); return make_tl_object( - td_->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"), m->caption); + td_->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"), + get_formatted_text_object(m->caption)); } case MessageAudio::ID: { const MessageAudio *m = static_cast(content); - return make_tl_object(td_->audios_manager_->get_audio_object(m->file_id), m->caption); + return make_tl_object(td_->audios_manager_->get_audio_object(m->file_id), + get_formatted_text_object(m->caption)); } case MessageContact::ID: { const MessageContact *m = static_cast(content); @@ -8098,7 +8112,7 @@ tl_object_ptr MessagesManager::get_message_content_objec case MessageDocument::ID: { const MessageDocument *m = static_cast(content); return make_tl_object(td_->documents_manager_->get_document_object(m->file_id), - m->caption); + get_formatted_text_object(m->caption)); } case MessageGame::ID: { const MessageGame *m = static_cast(content); @@ -8121,7 +8135,8 @@ tl_object_ptr MessagesManager::get_message_content_objec } case MessagePhoto::ID: { const MessagePhoto *m = static_cast(content); - return make_tl_object(get_photo_object(td_->file_manager_.get(), &m->photo), m->caption); + return make_tl_object(get_photo_object(td_->file_manager_.get(), &m->photo), + get_formatted_text_object(m->caption)); } case MessageSticker::ID: { const MessageSticker *m = static_cast(content); @@ -8129,7 +8144,7 @@ tl_object_ptr MessagesManager::get_message_content_objec } case MessageText::ID: { const MessageText *m = static_cast(content); - return make_tl_object(m->text, get_text_entities_object(m->entities), + return make_tl_object(get_formatted_text_object(m->text), td_->web_pages_manager_->get_web_page_object(m->web_page_id)); } case MessageUnsupported::ID: { @@ -8141,7 +8156,8 @@ tl_object_ptr MessagesManager::get_message_content_objec } case MessageVideo::ID: { const MessageVideo *m = static_cast(content); - return make_tl_object(td_->videos_manager_->get_video_object(m->file_id), m->caption); + return make_tl_object(td_->videos_manager_->get_video_object(m->file_id), + get_formatted_text_object(m->caption)); } case MessageVideoNote::ID: { const MessageVideoNote *m = static_cast(content); @@ -8151,7 +8167,7 @@ tl_object_ptr MessagesManager::get_message_content_objec case MessageVoiceNote::ID: { const MessageVoiceNote *m = static_cast(content); return make_tl_object(td_->voice_notes_manager_->get_voice_note_object(m->file_id), - m->caption, m->is_listened); + get_formatted_text_object(m->caption), m->is_listened); } case MessageChatCreate::ID: { const MessageChatCreate *m = static_cast(content); @@ -8617,7 +8633,7 @@ void MessagesManager::start_up() { m->random_y = get_random_y(message_id); m->message_id = message_id; m->date = G()->unix_time(); - m->content = make_unique("text", vector(), WebPageId()); + m->content = make_unique(FormattedText{"text", vector()}, WebPageId()); m->have_previous = have_previous; m->have_next = have_next; @@ -9100,10 +9116,11 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( if (is_message_auto_read(message_info.dialog_id, (message->flags_ & MESSAGE_FLAG_IS_OUT) != 0, true)) { is_content_read = true; } - message_info.content = - get_message_content(std::move(message->message_), std::move(message->media_), std::move(message->entities_), - message_info.dialog_id, is_content_read, message_info.via_bot_user_id, &message_info.ttl, - message_info.forward_header ? message_info.forward_header->date_ : message_info.date); + message_info.content = get_message_content( + get_message_text(std::move(message->message_), std::move(message->entities_), + message_info.forward_header ? message_info.forward_header->date_ : message_info.date), + std::move(message->media_), message_info.dialog_id, is_content_read, message_info.via_bot_user_id, + &message_info.ttl); message_info.reply_markup = message->flags_ & MESSAGE_FLAG_HAS_REPLY_MARKUP ? std::move(message->reply_markup_) : nullptr; message_info.author_signature = std::move(message->post_author_); @@ -9627,9 +9644,9 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, } auto message_text = static_cast(m->content.get()); - auto new_content = get_message_content(message_text->text, std::move(message_media), std::move(entities), dialog_id, - true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/, - m->forward_info ? m->forward_info->date : m->date); + auto new_content = get_message_content( + get_message_text(message_text->text.text, std::move(entities), m->forward_info ? m->forward_info->date : m->date), + std::move(message_media), dialog_id, true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/); if (new_content->get_id() != MessageText::ID) { LOG(ERROR) << "Text message content has changed to " << new_content->get_id(); return; @@ -9639,7 +9656,7 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, bool need_update = false; bool is_content_changed = false; - if (message_text->entities != new_message_text->entities) { + if (message_text->text.entities != new_message_text->text.entities) { is_content_changed = true; need_update = true; } @@ -11096,7 +11113,7 @@ Status MessagesManager::set_dialog_draft_message(DialogId dialog_id, new_draft_message->input_message_text = std::move(message_content); } - if (!new_draft_message->reply_to_message_id.is_valid() && new_draft_message->input_message_text.text.empty()) { + if (!new_draft_message->reply_to_message_id.is_valid() && new_draft_message->input_message_text.text.text.empty()) { new_draft_message = nullptr; } } @@ -11862,9 +11879,9 @@ void MessagesManager::close_dialog(Dialog *d) { tl_object_ptr MessagesManager::get_input_message_text_object( const InputMessageText &input_message_text) const { - return make_tl_object(input_message_text.text, input_message_text.disable_web_page_preview, - input_message_text.clear_draft, - get_text_entities_object(input_message_text.entities), nullptr); + return make_tl_object(get_formatted_text_object(input_message_text.text), + input_message_text.disable_web_page_preview, + input_message_text.clear_draft); } tl_object_ptr MessagesManager::get_draft_message_object( @@ -12140,7 +12157,7 @@ unique_ptr MessagesManager::get_draft_message( } auto entities = get_message_entities(contacts_manager, std::move(draft->entities_)); - auto status = fix_text_message(draft->message_, entities, nullptr, true, true, true, true); + auto status = fix_text_message(draft->message_, entities, true, true, true, true); if (status.is_error()) { LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft->message_; if (!clean_input_string(draft->message_)) { @@ -12148,10 +12165,9 @@ unique_ptr MessagesManager::get_draft_message( } entities.clear(); } - result->input_message_text.text = std::move(draft->message_); + result->input_message_text.text = FormattedText{std::move(draft->message_), std::move(entities)}; result->input_message_text.disable_web_page_preview = (flags & SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW) != 0; result->input_message_text.clear_draft = false; - result->input_message_text.entities = std::move(entities); return result; } @@ -13488,32 +13504,33 @@ tl_object_ptr MessagesManager::get_messages_object( return td_api::make_object(total_count, std::move(messages)); } -// like clean_input_string but also validates entities -Status MessagesManager::fix_text_message(string &text, vector &entities, - tl_object_ptr &&parse_mode, bool allow_empty, - bool skip_new_entities, bool skip_bot_commands, bool for_draft) { - if (!check_utf8(text)) { - return Status::Error(400, "Message text must be encoded in UTF-8"); +bool MessagesManager::need_skip_bot_commands(DialogId dialog_id, bool is_bot) const { + if (is_bot) { + return false; } - if (parse_mode != nullptr) { - Result> r_entities; - switch (parse_mode->get_id()) { - case td_api::textParseModeHTML::ID: - r_entities = parse_html(text); - break; - case td_api::textParseModeMarkdown::ID: - r_entities = parse_markdown(text); - break; - default: - UNREACHABLE(); - break; - } - if (r_entities.is_error()) { - return Status::Error(400, PSLICE() << "Can't parse entities in message text: " << r_entities.error().message()); - } else { - entities = r_entities.move_as_ok(); + switch (dialog_id.get_type()) { + case DialogType::User: + return !td_->contacts_manager_->is_user_bot(dialog_id.get_user_id()); + case DialogType::SecretChat: { + auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + return !td_->contacts_manager_->is_user_bot(user_id); } + case DialogType::Chat: + case DialogType::Channel: + case DialogType::None: + return false; + default: + UNREACHABLE(); + return false; + } +} + +// like clean_input_string but also validates entities +Status MessagesManager::fix_text_message(string &text, vector &entities, bool allow_empty, + bool skip_new_entities, bool skip_bot_commands, bool for_draft) { + if (!check_utf8(text)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); } fix_entities(entities); @@ -13720,109 +13737,31 @@ Status MessagesManager::fix_text_message(string &text, vector &en } } - // TODO MAX_MESSAGE_LENGTH + // TODO MAX_MESSAGE_LENGTH and MAX_CAPTION_LENGTH return Status::OK(); } +Result MessagesManager::process_input_caption(DialogId dialog_id, + tl_object_ptr &&text, + bool is_bot) const { + TRY_RESULT(entities, get_message_entities(td_->contacts_manager_.get(), std::move(text->entities_))); + TRY_STATUS(fix_text_message(text->text_, entities, true, false, need_skip_bot_commands(dialog_id, is_bot), false)); + return FormattedText{std::move(text->text_), std::move(entities)}; +} + Result MessagesManager::process_input_message_text( DialogId dialog_id, tl_object_ptr &&input_message_content, bool is_bot, bool for_draft) const { CHECK(input_message_content != nullptr); CHECK(input_message_content->get_id() == td_api::inputMessageText::ID); auto input_message_text = static_cast(input_message_content.get()); - - vector entities; - for (auto &entity : input_message_text->entities_) { - if (entity == nullptr || entity->type_ == nullptr) { - continue; - } - - switch (entity->type_->get_id()) { - case td_api::textEntityTypeMention::ID: - return Status::Error(400, "EntityMention can't be used in outgoing messages"); - case td_api::textEntityTypeHashtag::ID: - return Status::Error(400, "EntityHashtag can't be used in outgoing messages"); - case td_api::textEntityTypeBotCommand::ID: - return Status::Error(400, "EntityBotCommand can't be used in outgoing messages"); - case td_api::textEntityTypeUrl::ID: - return Status::Error(400, "EntityUrl can't be used in outgoing messages"); - case td_api::textEntityTypeEmailAddress::ID: - return Status::Error(400, "EntityEmailAddress can't be used in outgoing messages"); - case td_api::textEntityTypeBold::ID: - entities.emplace_back(MessageEntity::Type::Bold, entity->offset_, entity->length_); - break; - case td_api::textEntityTypeItalic::ID: - entities.emplace_back(MessageEntity::Type::Italic, entity->offset_, entity->length_); - break; - case td_api::textEntityTypeCode::ID: - entities.emplace_back(MessageEntity::Type::Code, entity->offset_, entity->length_); - break; - case td_api::textEntityTypePre::ID: - entities.emplace_back(MessageEntity::Type::Pre, entity->offset_, entity->length_); - break; - case td_api::textEntityTypePreCode::ID: { - auto entity_pre_code = static_cast(entity->type_.get()); - if (!clean_input_string(entity_pre_code->language_)) { - return Status::Error(400, "MessageEntityPreCode.language must be encoded in UTF-8"); - } - entities.emplace_back(MessageEntity::Type::PreCode, entity->offset_, entity->length_, - entity_pre_code->language_); - break; - } - case td_api::textEntityTypeTextUrl::ID: { - auto entity_text_url = static_cast(entity->type_.get()); - if (!clean_input_string(entity_text_url->url_)) { - return Status::Error(400, "MessageEntityTextUrl.url must be encoded in UTF-8"); - } - auto r_http_url = parse_url(entity_text_url->url_); - if (r_http_url.is_error()) { - return Status::Error(400, PSTRING() << "Wrong message entity: " << r_http_url.error().message()); - } - entities.emplace_back(MessageEntity::Type::TextUrl, entity->offset_, entity->length_, - r_http_url.ok().get_url()); - break; - } - case td_api::textEntityTypeMentionName::ID: { - auto entity_mention_name = static_cast(entity->type_.get()); - UserId user_id(entity_mention_name->user_id_); - if (!td_->contacts_manager_->have_input_user(user_id)) { - return Status::Error(7, "Have no access to the user"); - } - entities.emplace_back(entity->offset_, entity->length_, user_id); - break; - } - default: - UNREACHABLE(); - } - } - - bool skip_bot_commands = false; - if (!is_bot) { - switch (dialog_id.get_type()) { - case DialogType::User: - if (!td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) { - skip_bot_commands = true; - } - break; - case DialogType::Chat: - break; - case DialogType::Channel: - break; - case DialogType::SecretChat: - skip_bot_commands = true; - break; - case DialogType::None: - break; - default: - UNREACHABLE(); - } - } - TRY_STATUS(fix_text_message(input_message_text->text_, entities, std::move(input_message_text->parse_mode_), - for_draft, false, skip_bot_commands, for_draft)); - - return InputMessageText{std::move(input_message_text->text_), input_message_text->disable_web_page_preview_, - input_message_text->clear_draft_, std::move(entities)}; + TRY_RESULT(entities, + get_message_entities(td_->contacts_manager_.get(), std::move(input_message_text->text_->entities_))); + TRY_STATUS(fix_text_message(input_message_text->text_->text_, entities, for_draft, false, + need_skip_bot_commands(dialog_id, is_bot), for_draft)); + return InputMessageText{FormattedText{std::move(input_message_text->text_->text_), std::move(entities)}, + input_message_text->disable_web_page_preview_, input_message_text->clear_draft_}; } Result> MessagesManager::process_input_message_location( @@ -13923,12 +13862,12 @@ SecretInputMedia MessagesManager::get_secret_input_media(const MessageContent *c switch (content->get_id()) { case MessageAnimation::ID: { auto m = static_cast(content); - return td_->animations_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption, + return td_->animations_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, std::move(thumbnail)); } case MessageAudio::ID: { auto m = static_cast(content); - return td_->audios_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption, + return td_->audios_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, std::move(thumbnail)); } case MessageContact::ID: { @@ -13937,7 +13876,7 @@ SecretInputMedia MessagesManager::get_secret_input_media(const MessageContent *c } case MessageDocument::ID: { auto m = static_cast(content); - return td_->documents_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption, + return td_->documents_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, std::move(thumbnail)); } case MessageLocation::ID: { @@ -13946,7 +13885,7 @@ SecretInputMedia MessagesManager::get_secret_input_media(const MessageContent *c } case MessagePhoto::ID: { auto m = static_cast(content); - return photo_get_secret_input_media(td_->file_manager_.get(), m->photo, std::move(input_file), m->caption, + return photo_get_secret_input_media(td_->file_manager_.get(), m->photo, std::move(input_file), m->caption.text, std::move(thumbnail)); } case MessageSticker::ID: { @@ -13965,7 +13904,7 @@ SecretInputMedia MessagesManager::get_secret_input_media(const MessageContent *c } case MessageVideo::ID: { auto m = static_cast(content); - return td_->videos_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption, + return td_->videos_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, std::move(thumbnail)); } case MessageVideoNote::ID: { @@ -13975,7 +13914,7 @@ SecretInputMedia MessagesManager::get_secret_input_media(const MessageContent *c } case MessageVoiceNote::ID: { auto m = static_cast(content); - return td_->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption); + return td_->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text); } case MessageLiveLocation::ID: case MessageGame::ID: @@ -14096,13 +14035,11 @@ tl_object_ptr MessagesManager::get_input_media( switch (content->get_id()) { case MessageAnimation::ID: { auto m = static_cast(content); - return td_->animations_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), - m->caption); + return td_->animations_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail)); } case MessageAudio::ID: { auto m = static_cast(content); - return td_->audios_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), - m->caption); + return td_->audios_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail)); } case MessageContact::ID: { auto m = static_cast(content); @@ -14110,8 +14047,7 @@ tl_object_ptr MessagesManager::get_input_media( } case MessageDocument::ID: { auto m = static_cast(content); - return td_->documents_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), - m->caption); + return td_->documents_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail)); } case MessageGame::ID: { auto m = static_cast(content); @@ -14131,7 +14067,7 @@ tl_object_ptr MessagesManager::get_input_media( } case MessagePhoto::ID: { auto m = static_cast(content); - return photo_get_input_media(td_->file_manager_.get(), m->photo, std::move(input_file), m->caption, ttl); + return photo_get_input_media(td_->file_manager_.get(), m->photo, std::move(input_file), ttl); } case MessageSticker::ID: { auto m = static_cast(content); @@ -14143,8 +14079,7 @@ tl_object_ptr MessagesManager::get_input_media( } case MessageVideo::ID: { auto m = static_cast(content); - return td_->videos_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), - m->caption, ttl); + return td_->videos_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), ttl); } case MessageVideoNote::ID: { auto m = static_cast(content); @@ -14152,7 +14087,7 @@ tl_object_ptr MessagesManager::get_input_media( } case MessageVoiceNote::ID: { auto m = static_cast(content); - return td_->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file), m->caption); + return td_->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file)); } case MessageText::ID: case MessageUnsupported::ID: @@ -14566,8 +14501,7 @@ MessageId MessagesManager::get_reply_to_message_id(Dialog *d, MessageId message_ return message_id; } -string MessagesManager::get_message_content_caption(const MessageContent *content) { - // TODO return with entities +FormattedText MessagesManager::get_message_content_caption(const MessageContent *content) { switch (content->get_id()) { case MessageAnimation::ID: return static_cast(content)->caption; @@ -14582,7 +14516,7 @@ string MessagesManager::get_message_content_caption(const MessageContent *conten case MessageVoiceNote::ID: return static_cast(content)->caption; default: - return string(); + return FormattedText(); } } @@ -14824,8 +14758,10 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, Dialo switch (m->content->get_id()) { case MessageText::ID: { auto content = static_cast(m->content.get()); - for (auto &entity : content->entities) { - dependencies.user_ids.insert(entity.user_id); + for (auto &entity : content->text.entities) { + if (entity.user_id.is_valid()) { + dependencies.user_ids.insert(entity.user_id); + } } dependencies.web_page_ids.insert(content->web_page_id); break; @@ -15198,10 +15134,10 @@ Result MessagesManager::process_input_mess unique_ptr content; UserId via_bot_user_id; int32 ttl = 0; + bool is_bot = td_->auth_manager_->is_bot(); switch (message_type) { case td_api::inputMessageText::ID: { - TRY_RESULT(input_message_text, - process_input_message_text(dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot())); + TRY_RESULT(input_message_text, process_input_message_text(dialog_id, std::move(input_message_content), is_bot)); disable_web_page_preview = input_message_text.disable_web_page_preview; clear_draft = input_message_text.clear_draft; @@ -15210,25 +15146,21 @@ Result MessagesManager::process_input_mess (dialog_id.get_type() != DialogType::Channel || td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_add_web_page_previews())) { web_page_id = td_->web_pages_manager_->get_web_page_by_url( - get_first_url(input_message_text.text, input_message_text.entities)); + get_first_url(input_message_text.text.text, input_message_text.text.entities)); } - content = make_unique(std::move(input_message_text.text), std::move(input_message_text.entities), - web_page_id); + content = make_unique(std::move(input_message_text.text), web_page_id); break; } case td_api::inputMessageAnimation::ID: { auto input_animation = static_cast(input_message_content.get()); - if (!clean_input_string(input_animation->caption_)) { - return Status::Error(400, "Animation caption must be encoded in UTF-8"); - } + TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_animation->caption_), is_bot)); td_->animations_manager_->create_animation( file_id, thumbnail, std::move(file_name), std::move(mime_type), input_animation->duration_, get_dimensions(input_animation->width_, input_animation->height_), false); - content = - make_unique(file_id, strip_empty_characters(input_animation->caption_, MAX_CAPTION_LENGTH)); + content = make_unique(file_id, std::move(caption)); break; } case td_api::inputMessageAudio::ID: { @@ -15240,36 +15172,29 @@ Result MessagesManager::process_input_mess if (!clean_input_string(input_audio->performer_)) { return Status::Error(400, "Audio performer must be encoded in UTF-8"); } - if (!clean_input_string(input_audio->caption_)) { - return Status::Error(400, "Audio caption must be encoded in UTF-8"); - } + TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_audio->caption_), is_bot)); td_->audios_manager_->create_audio(file_id, thumbnail, std::move(file_name), std::move(mime_type), input_audio->duration_, std::move(input_audio->title_), std::move(input_audio->performer_), false); - content = make_unique(file_id, strip_empty_characters(input_audio->caption_, MAX_CAPTION_LENGTH)); + content = make_unique(file_id, std::move(caption)); break; } case td_api::inputMessageDocument::ID: { auto input_document = static_cast(input_message_content.get()); - if (!clean_input_string(input_document->caption_)) { - return Status::Error(400, "Document caption must be encoded in UTF-8"); - } + TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_document->caption_), is_bot)); td_->documents_manager_->create_document(file_id, thumbnail, std::move(file_name), std::move(mime_type), false); - content = - make_unique(file_id, strip_empty_characters(input_document->caption_, MAX_CAPTION_LENGTH)); + content = make_unique(file_id, std::move(caption)); break; } case td_api::inputMessagePhoto::ID: { auto input_photo = static_cast(input_message_content.get()); - if (!clean_input_string(input_photo->caption_)) { - return Status::Error(400, "Photo caption must be encoded in UTF-8"); - } + TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_photo->caption_), is_bot)); if (input_photo->width_ < 0 || input_photo->width_ > 10000) { return Status::Error(400, "Wrong photo width"); } @@ -15300,7 +15225,7 @@ Result MessagesManager::process_input_mess message_photo->photo.has_stickers = !sticker_file_ids.empty(); message_photo->photo.sticker_file_ids = std::move(sticker_file_ids); - message_photo->caption = strip_empty_characters(input_photo->caption_, MAX_CAPTION_LENGTH); + message_photo->caption = std::move(caption); content = std::move(message_photo); break; @@ -15316,9 +15241,7 @@ Result MessagesManager::process_input_mess case td_api::inputMessageVideo::ID: { auto input_video = static_cast(input_message_content.get()); - if (!clean_input_string(input_video->caption_)) { - return Status::Error(400, "Video caption must be encoded in UTF-8"); - } + TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_video->caption_), is_bot)); ttl = input_video->ttl_; bool has_stickers = !sticker_file_ids.empty(); @@ -15326,7 +15249,7 @@ Result MessagesManager::process_input_mess std::move(file_name), std::move(mime_type), input_video->duration_, get_dimensions(input_video->width_, input_video->height_), false); - content = make_unique(file_id, strip_empty_characters(input_video->caption_, MAX_CAPTION_LENGTH)); + content = make_unique(file_id, std::move(caption)); break; } case td_api::inputMessageVideoNote::ID: { @@ -15346,15 +15269,12 @@ Result MessagesManager::process_input_mess case td_api::inputMessageVoiceNote::ID: { auto input_voice_note = static_cast(input_message_content.get()); - if (!clean_input_string(input_voice_note->caption_)) { - return Status::Error(400, "Voice note caption must be encoded in UTF-8"); - } + TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_voice_note->caption_), is_bot)); td_->voice_notes_manager_->create_voice_note(file_id, std::move(mime_type), input_voice_note->duration_, std::move(input_voice_note->waveform_), false); - content = make_unique( - file_id, strip_empty_characters(input_voice_note->caption_, MAX_CAPTION_LENGTH), false); + content = make_unique(file_id, std::move(caption), false); break; } case td_api::inputMessageLocation::ID: { @@ -15387,7 +15307,7 @@ Result MessagesManager::process_input_mess break; } case td_api::inputMessageInvoice::ID: { - if (!td_->auth_manager_->is_bot()) { + if (!is_bot) { return Status::Error(400, "Invoices can be sent only by bots"); } @@ -15605,15 +15525,16 @@ void MessagesManager::do_send_message(DialogId dialog_id, Message *m, vectorcontacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); send_closure(td_->create_net_actor(), &SendSecretMessageActor::send, dialog_id, - m->reply_to_random_id, m->ttl, message_text->text, + m->reply_to_random_id, m->ttl, message_text->text.text, get_secret_input_media(content, nullptr, BufferSlice(), layer), - get_input_secret_message_entities(message_text->entities), m->via_bot_user_id, m->media_album_id, - random_id); + get_input_secret_message_entities(message_text->text.entities), m->via_bot_user_id, + m->media_album_id, random_id); } else { send_closure(td_->create_net_actor(), &SendMessageActor::send, get_message_flags(m), dialog_id, m->reply_to_message_id, get_input_reply_markup(m->reply_markup), - get_input_message_entities(td_->contacts_manager_.get(), message_text->entities), message_text->text, - random_id, &m->send_query_ref, get_sequence_dispatcher_id(dialog_id, content_type)); + get_input_message_entities(td_->contacts_manager_.get(), message_text->text.entities), + message_text->text.text, random_id, &m->send_query_ref, + get_sequence_dispatcher_id(dialog_id, content_type)); } return; } @@ -15669,11 +15590,15 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, Message *m, auto m = result.move_as_ok(); CHECK(m != nullptr); CHECK(input_media != nullptr); + + auto caption = get_message_content_caption(m->content.get()); + LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id << " in reply to " << m->reply_to_message_id; int64 random_id = begin_send_message(dialog_id, m); send_closure(td_->create_net_actor(), &SendMediaActor::send, file_id, thumbnail_file_id, get_message_flags(m), dialog_id, m->reply_to_message_id, get_input_reply_markup(m->reply_markup), + get_input_message_entities(td_->contacts_manager_.get(), caption.entities), caption.text, std::move(input_media), random_id, &m->send_query_ref, get_sequence_dispatcher_id(dialog_id, m->content->get_id())); })); @@ -15766,9 +15691,8 @@ void MessagesManager::on_upload_message_media_success(DialogId dialog_id, Messag return; // the message should be deleted soon } - // TODO use get_message_content_caption() - auto content = get_message_content(string(), std::move(media), Auto(), dialog_id, false, UserId(), nullptr, - m->forward_info ? m->forward_info->date : m->date); + auto content = get_message_content(get_message_content_caption(m->content.get()), std::move(media), dialog_id, false, + UserId(), nullptr); update_message_content(dialog_id, m, m->content, std::move(content), true); @@ -15891,7 +15815,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { auto default_status = can_send_message(dialog_id); bool success = default_status.is_ok(); vector random_ids; - vector> input_media; + vector> input_single_media; MessageId reply_to_message_id; int32 flags = 0; for (size_t i = 0; i < request.message_ids.size(); i++) { @@ -15899,7 +15823,6 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { if (m == nullptr) { // skip deleted messages random_ids.push_back(0); - input_media.push_back(nullptr); continue; } @@ -15907,7 +15830,16 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { flags = get_message_flags(m); random_ids.push_back(begin_send_message(dialog_id, m)); - input_media.push_back(get_input_media(m->content.get(), nullptr, nullptr, m->ttl)); + auto caption = get_message_content_caption(m->content.get()); + auto input_media = get_input_media(m->content.get(), nullptr, nullptr, m->ttl); + auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities); + int32 input_single_media_flags = 0; + if (!entities.empty()) { + input_single_media_flags |= telegram_api::inputSingleMedia::ENTITIES_MASK; + } + + input_single_media.push_back(make_tl_object( + std::move(input_media), input_single_media_flags, random_ids.back(), caption.text, std::move(entities))); if (request.results[i].is_error()) { success = false; } @@ -15931,15 +15863,6 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { LOG(INFO) << "Begin to send media group " << media_album_id << " to " << dialog_id; - vector> input_single_media; - for (size_t i = 0; i < random_ids.size(); i++) { - if (random_ids[i] != 0) { - CHECK(input_media[i] != nullptr); - input_single_media.push_back( - make_tl_object(std::move(input_media[i]), random_ids[i])); - } - } - if (input_single_media.empty()) { LOG(INFO) << "Media group " << media_album_id << " from " << dialog_id << " is empty"; } @@ -16078,8 +16001,8 @@ Result MessagesManager::send_bot_start_message(UserId bot_user_id, Di Message *m = get_message_to_send( d, MessageId(), false, false, make_unique( - text, - vector{MessageEntity(MessageEntity::Type::BotCommand, 0, narrow_cast(text.size()))}, + FormattedText{text, vector{MessageEntity(MessageEntity::Type::BotCommand, 0, + narrow_cast(text.size()))}}, WebPageId()), &need_update_dialog_pos); @@ -16488,8 +16411,8 @@ void MessagesManager::edit_message_text(FullMessageId full_message_id, } send_closure(td_->create_net_actor(std::move(promise)), &EditMessageActor::send, flags, dialog_id, - message_id, input_message_text.text, - get_input_message_entities(td_->contacts_manager_.get(), input_message_text.entities), nullptr, + message_id, input_message_text.text.text, + get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities), nullptr, std::move(input_reply_markup), get_sequence_dispatcher_id(dialog_id, -1)); } @@ -16546,9 +16469,11 @@ void MessagesManager::edit_message_live_location(FullMessageId full_message_id, } void MessagesManager::edit_message_caption(FullMessageId full_message_id, - tl_object_ptr &&reply_markup, const string &caption, + tl_object_ptr &&reply_markup, + tl_object_ptr &&input_caption, Promise &&promise) { LOG(INFO) << "Begin to edit caption of " << full_message_id; + auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id); if (d == nullptr) { @@ -16614,6 +16539,12 @@ void MessagesManager::edit_message_caption(FullMessageId full_message_id, UNREACHABLE(); } + auto r_caption = process_input_caption(dialog_id, std::move(input_caption), td_->auth_manager_->is_bot()); + if (r_caption.is_error()) { + return promise.set_error(r_caption.move_as_error()); + } + auto caption = r_caption.move_as_ok(); + auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, !is_broadcast_channel(dialog_id)); if (r_new_reply_markup.is_error()) { @@ -16622,9 +16553,8 @@ void MessagesManager::edit_message_caption(FullMessageId full_message_id, auto input_reply_markup = get_input_reply_markup(r_new_reply_markup.ok()); send_closure(td_->create_net_actor(std::move(promise)), &EditMessageActor::send, 1 << 11, dialog_id, - message_id, strip_empty_characters(caption, MAX_CAPTION_LENGTH), - vector>(), nullptr, std::move(input_reply_markup), - get_sequence_dispatcher_id(dialog_id, -1)); + message_id, caption.text, get_input_message_entities(td_->contacts_manager_.get(), caption.entities), + nullptr, std::move(input_reply_markup), get_sequence_dispatcher_id(dialog_id, -1)); } void MessagesManager::edit_message_reply_markup(FullMessageId full_message_id, @@ -16704,8 +16634,8 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id, flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } td_->create_handler(std::move(promise)) - ->send(flags, std::move(input_bot_inline_message_id), input_message_text.text, - get_input_message_entities(td_->contacts_manager_.get(), input_message_text.entities), nullptr, + ->send(flags, std::move(input_bot_inline_message_id), input_message_text.text.text, + get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities), nullptr, get_input_reply_markup(r_new_reply_markup.ok())); } @@ -16744,11 +16674,18 @@ void MessagesManager::edit_inline_message_live_location(const string &inline_mes void MessagesManager::edit_inline_message_caption(const string &inline_message_id, tl_object_ptr &&reply_markup, - const string &caption, Promise &&promise) { + tl_object_ptr &&input_caption, + Promise &&promise) { if (!td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(3, "Method is available only for bots")); } + auto r_caption = process_input_caption(DialogId(), std::move(input_caption), td_->auth_manager_->is_bot()); + if (r_caption.is_error()) { + return promise.set_error(r_caption.move_as_error()); + } + auto caption = r_caption.move_as_ok(); + auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { return promise.set_error(r_new_reply_markup.move_as_error()); @@ -16760,8 +16697,8 @@ void MessagesManager::edit_inline_message_caption(const string &inline_message_i } td_->create_handler(std::move(promise)) - ->send(1 << 11, std::move(input_bot_inline_message_id), strip_empty_characters(caption, MAX_CAPTION_LENGTH), - vector>(), nullptr, + ->send(1 << 11, std::move(input_bot_inline_message_id), caption.text, + get_input_message_entities(td_->contacts_manager_.get(), caption.entities), nullptr, get_input_reply_markup(r_new_reply_markup.ok())); } @@ -19733,15 +19670,15 @@ NotificationSettings MessagesManager::get_notification_settings( unique_ptr MessagesManager::get_secret_message_document( tl_object_ptr file, tl_object_ptr &&document, - vector> &&attributes, DialogId owner_dialog_id, string &&caption, - bool is_opened) const { + vector> &&attributes, DialogId owner_dialog_id, + FormattedText &&caption, bool is_opened) const { return get_message_document(td_->documents_manager_->on_get_document( {std::move(file), std::move(document), std::move(attributes)}, owner_dialog_id), std::move(caption), is_opened); } unique_ptr MessagesManager::get_message_document(tl_object_ptr &&document, - DialogId owner_dialog_id, string &&caption, + DialogId owner_dialog_id, FormattedText &&caption, bool is_opened, MultiPromiseActor *load_data_multipromise_ptr) const { return get_message_document( @@ -19750,7 +19687,8 @@ unique_ptr MessagesManager::get_message_document(tl_object_ptr MessagesManager::get_message_document( - std::pair &&parsed_document, string &&caption, bool is_opened) const { + std::pair &&parsed_document, FormattedText &&caption, + bool is_opened) const { auto document_type = parsed_document.first; auto file_id = parsed_document.second; if (document_type != DocumentsManager::DocumentType::Unknown) { @@ -19780,7 +19718,7 @@ unique_ptr MessagesManager::get_message_document( } unique_ptr MessagesManager::get_message_photo(tl_object_ptr &&photo, - DialogId owner_dialog_id, string &&caption) const { + DialogId owner_dialog_id, FormattedText &&caption) const { auto m = make_unique(); m->photo = get_photo(td_->file_manager_.get(), std::move(photo), owner_dialog_id); @@ -19789,14 +19727,36 @@ unique_ptr MessagesManager::get_message_photo(tl_object_ptr> &&server_entities, + int32 send_date) const { + auto entities = get_message_entities(td_->contacts_manager_.get(), std::move(server_entities)); + auto status = fix_text_message(message_text, entities, true, true, true, false); + if (status.is_error()) { + if (send_date == 0 || send_date > 1497000000) { // approximate fix date + LOG(ERROR) << "Receive error " << status << " while parsing message content \"" << message_text << "\" sent at " + << send_date << " with entities " << format::as_array(entities); + } + if (!clean_input_string(message_text)) { + message_text.clear(); + } + entities.clear(); } - return message_text + "\n\n" + caption; + return FormattedText{std::move(message_text), std::move(entities)}; } template @@ -20036,7 +19996,7 @@ unique_ptr MessagesManager::get_secret_message_content( vector> &&secret_entities, DialogId owner_dialog_id, MultiPromiseActor &load_data_multipromise) const { auto entities = get_message_entities(std::move(secret_entities)); - auto status = fix_text_message(message_text, entities, nullptr, true, false, true, false); + auto status = fix_text_message(message_text, entities, true, false, true, false); if (status.is_error()) { LOG(WARNING) << "Receive error " << status << " while parsing secret message \"" << message_text << "\" with entities " << format::as_array(entities); @@ -20047,7 +20007,7 @@ unique_ptr MessagesManager::get_secret_message_content( } if (media == nullptr) { - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(FormattedText{std::move(message_text), std::move(entities)}, WebPageId()); } int32 constructor_id = media->get_id(); @@ -20055,7 +20015,7 @@ unique_ptr MessagesManager::get_secret_message_content( if (constructor_id != secret_api::decryptedMessageMediaEmpty::ID) { LOG(INFO) << "Receive non-empty message text and media"; } else { - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(FormattedText{std::move(message_text), std::move(entities)}, WebPageId()); } } @@ -20146,7 +20106,7 @@ unique_ptr MessagesManager::get_secret_message_content( auto url = r_http_url.ok().get_url(); auto web_page_id = td_->web_pages_manager_->get_web_page_by_url(url, load_data_multipromise.get_promise()); - auto result = make_unique(std::move(message_text), std::move(entities), web_page_id); + auto result = make_unique(FormattedText{std::move(message_text), std::move(entities)}, web_page_id); if (!result->web_page_id.is_valid()) { load_data_multipromise.add_promise( PromiseCreator::lambda([td = td_, url, &web_page_id = result->web_page_id](Result result) { @@ -20160,7 +20120,8 @@ unique_ptr MessagesManager::get_secret_message_content( case secret_api::decryptedMessageMediaExternalDocument::ID: { auto external_document = move_tl_object_as(media); auto document = secret_to_telegram_document(*external_document); - return get_message_document(std::move(document), owner_dialog_id, std::move(message_text), false, + return get_message_document(std::move(document), owner_dialog_id, + FormattedText{std::move(message_text), std::move(entities)}, false, &load_data_multipromise); } } @@ -20169,7 +20130,7 @@ unique_ptr MessagesManager::get_secret_message_content( is_media_empty = true; } if (is_media_empty) { - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(FormattedText{std::move(message_text), std::move(entities)}, WebPageId()); } switch (constructor_id) { case secret_api::decryptedMessageMediaPhoto::ID: { @@ -20177,10 +20138,9 @@ unique_ptr MessagesManager::get_secret_message_content( if (!clean_input_string(message_photo->caption_)) { message_photo->caption_.clear(); } - string caption = get_media_caption(message_text, std::move(message_photo->caption_)); return make_unique( get_photo(td_->file_manager_.get(), std::move(file), std::move(message_photo), owner_dialog_id), - std::move(caption)); + get_secret_media_caption(std::move(message_text), std::move(message_photo->caption_))); } case secret_api::decryptedMessageMediaDocument::ID: { auto message_document = move_tl_object_as(media); @@ -20190,11 +20150,11 @@ unique_ptr MessagesManager::get_secret_message_content( if (!clean_input_string(message_document->mime_type_)) { message_document->mime_type_.clear(); } - string caption = get_media_caption(message_text, std::move(message_document->caption_)); auto attributes = secret_to_telegram(message_document->attributes_); message_document->attributes_.clear(); - return get_secret_message_document(std::move(file), std::move(message_document), std::move(attributes), - owner_dialog_id, std::move(caption), false); + return get_secret_message_document( + std::move(file), std::move(message_document), std::move(attributes), owner_dialog_id, + get_secret_media_caption(std::move(message_text), std::move(message_document->caption_)), false); } default: LOG(ERROR) << "Unsupported: " << to_string(media); @@ -20202,43 +20162,28 @@ unique_ptr MessagesManager::get_secret_message_content( } } -unique_ptr MessagesManager::get_message_content( - string message_text, tl_object_ptr &&media, - vector> &&server_entities, DialogId owner_dialog_id, - bool is_content_read, UserId via_bot_user_id, int32 *ttl, int32 send_date) const { - auto entities = get_message_entities(td_->contacts_manager_.get(), std::move(server_entities)); - auto status = fix_text_message(message_text, entities, nullptr, true, true, true, false); - if (status.is_error()) { - if (send_date > 1497000000) { // approximate fix date - LOG(ERROR) << "Receive error " << status << " while parsing message content \"" << message_text << "\" sent at " - << send_date << " with entities " << format::as_array(entities); - } - if (!clean_input_string(message_text)) { - message_text.clear(); - } - entities.clear(); - } - +unique_ptr MessagesManager::get_message_content(FormattedText message, + tl_object_ptr &&media, + DialogId owner_dialog_id, bool is_content_read, + UserId via_bot_user_id, int32 *ttl) const { if (media == nullptr) { - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(std::move(message), WebPageId()); } int32 constructor_id = media->get_id(); - if (message_text.size()) { + if (message.text.size()) { if (constructor_id != telegram_api::messageMediaEmpty::ID) { LOG(INFO) << "Receive non-empty message text and media for message from " << owner_dialog_id; } else { - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(std::move(message), WebPageId()); } } - string caption; switch (constructor_id) { case telegram_api::messageMediaEmpty::ID: LOG(ERROR) << "Receive empty message text and media for message from " << owner_dialog_id; - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(std::move(message), WebPageId()); case telegram_api::messageMediaPhoto::ID: { auto message_photo = move_tl_object_as(media); - caption = get_media_caption(message_text, std::move(message_photo->caption_)); if ((message_photo->flags_ & telegram_api::messageMediaPhoto::PHOTO_MASK) == 0) { if ((message_photo->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) == 0) { LOG(ERROR) << "Receive messageMediaPhoto without photo and TTL: " << oneline(to_string(message_photo)); @@ -20258,7 +20203,7 @@ unique_ptr MessagesManager::get_message_content( if (ttl != nullptr && (message_photo->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) != 0) { *ttl = message_photo->ttl_seconds_; } - return get_message_photo(move_tl_object_as(photo_ptr), owner_dialog_id, std::move(caption)); + return get_message_photo(move_tl_object_as(photo_ptr), owner_dialog_id, std::move(message)); } case telegram_api::messageMediaGeo::ID: { auto message_geo_point = move_tl_object_as(media); @@ -20303,7 +20248,6 @@ unique_ptr MessagesManager::get_message_content( } case telegram_api::messageMediaDocument::ID: { auto message_document = move_tl_object_as(media); - caption = get_media_caption(message_text, std::move(message_document->caption_)); if ((message_document->flags_ & telegram_api::messageMediaDocument::DOCUMENT_MASK) == 0) { if ((message_document->flags_ & telegram_api::messageMediaDocument::TTL_SECONDS_MASK) == 0) { LOG(ERROR) << "Receive messageMediaDocument without document and TTL: " @@ -20325,7 +20269,7 @@ unique_ptr MessagesManager::get_message_content( *ttl = message_document->ttl_seconds_; } return get_message_document(move_tl_object_as(document_ptr), owner_dialog_id, - std::move(caption), is_content_read, nullptr); + std::move(message), is_content_read, nullptr); } case telegram_api::messageMediaGame::ID: { auto message_game = move_tl_object_as(media); @@ -20336,7 +20280,7 @@ unique_ptr MessagesManager::get_message_content( } m->game.set_bot_user_id(via_bot_user_id); - m->game.set_message_text(std::move(message_text), std::move(entities)); + m->game.set_message_text(std::move(message)); return std::move(m); } @@ -20363,7 +20307,7 @@ unique_ptr MessagesManager::get_message_content( case telegram_api::messageMediaWebPage::ID: { auto media_web_page = move_tl_object_as(media); auto web_page_id = td_->web_pages_manager_->on_get_web_page(std::move(media_web_page->webpage_), owner_dialog_id); - return make_unique(std::move(message_text), std::move(entities), web_page_id); + return make_unique(std::move(message), web_page_id); } case telegram_api::messageMediaUnsupported::ID: { @@ -20374,11 +20318,7 @@ unique_ptr MessagesManager::get_message_content( } // explicit empty media message - if (!caption.empty()) { - // caption already includes message text - return make_unique(std::move(caption), std::move(entities), WebPageId()); - } - return make_unique(std::move(message_text), std::move(entities), WebPageId()); + return make_unique(std::move(message), WebPageId()); } unique_ptr MessagesManager::dup_message_content(DialogId dialog_id, const MessageContent *content, @@ -20724,7 +20664,7 @@ unique_ptr MessagesManager::get_message_action_content( UNREACHABLE(); } // explicit empty or wrong action - return make_unique("", vector(), WebPageId()); + return make_unique(FormattedText{}, WebPageId()); } int32 MessagesManager::get_random_y(MessageId message_id) { @@ -21770,14 +21710,15 @@ bool MessagesManager::need_message_text_changed_warning(const Message *old_messa // original message may be edited return false; } - if (new_content->text == "Unsupported characters" || - new_content->text == "This channel is blocked because it was used to spread pornographic content.") { + if (new_content->text.text == "Unsupported characters" || + new_content->text.text == "This channel is blocked because it was used to spread pornographic content.") { // message contained unsupported characters, text is replaced return false; } - if (old_message->message_id.is_yet_unsent() && !old_content->entities.empty() && - old_content->entities[0].offset == 0 && (new_content->entities.empty() || new_content->entities[0].offset != 0) && - old_content->text != new_content->text && ends_with(old_content->text, new_content->text)) { + if (old_message->message_id.is_yet_unsent() && !old_content->text.entities.empty() && + old_content->text.entities[0].offset == 0 && + (new_content->text.entities.empty() || new_content->text.entities[0].offset != 0) && + old_content->text.text != new_content->text.text && ends_with(old_content->text.text, new_content->text.text)) { // server has deleted first entity and ltrim the message return false; } @@ -21847,17 +21788,17 @@ bool MessagesManager::update_message_content(DialogId dialog_id, const Message * case MessageText::ID: { auto old_ = static_cast(old_content.get()); auto new_ = static_cast(new_content.get()); - if (old_->text != new_->text) { + if (old_->text.text != new_->text.text) { if (need_message_text_changed_warning(old_message, old_, new_)) { LOG(ERROR) << "Message text has changed for " << to_string(get_message_object(dialog_id, old_message)) << ". New content is " << to_string(get_message_content_object(new_content.get())); } need_update = true; } - if (old_->entities != new_->entities) { + if (old_->text.entities != new_->text.entities) { const int32 MAX_CUSTOM_ENTITIES_COUNT = 100; // server-size limit if (need_message_text_changed_warning(old_message, old_, new_) && - old_->entities.size() <= MAX_CUSTOM_ENTITIES_COUNT) { + old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT) { LOG(WARNING) << "Entities has changed for " << to_string(get_message_object(dialog_id, old_message)) << ". New content is " << to_string(get_message_content_object(new_content.get())); } @@ -23347,10 +23288,10 @@ void MessagesManager::update_used_hashtags(DialogId dialog_id, const Message *m) return; } auto message_text = static_cast(m->content.get()); - const unsigned char *ptr = Slice(message_text->text).ubegin(); - const unsigned char *end = Slice(message_text->text).uend(); + const unsigned char *ptr = Slice(message_text->text.text).ubegin(); + const unsigned char *end = Slice(message_text->text.text).uend(); int32 utf16_pos = 0; - for (auto &entity : message_text->entities) { + for (auto &entity : message_text->text.entities) { if (entity.type != MessageEntity::Type::Hashtag) { continue; } diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index ab860f7d..5e5a5f0b 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -76,13 +76,11 @@ class MessageContent { class MessageText : public MessageContent { public: - string text; - vector entities; + FormattedText text; WebPageId web_page_id; MessageText() = default; - MessageText(string text, vector &&entities, WebPageId web_page_id) - : text(std::move(text)), entities(std::move(entities)), web_page_id(web_page_id) { + MessageText(FormattedText text, WebPageId web_page_id) : text(std::move(text)), web_page_id(web_page_id) { } static const int32 ID = 0; @@ -95,10 +93,10 @@ class MessageAnimation : public MessageContent { public: FileId file_id; - string caption; + FormattedText caption; MessageAnimation() = default; - MessageAnimation(FileId file_id, string &&caption) : file_id(file_id), caption(std::move(caption)) { + MessageAnimation(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) { } static const int32 ID = 1; @@ -111,10 +109,10 @@ class MessageAudio : public MessageContent { public: FileId file_id; - string caption; + FormattedText caption; MessageAudio() = default; - MessageAudio(FileId file_id, string &&caption) : file_id(file_id), caption(std::move(caption)) { + MessageAudio(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) { } static const int32 ID = 2; @@ -127,10 +125,10 @@ class MessageDocument : public MessageContent { public: FileId file_id; - string caption; + FormattedText caption; MessageDocument() = default; - MessageDocument(FileId file_id, string &&caption) : file_id(file_id), caption(std::move(caption)) { + MessageDocument(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) { } static const int32 ID = 3; @@ -143,10 +141,10 @@ class MessagePhoto : public MessageContent { public: Photo photo; - string caption; + FormattedText caption; MessagePhoto() = default; - MessagePhoto(Photo &&photo, string &&caption) : photo(std::move(photo)), caption(std::move(caption)) { + MessagePhoto(Photo &&photo, FormattedText &&caption) : photo(std::move(photo)), caption(std::move(caption)) { } static const int32 ID = 4; @@ -173,10 +171,10 @@ class MessageVideo : public MessageContent { public: FileId file_id; - string caption; + FormattedText caption; MessageVideo() = default; - MessageVideo(FileId file_id, string &&caption) : file_id(file_id), caption(std::move(caption)) { + MessageVideo(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) { } static const int32 ID = 6; @@ -189,11 +187,11 @@ class MessageVoiceNote : public MessageContent { public: FileId file_id; - string caption; + FormattedText caption; bool is_listened; MessageVoiceNote() = default; - MessageVoiceNote(FileId file_id, string &&caption, bool is_listened) + MessageVoiceNote(FileId file_id, FormattedText &&caption, bool is_listened) : file_id(file_id), caption(std::move(caption)), is_listened(is_listened) { } @@ -616,16 +614,12 @@ class MessageCustomServiceAction : public MessageContent { class InputMessageText { public: - string text; + FormattedText text; bool disable_web_page_preview = false; bool clear_draft = false; - vector entities; InputMessageText() = default; - InputMessageText(string text, bool disable_web_page_preview, bool clear_draft, vector entities) - : text(std::move(text)) - , disable_web_page_preview(disable_web_page_preview) - , clear_draft(clear_draft) - , entities(std::move(entities)) { + InputMessageText(FormattedText text, bool disable_web_page_preview, bool clear_draft) + : text(std::move(text)), disable_web_page_preview(disable_web_page_preview), clear_draft(clear_draft) { } }; @@ -958,6 +952,9 @@ class MessagesManager : public Actor { DialogId search_public_dialog(const string &username_to_search, bool force, Promise &&promise); + Result process_input_caption(DialogId dialog_id, tl_object_ptr &&text, + bool is_bot) const; + Result process_input_message_text( DialogId dialog_id, tl_object_ptr &&input_message_content, bool is_bot, bool for_draft = false) const TD_WARN_UNUSED_RESULT; @@ -974,8 +971,13 @@ class MessagesManager : public Actor { Result process_input_message_game(tl_object_ptr &&input_message_content) const TD_WARN_UNUSED_RESULT; - static Status fix_text_message(string &text, vector &entities, - tl_object_ptr &&parse_mode, bool allow_empty, + bool need_skip_bot_commands(DialogId dialog_id, bool is_bot) const; + + FormattedText get_message_text(string message_text, + vector> &&server_entities, + int32 send_date) const; + + static Status fix_text_message(string &text, vector &entities, bool allow_empty, bool skip_new_entities, bool skip_bot_commands, bool for_draft) TD_WARN_UNUSED_RESULT; Result send_message(DialogId dialog_id, MessageId reply_to_message_id, bool disable_notification, @@ -1010,7 +1012,7 @@ class MessagesManager : public Actor { tl_object_ptr &&input_location, Promise &&promise); void edit_message_caption(FullMessageId full_message_id, tl_object_ptr &&reply_markup, - const string &caption, Promise &&promise); + tl_object_ptr &&input_caption, Promise &&promise); void edit_message_reply_markup(FullMessageId full_message_id, tl_object_ptr &&reply_markup, Promise &&promise); @@ -1024,7 +1026,7 @@ class MessagesManager : public Actor { tl_object_ptr &&input_location, Promise &&promise); void edit_inline_message_caption(const string &inline_message_id, tl_object_ptr &&reply_markup, - const string &caption, Promise &&promise); + tl_object_ptr &&input_caption, Promise &&promise); void edit_inline_message_reply_markup(const string &inline_message_id, tl_object_ptr &&reply_markup, Promise &&promise); @@ -2178,25 +2180,25 @@ class MessagesManager : public Actor { static unique_ptr get_draft_message(ContactsManager *contacts_manager, tl_object_ptr &&draft_message_ptr); - static string get_media_caption(const string &message_text, string &&caption); + static FormattedText get_secret_media_caption(string &&message_text, string &&message_caption); Photo get_web_document_photo(tl_object_ptr web_document, DialogId owner_dialog_id) const; unique_ptr get_secret_message_document( tl_object_ptr file, tl_object_ptr &&document, - vector> &&attributes, DialogId owner_dialog_id, string &&caption, - bool is_opened) const; + vector> &&attributes, DialogId owner_dialog_id, + FormattedText &&caption, bool is_opened) const; unique_ptr get_message_document(tl_object_ptr &&document, - DialogId owner_dialog_id, string &&caption, bool is_opened, + DialogId owner_dialog_id, FormattedText &&caption, bool is_opened, MultiPromiseActor *load_data_multipromise_ptr) const; unique_ptr get_message_document(std::pair &&parsed_document, - string &&caption, bool is_opened) const; + FormattedText &&caption, bool is_opened) const; unique_ptr get_message_photo(tl_object_ptr &&photo, DialogId owner_dialog_id, - string &&caption) const; + FormattedText &&caption) const; unique_ptr get_secret_message_content( string message_text, tl_object_ptr file, @@ -2204,10 +2206,10 @@ class MessagesManager : public Actor { vector> &&secret_entities, DialogId owner_dialog_id, MultiPromiseActor &load_data_multipromise) const; - unique_ptr get_message_content(string message_text, tl_object_ptr &&media, - vector> &&server_entities, + unique_ptr get_message_content(FormattedText message_text, + tl_object_ptr &&media, DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id, - int32 *ttl, int32 send_date) const; + int32 *ttl) const; unique_ptr dup_message_content(DialogId dialog_id, const MessageContent *content, bool for_forward); @@ -2216,7 +2218,7 @@ class MessagesManager : public Actor { tl_object_ptr get_message_content_object(const MessageContent *content) const; - static string get_message_content_caption(const MessageContent *content); + static FormattedText get_message_content_caption(const MessageContent *content); int32 get_message_content_duration(const MessageContent *content) const; diff --git a/td/telegram/Photo.cpp b/td/telegram/Photo.cpp index 053b42b2..f56e72e9 100644 --- a/td/telegram/Photo.cpp +++ b/td/telegram/Photo.cpp @@ -397,7 +397,7 @@ bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool i tl_object_ptr photo_get_input_media(FileManager *file_manager, const Photo &photo, tl_object_ptr input_file, - const string &caption, int32 ttl) { + int32 ttl) { if (!photo.photos.empty()) { auto file_id = photo.photos.back().file_id; auto file_view = file_manager->get_file_view(file_id); @@ -409,17 +409,15 @@ tl_object_ptr photo_get_input_media(FileManager *file_ if (ttl != 0) { flags |= telegram_api::inputMediaPhoto::TTL_SECONDS_MASK; } - return make_tl_object(flags, file_view.remote_location().as_input_photo(), caption, - ttl); + return make_tl_object(flags, file_view.remote_location().as_input_photo(), ttl); } if (file_view.has_url()) { int32 flags = 0; if (ttl != 0) { flags |= telegram_api::inputMediaPhotoExternal::TTL_SECONDS_MASK; } - LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << ", caption " << caption - << " and ttl " << ttl; - return make_tl_object(flags, file_view.url(), caption, ttl); + LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and ttl " << ttl; + return make_tl_object(flags, file_view.url(), ttl); } CHECK(!file_view.has_remote_location()); } @@ -434,7 +432,7 @@ tl_object_ptr photo_get_input_media(FileManager *file_ flags |= telegram_api::inputMediaUploadedPhoto::TTL_SECONDS_MASK; } - return make_tl_object(flags, std::move(input_file), caption, + return make_tl_object(flags, std::move(input_file), std::move(added_stickers), ttl); } return nullptr; diff --git a/td/telegram/Photo.h b/td/telegram/Photo.h index f032f05e..16a9fa5e 100644 --- a/td/telegram/Photo.h +++ b/td/telegram/Photo.h @@ -104,7 +104,7 @@ SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const P tl_object_ptr photo_get_input_media(FileManager *file_manager, const Photo &photo, tl_object_ptr input_file, - const string &caption, int32 ttl); + int32 ttl); bool operator==(const Photo &lhs, const Photo &rhs); bool operator!=(const Photo &lhs, const Photo &rhs); diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index 41793a43..82be450e 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -1319,10 +1319,10 @@ tl_object_ptr StickersManager::get_input_media( return nullptr; } if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return make_tl_object(0, file_view.remote_location().as_input_document(), "", 0); + return make_tl_object(0, file_view.remote_location().as_input_document(), 0); } if (file_view.has_url()) { - return make_tl_object(0, file_view.url(), "", 0); + return make_tl_object(0, file_view.url(), 0); } CHECK(!file_view.has_remote_location()); @@ -1344,7 +1344,7 @@ tl_object_ptr StickersManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), "image/webp", - std::move(attributes), "", vector>(), 0); + std::move(attributes), vector>(), 0); } return nullptr; @@ -2832,7 +2832,7 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri } void StickersManager::upload_sticker_file(UserId user_id, FileId file_id, Promise &&promise) { - CHECK(td_->documents_manager_->get_input_media(file_id, nullptr, nullptr, "") == nullptr); + CHECK(td_->documents_manager_->get_input_media(file_id, nullptr, nullptr) == nullptr); auto upload_file_id = td_->documents_manager_->dup_document(td_->file_manager_->dup_file_id(file_id), file_id); @@ -2886,7 +2886,7 @@ void StickersManager::do_upload_sticker_file(UserId user_id, FileId file_id, return promise.set_error(Status::Error(3, "Have no access to the user")); } - auto input_media = td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr, ""); + auto input_media = td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr); CHECK(input_media != nullptr); td_->create_handler(std::move(promise)) diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index cadd4705..940afb52 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -1081,10 +1081,10 @@ class EditMessageLiveLocationRequest : public RequestOnceActor { class EditMessageCaptionRequest : public RequestOnceActor { FullMessageId full_message_id_; tl_object_ptr reply_markup_; - string caption_; + tl_object_ptr caption_; void do_run(Promise &&promise) override { - td->messages_manager_->edit_message_caption(full_message_id_, std::move(reply_markup_), caption_, + td->messages_manager_->edit_message_caption(full_message_id_, std::move(reply_markup_), std::move(caption_), std::move(promise)); } @@ -1094,7 +1094,8 @@ class EditMessageCaptionRequest : public RequestOnceActor { public: EditMessageCaptionRequest(ActorShared td, uint64 request_id, int64 dialog_id, int64 message_id, - tl_object_ptr reply_markup, string caption) + tl_object_ptr reply_markup, + tl_object_ptr caption) : RequestOnceActor(std::move(td), request_id) , full_message_id_(DialogId(dialog_id), MessageId(message_id)) , reply_markup_(std::move(reply_markup)) @@ -1168,16 +1169,17 @@ class EditInlineMessageLiveLocationRequest : public RequestOnceActor { class EditInlineMessageCaptionRequest : public RequestOnceActor { string inline_message_id_; tl_object_ptr reply_markup_; - string caption_; + tl_object_ptr caption_; void do_run(Promise &&promise) override { - td->messages_manager_->edit_inline_message_caption(inline_message_id_, std::move(reply_markup_), caption_, - std::move(promise)); + td->messages_manager_->edit_inline_message_caption(inline_message_id_, std::move(reply_markup_), + std::move(caption_), std::move(promise)); } public: EditInlineMessageCaptionRequest(ActorShared td, uint64 request_id, string inline_message_id, - tl_object_ptr reply_markup, string caption) + tl_object_ptr reply_markup, + tl_object_ptr caption) : RequestOnceActor(std::move(td), request_id) , inline_message_id_(std::move(inline_message_id)) , reply_markup_(std::move(reply_markup)) @@ -5233,7 +5235,6 @@ void Td::on_request(uint64 id, td_api::editMessageLiveLocation &request) { void Td::on_request(uint64 id, td_api::editMessageCaption &request) { CHECK_AUTH(); - CLEAN_INPUT_STRING(request.caption_); CREATE_REQUEST(EditMessageCaptionRequest, request.chat_id_, request.message_id_, std::move(request.reply_markup_), std::move(request.caption_)); } @@ -5265,7 +5266,6 @@ void Td::on_request(uint64 id, td_api::editInlineMessageCaption &request) { CHECK_AUTH(); CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.inline_message_id_); - CLEAN_INPUT_STRING(request.caption_); CREATE_REQUEST(EditInlineMessageCaptionRequest, std::move(request.inline_message_id_), std::move(request.reply_markup_), std::move(request.caption_)); } @@ -6463,6 +6463,11 @@ void Td::on_request(uint64 id, const td_api::getTextEntities &request) { send_result(id, do_static_request(request)); } +void Td::on_request(uint64 id, td_api::parseTextEntities &request) { + // don't check authorization state + send_result(id, do_static_request(request)); +} + void Td::on_request(uint64 id, const td_api::getFileMimeType &request) { // don't check authorization state send_result(id, do_static_request(request)); @@ -6486,6 +6491,33 @@ td_api::object_ptr Td::do_static_request(const td_api::getTextEn return make_tl_object(get_text_entities_object(text_entities)); } +td_api::object_ptr Td::do_static_request(td_api::parseTextEntities &request) { + if (!check_utf8(request.text_)) { + return create_error_raw(400, "Text must be encoded in UTF-8"); + } + if (request.parse_mode_ == nullptr) { + return create_error_raw(400, "Parse mode must be non-empty"); + } + + Result> r_entities; + switch (request.parse_mode_->get_id()) { + case td_api::textParseModeHTML::ID: + r_entities = parse_html(request.text_); + break; + case td_api::textParseModeMarkdown::ID: + r_entities = parse_markdown(request.text_); + break; + default: + UNREACHABLE(); + break; + } + if (r_entities.is_error()) { + return create_error_raw(400, PSLICE() << "Can't parse entities: " << r_entities.error().message()); + } + + return make_tl_object(std::move(request.text_), get_text_entities_object(r_entities.ok())); +} + td_api::object_ptr Td::do_static_request(const td_api::getFileMimeType &request) { // don't check file name UTF-8 correctness return make_tl_object(MimeType::from_extension(PathView(request.file_name_).extension())); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 9f2a44d9..08ea3b4d 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -189,7 +189,7 @@ class Td final : public NetQueryCallback { static td_api::object_ptr static_request(td_api::object_ptr function); private: - static constexpr const char *tdlib_version = "1.0.6"; + static constexpr const char *tdlib_version = "1.0.7"; static constexpr int32 ONLINE_TIMEOUT = 240; void send_result(uint64 id, tl_object_ptr object); @@ -740,6 +740,8 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, const td_api::getTextEntities &request); + void on_request(uint64 id, td_api::parseTextEntities &request); + void on_request(uint64 id, const td_api::getFileMimeType &request); void on_request(uint64 id, const td_api::getFileExtension &request); @@ -761,6 +763,7 @@ class Td final : public NetQueryCallback { template static td_api::object_ptr do_static_request(const T &); static td_api::object_ptr do_static_request(const td_api::getTextEntities &request); + static td_api::object_ptr do_static_request(td_api::parseTextEntities &request); static td_api::object_ptr do_static_request(const td_api::getFileMimeType &request); static td_api::object_ptr do_static_request(const td_api::getFileExtension &request); diff --git a/td/telegram/Version.h b/td/telegram/Version.h index b4460f2f..f27c3b77 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -22,6 +22,7 @@ enum class Version : int32 { FixMinUsers, FixPageBlockAudioEmptyFile, AddMessageInvoiceProviderData, + AddCaptionEntities, Next }; diff --git a/td/telegram/VideoNotesManager.cpp b/td/telegram/VideoNotesManager.cpp index cbf475ec..e49e8c97 100644 --- a/td/telegram/VideoNotesManager.cpp +++ b/td/telegram/VideoNotesManager.cpp @@ -201,10 +201,10 @@ tl_object_ptr VideoNotesManager::get_input_media( return nullptr; } if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return make_tl_object(0, file_view.remote_location().as_input_document(), "", 0); + return make_tl_object(0, file_view.remote_location().as_input_document(), 0); } if (file_view.has_url()) { - return make_tl_object(0, file_view.url(), "", 0); + return make_tl_object(0, file_view.url(), 0); } CHECK(!file_view.has_remote_location()); @@ -223,7 +223,7 @@ tl_object_ptr VideoNotesManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), "video/mp4", std::move(attributes), - "", vector>(), 0); + vector>(), 0); } return nullptr; diff --git a/td/telegram/VideosManager.cpp b/td/telegram/VideosManager.cpp index 6c1a181c..8a51834c 100644 --- a/td/telegram/VideosManager.cpp +++ b/td/telegram/VideosManager.cpp @@ -215,7 +215,7 @@ SecretInputMedia VideosManager::get_secret_input_media(FileId video_file_id, tl_object_ptr VideosManager::get_input_media( FileId file_id, tl_object_ptr input_file, - tl_object_ptr input_thumbnail, const string &caption, int32 ttl) const { + tl_object_ptr input_thumbnail, int32 ttl) const { if (!file_id.is_valid()) { LOG_IF(ERROR, ttl == 0) << "Video has invalid file_id"; return nullptr; @@ -230,14 +230,14 @@ tl_object_ptr VideosManager::get_input_media( flags |= telegram_api::inputMediaDocument::TTL_SECONDS_MASK; } return make_tl_object(flags, file_view.remote_location().as_input_document(), - caption, ttl); + ttl); } if (file_view.has_url()) { int32 flags = 0; if (ttl != 0) { flags |= telegram_api::inputMediaDocumentExternal::TTL_SECONDS_MASK; } - return make_tl_object(flags, file_view.url(), caption, ttl); + return make_tl_object(flags, file_view.url(), ttl); } CHECK(!file_view.has_remote_location()); @@ -269,7 +269,7 @@ tl_object_ptr VideosManager::get_input_media( } return make_tl_object( flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes), - caption, std::move(added_stickers), ttl); + std::move(added_stickers), ttl); } return nullptr; diff --git a/td/telegram/VideosManager.h b/td/telegram/VideosManager.h index 829fc6d7..6e6482ad 100644 --- a/td/telegram/VideosManager.h +++ b/td/telegram/VideosManager.h @@ -38,7 +38,7 @@ class VideosManager { tl_object_ptr get_input_media(FileId file_id, tl_object_ptr input_file, tl_object_ptr input_thumbnail, - const string &caption, int32 ttl) const; + int32 ttl) const; SecretInputMedia get_secret_input_media(FileId video_file_id, tl_object_ptr input_file, diff --git a/td/telegram/VoiceNotesManager.cpp b/td/telegram/VoiceNotesManager.cpp index 619ef311..ad1ee625 100644 --- a/td/telegram/VoiceNotesManager.cpp +++ b/td/telegram/VoiceNotesManager.cpp @@ -169,17 +169,16 @@ SecretInputMedia VoiceNotesManager::get_secret_input_media(FileId voice_file_id, } tl_object_ptr VoiceNotesManager::get_input_media( - FileId file_id, tl_object_ptr input_file, const string &caption) const { + FileId file_id, tl_object_ptr input_file) const { auto file_view = td_->file_manager_->get_file_view(file_id); if (file_view.is_encrypted()) { return nullptr; } if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return make_tl_object(0, file_view.remote_location().as_input_document(), caption, - 0); + return make_tl_object(0, file_view.remote_location().as_input_document(), 0); } if (file_view.has_url()) { - return make_tl_object(0, file_view.url(), caption, 0); + return make_tl_object(0, file_view.url(), 0); } CHECK(!file_view.has_remote_location()); @@ -199,7 +198,7 @@ tl_object_ptr VoiceNotesManager::get_input_media( mime_type = "audio/ogg"; } return make_tl_object( - 0, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes), caption, + 0, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes), vector>(), 0); } diff --git a/td/telegram/VoiceNotesManager.h b/td/telegram/VoiceNotesManager.h index d0161754..ba77f003 100644 --- a/td/telegram/VoiceNotesManager.h +++ b/td/telegram/VoiceNotesManager.h @@ -33,8 +33,7 @@ class VoiceNotesManager { void create_voice_note(FileId file_id, string mime_type, int32 duration, string waveform, bool replace); tl_object_ptr get_input_media(FileId file_id, - tl_object_ptr input_file, - const string &caption) const; + tl_object_ptr input_file) const; SecretInputMedia get_secret_input_media(FileId voice_file_id, tl_object_ptr input_file, diff --git a/td/telegram/WebPagesManager.cpp b/td/telegram/WebPagesManager.cpp index 21a3e8f2..e8ce9967 100644 --- a/td/telegram/WebPagesManager.cpp +++ b/td/telegram/WebPagesManager.cpp @@ -60,7 +60,8 @@ class GetWebPagePreviewQuery : public Td::ResultHandler { void send(const string &message_text) { message_text_ = message_text; - send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getWebPagePreview(message_text)))); + send_query(G()->net_query_creator().create( + create_storer(telegram_api::messages_getWebPagePreview(0, message_text, Auto())))); } void on_result(uint64 id, BufferSlice packet) override { diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index e830a4f4..a87c4a1d 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -293,7 +293,7 @@ class CliClient final : public Actor { for (auto &m : messages.messages_) { // LOG(PLAIN) << to_string(m); if (m->content_->get_id() == td_api::messageText::ID) { - LOG(PLAIN) << td::oneline(static_cast(m->content_.get())->text_) << "\n"; + LOG(PLAIN) << td::oneline(static_cast(m->content_.get())->text_->text_) << "\n"; } last_message_id = m->id_; } @@ -539,7 +539,7 @@ class CliClient final : public Actor { case td_api::updateChatLastMessage::ID: { auto message = static_cast(result.get())->last_message_.get(); if (message != nullptr && message->content_->get_id() == td_api::messageText::ID) { - // auto text = static_cast(message->content_.get())->text_; + // auto text = static_cast(message->content_.get())->text_->text_; } break; } @@ -742,6 +742,20 @@ class CliClient final : public Actor { } #endif + static tl_object_ptr as_caption(string caption, + vector> entities = {}) { + if (entities.empty()) { + auto parsed_caption = + execute(make_tl_object(caption, make_tl_object())); + if (parsed_caption->get_id() == td_api::formattedText::ID) { + auto text = td_api::move_object_as(parsed_caption); + caption = std::move(text->text_); + entities = std::move(text->entities_); + } + } + return make_tl_object(caption, std::move(entities)); + } + tl_object_ptr get_notification_settings_scope(Slice scope) const { if (scope == "users" || scope == "privateChats") { return make_tl_object(); @@ -907,10 +921,10 @@ class CliClient final : public Actor { return nullptr; } - auto execute(tl_object_ptr f) { - LOG(INFO) << "Execute " << tag("request", to_string(f)); + static td_api::object_ptr execute(tl_object_ptr f) { + LOG(INFO) << "Execute request " << to_string(f); auto res = ClientActor::execute(std::move(f)); - LOG(INFO) << "Execute " << tag("response", to_string(res)); + LOG(INFO) << "Execute response" << to_string(res); return res; } @@ -927,10 +941,10 @@ class CliClient final : public Actor { } void send_message(const string &chat_id, tl_object_ptr &&input_message_content, - bool disable_notification = false, bool from_background = false) { + bool disable_notification = false, bool from_background = false, int64 reply_to_message_id = 0) { auto chat = as_chat_id(chat_id); - auto id = send_request(make_tl_object(chat, 0, disable_notification, from_background, nullptr, - std::move(input_message_content))); + auto id = send_request(make_tl_object( + chat, reply_to_message_id, disable_notification, from_background, nullptr, std::move(input_message_content))); query_id_to_send_message_info_[id].start_time = Time::now(); } @@ -1916,6 +1930,10 @@ class CliClient final : public Actor { send_request(make_tl_object(args)); } else if (op == "gtes") { execute(make_tl_object(args)); + } else if (op == "pte") { + send_request(make_tl_object(args, make_tl_object())); + } else if (op == "ptes") { + execute(make_tl_object(args, make_tl_object())); } else if (op == "gfmt") { send_request(make_tl_object(trim(args))); } else if (op == "gfe") { @@ -1937,7 +1955,8 @@ class CliClient final : public Actor { draft_message = make_tl_object( as_message_id(reply_to_message_id), - make_tl_object(message, true, false, std::move(entities), nullptr)); + make_tl_object( + make_tl_object(message, std::move(entities)), true, false)); } send_request(make_tl_object(as_chat_id(chat_id), std::move(draft_message))); } else if (op == "tcip") { @@ -1962,13 +1981,15 @@ class CliClient final : public Actor { for (int i = 1; i <= 200; i++) { string message = PSTRING() << "#" << i; if (i == 6) { - send_message(chat_id, - make_tl_object(string(4097, 'a'), false, true, - vector>(), nullptr)); - } else { send_message(chat_id, make_tl_object( - message, false, true, vector>(), - make_tl_object())); + make_tl_object(string(4097, 'a'), + vector>()), + false, true)); + } else { + send_message(chat_id, + make_tl_object( + make_tl_object(message, vector>()), + false, true)); } } } else if (op == "ssm") { @@ -1986,30 +2007,29 @@ class CliClient final : public Actor { send_request( make_tl_object(as_chat_id(chat_id), query, to_integer(from_search_id), to_integer(limit), get_search_messages_filter(filter))); - } else if (op == "sm" || op == "sms") { - string chat_id; - string message; - std::tie(chat_id, message) = split(args); - send_message( - chat_id, - make_tl_object(message, false, true, vector>(), - make_tl_object()), - op == "sms"); - } else if (op == "smr") { + } else if (op == "sm" || op == "sms" || op == "smr" || op == "smf") { string chat_id; string reply_to_message_id; string message; - std::tie(chat_id, args) = split(args); - std::tie(reply_to_message_id, message) = split(args); - send_request(make_tl_object( - as_chat_id(chat_id), as_message_id(reply_to_message_id), false, true, nullptr, - make_tl_object(message, false, true, vector>(), - make_tl_object()))); - } else if (op == "smf") { - const string &chat_id = args; - send_message(chat_id, make_tl_object(string(1000097, 'a'), false, true, - vector>(), - make_tl_object())); + + std::tie(chat_id, message) = split(args); + if (op == "smr") { + std::tie(reply_to_message_id, message) = split(message); + } + if (op == "smf") { + message = string(1000097, 'a'); + } + + auto parsed_text = + execute(make_tl_object(message, make_tl_object())); + if (parsed_text->get_id() == td_api::error::ID) { + parsed_text = make_tl_object(message, vector>()); + } + + send_message( + chat_id, + make_tl_object(move_tl_object_as(parsed_text), false, true), + op == "sms", false, as_message_id(reply_to_message_id)); } else if (op == "smap" || op == "smapr") { string chat_id; string reply_to_message_id; @@ -2024,8 +2044,8 @@ class CliClient final : public Actor { send_request(make_tl_object( as_chat_id(chat_id), as_message_id(reply_to_message_id), false, false, transform(photos, [](const string &photo_path) { - tl_object_ptr content = - make_tl_object(as_local_file(photo_path), nullptr, Auto(), 0, 0, "", 0); + tl_object_ptr content = make_tl_object( + as_local_file(photo_path), nullptr, Auto(), 0, 0, as_caption(""), 0); return content; }))); } else if (op == "em") { @@ -2036,8 +2056,9 @@ class CliClient final : public Actor { std::tie(message_id, message) = split(args); send_request(make_tl_object( as_chat_id(chat_id), as_message_id(message_id), nullptr, - make_tl_object(message, true, true, vector>(), - make_tl_object()))); + make_tl_object( + make_tl_object(message, vector>()), true, + true))); } else if (op == "emll") { string chat_id; string message_id; @@ -2110,39 +2131,40 @@ class CliClient final : public Actor { std::tie(width, args) = split(args); std::tie(height, caption) = split(args); - send_message(chat_id, make_tl_object(as_local_file(animation_path), nullptr, 60, - to_integer(width), - to_integer(height), caption)); + send_message(chat_id, make_tl_object( + as_local_file(animation_path), nullptr, 60, to_integer(width), + to_integer(height), as_caption(caption))); } else if (op == "sang") { string chat_id; string animation_path; string animation_conversion; std::tie(chat_id, args) = split(args); std::tie(animation_path, animation_conversion) = split(args); - send_message(chat_id, make_tl_object( - as_generated_file(animation_path, animation_conversion), nullptr, 60, 0, 0, "")); + send_message(chat_id, + make_tl_object( + as_generated_file(animation_path, animation_conversion), nullptr, 60, 0, 0, as_caption(""))); } else if (op == "sanid") { string chat_id; string file_id; std::tie(chat_id, file_id) = split(args); - send_message(chat_id, - make_tl_object(as_input_file_id(file_id), nullptr, 0, 0, 0, "")); + send_message(chat_id, make_tl_object(as_input_file_id(file_id), nullptr, 0, 0, 0, + as_caption(""))); } else if (op == "sanurl") { string chat_id; string url; std::tie(chat_id, url) = split(args); - send_message(chat_id, - make_tl_object( - td_api::make_object(url, "#url#", 0), nullptr, 0, 0, 0, "")); + send_message(chat_id, make_tl_object( + td_api::make_object(url, "#url#", 0), nullptr, 0, 0, 0, + as_caption(""))); } else if (op == "sanurl2") { string chat_id; string url; std::tie(chat_id, url) = split(args); send_message(chat_id, make_tl_object( - td_api::make_object(url), nullptr, 0, 0, 0, "")); + td_api::make_object(url), nullptr, 0, 0, 0, as_caption(""))); } else if (op == "sau") { string chat_id; string audio_path; @@ -2156,14 +2178,14 @@ class CliClient final : public Actor { send_message(chat_id, make_tl_object(as_local_file(audio_path), nullptr, to_integer(duration), title, performer, - "audio caption")); + as_caption("audio caption"))); } else if (op == "svoice") { string chat_id; string voice_path; std::tie(chat_id, voice_path) = split(args); send_message(chat_id, make_tl_object(as_local_file(voice_path), 0, "abacaba", - "voice caption")); + as_caption("voice caption"))); } else if (op == "SendContact" || op == "scontact") { string chat_id; string phone_number; @@ -2190,33 +2212,34 @@ class CliClient final : public Actor { string chat_id; string document_path; std::tie(chat_id, document_path) = split(args); - send_message(chat_id, - make_tl_object( - as_local_file(document_path), nullptr, - Random::fast(0, 1) ? u8"\u1680\u180Etest \u180E\n\u180E\n\u180E\n cap\ttion\u180E\u180E" - : u8"\u200C\u200D\u202E " - " " - " " - " " - " abacaba")); + send_message(chat_id, make_tl_object( + as_local_file(document_path), nullptr, + Random::fast(0, 1) + ? as_caption(u8"\u1680\u180Etest \u180E\n\u180E\n\u180E\n cap\ttion\u180E\u180E") + : as_caption(u8"\u200C\u200D\u202E " + " " + " " + " " + " abacaba"))); } else if (op == "sdt") { string chat_id; string document_path; string thumbnail_path; std::tie(chat_id, args) = split(args); std::tie(document_path, thumbnail_path) = split(args); - send_message(chat_id, - make_tl_object( - as_local_file(document_path), - make_tl_object(as_local_file(thumbnail_path), 0, 0), "test caption")); + send_message(chat_id, make_tl_object( + as_local_file(document_path), + make_tl_object(as_local_file(thumbnail_path), 0, 0), + as_caption("test caption"))); } else if (op == "sdg") { string chat_id; string document_path; string document_conversion; std::tie(chat_id, args) = split(args); std::tie(document_path, document_conversion) = split(args); - send_message(chat_id, make_tl_object( - as_generated_file(document_path, document_conversion), nullptr, "test caption")); + send_message(chat_id, + make_tl_object(as_generated_file(document_path, document_conversion), + nullptr, as_caption("test caption"))); } else if (op == "sdtg") { string chat_id; string document_path; @@ -2229,7 +2252,7 @@ class CliClient final : public Actor { as_local_file(document_path), make_tl_object( as_generated_file(thumbnail_path, thumbnail_conversion), 0, 0), - "test caption")); + as_caption("test caption"))); } else if (op == "sdgtg") { string chat_id; string document_path; @@ -2244,12 +2267,13 @@ class CliClient final : public Actor { as_generated_file(document_path, document_conversion), make_tl_object( as_generated_file(thumbnail_path, thumbnail_conversion), 0, 0), - "test caption")); + as_caption("test caption"))); } else if (op == "sdid") { string chat_id; string file_id; std::tie(chat_id, file_id) = split(args); - send_message(chat_id, make_tl_object(as_input_file_id(file_id), nullptr, "")); + send_message(chat_id, + make_tl_object(as_input_file_id(file_id), nullptr, as_caption(""))); } else if (op == "sg") { string chat_id; string bot_user_id; @@ -2290,15 +2314,16 @@ class CliClient final : public Actor { sticker_file_ids = to_integers(sticker_file_ids_str, ','); } - send_message(chat_id, make_tl_object(as_local_file(photo_path), nullptr, - std::move(sticker_file_ids), 0, 0, "", 0)); + send_message(chat_id, + make_tl_object(as_local_file(photo_path), nullptr, + std::move(sticker_file_ids), 0, 0, as_caption(""), 0)); } else if (op == "spttl") { string chat_id; string photo_path; std::tie(chat_id, photo_path) = split(args); - send_message(chat_id, - make_tl_object(as_local_file(photo_path), nullptr, Auto(), 0, 0, "", 10)); + send_message(chat_id, make_tl_object(as_local_file(photo_path), nullptr, Auto(), 0, 0, + as_caption(""), 10)); } else if (op == "spg") { string chat_id; string photo_path; @@ -2306,8 +2331,9 @@ class CliClient final : public Actor { std::tie(chat_id, args) = split(args); std::tie(photo_path, conversion) = split(args); - send_message(chat_id, make_tl_object(as_generated_file(photo_path, conversion), - nullptr, vector(), 0, 0, "", 0)); + send_message(chat_id, + make_tl_object(as_generated_file(photo_path, conversion), nullptr, + vector(), 0, 0, as_caption(""), 0)); } else if (op == "spt") { string chat_id; string photo_path; @@ -2318,7 +2344,7 @@ class CliClient final : public Actor { send_message(chat_id, make_tl_object( as_local_file(photo_path), make_tl_object(as_local_file(thumbnail_path), 90, 89), - vector(), 0, 0, "", 0)); + vector(), 0, 0, as_caption(""), 0)); } else if (op == "sptg") { string chat_id; string photo_path; @@ -2332,7 +2358,7 @@ class CliClient final : public Actor { as_local_file(photo_path), make_tl_object( as_generated_file(thumbnail_path, thumbnail_conversion), 90, 89), - vector(), 0, 0, "", 0)); + vector(), 0, 0, as_caption(""), 0)); } else if (op == "spgtg") { string chat_id; string photo_path; @@ -2349,13 +2375,13 @@ class CliClient final : public Actor { as_generated_file(photo_path, conversion), make_tl_object( as_generated_file(thumbnail_path, thumbnail_conversion), 90, 89), - vector(), 0, 0, "", 0)); + vector(), 0, 0, as_caption(""), 0)); } else if (op == "spid") { string chat_id; string file_id; std::tie(chat_id, file_id) = split(args); send_message(chat_id, make_tl_object(as_input_file_id(file_id), nullptr, - vector(), 0, 0, "", 0)); + vector(), 0, 0, as_caption(""), 0)); } else if (op == "ss") { string chat_id; string sticker_path; @@ -2381,8 +2407,9 @@ class CliClient final : public Actor { sticker_file_ids = to_integers(sticker_file_ids_str, ','); } - send_message(chat_id, make_tl_object(as_local_file(video_path), nullptr, - std::move(sticker_file_ids), 1, 2, 3, "", 0)); + send_message(chat_id, + make_tl_object(as_local_file(video_path), nullptr, + std::move(sticker_file_ids), 1, 2, 3, as_caption(""), 0)); } else if (op == "svn") { string chat_id; string video_path; diff --git a/td/telegram/net/MtprotoHeader.cpp b/td/telegram/net/MtprotoHeader.cpp index 6223f6b2..885c6158 100644 --- a/td/telegram/net/MtprotoHeader.cpp +++ b/td/telegram/net/MtprotoHeader.cpp @@ -18,7 +18,7 @@ class HeaderStorer { } template void store(StorerT &storer) const { - constexpr int32 LAYER = 74; + constexpr int32 LAYER = 75; using td::store; // invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; diff --git a/td/tl/TlObject.h b/td/tl/TlObject.h index 1ce9a55a..4fcbf6bc 100644 --- a/td/tl/TlObject.h +++ b/td/tl/TlObject.h @@ -97,8 +97,10 @@ using tl_object_ptr = std::unique_ptr; * Usage example: * \code * auto get_authorization_state_request = td::make_tl_object(); + * auto message_text = td::make_tl_object("Hello, world!!!", + * std::vector>()); * auto send_message_request = td::make_tl_object(chat_id, 0, false, false, nullptr, - * td::make_tl_object("Hello, world!!!", false, true, {}, nullptr)); + * td::make_tl_object(std::move(message_text), false, true)); * \endcode * * \tparam Type Type of the TL-object to construct. diff --git a/test/tdclient.cpp b/test/tdclient.cpp index b3bfb40b..7cef0fcb 100644 --- a/test/tdclient.cpp +++ b/test/tdclient.cpp @@ -307,7 +307,8 @@ class SetUsername : public Task { this->send_query( make_tl_object( chat->id_, 0, false, false, nullptr, - make_tl_object(PSTRING() << tag_ << " INIT", false, false, Auto(), nullptr)), + make_tl_object( + make_tl_object(PSTRING() << tag_ << " INIT", Auto()), false, false)), [](auto res) {}); }); } @@ -321,7 +322,7 @@ class SetUsername : public Task { auto &message = updateNewMessage->message_; if (message->content_->get_id() == td_api::messageText::ID) { auto messageText = move_tl_object_as(message->content_); - auto text = messageText->text_; + auto text = messageText->text_->text_; if (text.substr(0, tag_.size()) == tag_) { LOG(INFO) << "GOT SELF MESSAGE"; return stop(); @@ -347,7 +348,7 @@ class CheckTestA : public Task { auto &message = updateNewMessage->message_; if (message->content_->get_id() == td_api::messageText::ID) { auto messageText = move_tl_object_as(message->content_); - auto text = messageText->text_; + auto text = messageText->text_->text_; if (text.substr(0, tag_.size()) == tag_) { CHECK(text > previous_text_) << tag("now", text) << tag("previous", previous_text_); previous_text_ = text; @@ -373,14 +374,11 @@ class TestA : public Task { for (int i = 0; i < 20; i++) { this->send_query(make_tl_object( chat->id_, 0, false, false, nullptr, - make_tl_object(PSTRING() << tag_ << " " << (1000 + i), false, - false, Auto(), nullptr)), + make_tl_object( + make_tl_object(PSTRING() << tag_ << " " << (1000 + i), Auto()), + false, false)), [&](auto res) { this->stop(); }); } - // sendMessage chat_id:long reply_to_message_id:int disable_notification:Bool from_background:Bool - // reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; - // inputMessageText text:string disable_web_page_preview:Bool clear_draft:Bool entities:vector - // parse_mode:TextParseMode = InputMessageContent; }); } @@ -424,8 +422,9 @@ class TestSecretChat : public Task { for (int i = 0; i < 20; i++) { send_query(make_tl_object( chat_id_, 0, false, false, nullptr, - make_tl_object(PSTRING() << tag_ << " " << (1000 + i), false, false, - Auto(), nullptr)), + make_tl_object( + make_tl_object(PSTRING() << tag_ << " " << (1000 + i), Auto()), false, + false)), [](auto res) {}); } } @@ -456,7 +455,7 @@ class TestFileGenerated : public Task { chat_id_ = message->chat_id_; if (message->content_->get_id() == td_api::messageText::ID) { auto messageText = move_tl_object_as(message->content_); - auto text = messageText->text_; + auto text = messageText->text_->text_; if (text.substr(0, tag_.size()) == tag_) { if (text.substr(tag_.size() + 1) == "ONE_FILE") { return one_file(); @@ -489,14 +488,15 @@ class TestFileGenerated : public Task { make_tl_object(file_path, "square", 0), make_tl_object( make_tl_object(file_path, "thumbnail", 0), 0, 0), - tag_)), + make_tl_object(tag_, Auto()))), [](auto res) { check_td_error(res); }); - this->send_query(make_tl_object( - chat_id_, 0, false, false, nullptr, - make_tl_object( - make_tl_object(file_path, "square", 0), nullptr, tag_)), - [](auto res) { check_td_error(res); }); + this->send_query( + make_tl_object(chat_id_, 0, false, false, nullptr, + make_tl_object( + make_tl_object(file_path, "square", 0), + nullptr, make_tl_object(tag_, Auto()))), + [](auto res) { check_td_error(res); }); } friend class GenerateFile; @@ -599,7 +599,8 @@ class CheckTestC : public Task { this->send_query( make_tl_object( chat_id_, 0, false, false, nullptr, - make_tl_object(PSTRING() << tag_ << " ONE_FILE", false, false, Auto(), nullptr)), + make_tl_object( + make_tl_object(PSTRING() << tag_ << " ONE_FILE", Auto()), false, false)), [](auto res) { check_td_error(res); }); } @@ -612,7 +613,7 @@ class CheckTestC : public Task { auto &message = updateNewMessage->message_; if (message->content_->get_id() == td_api::messageDocument::ID) { auto messageDocument = move_tl_object_as(message->content_); - auto text = messageDocument->caption_; + auto text = messageDocument->caption_->text_; if (text.substr(0, tag_.size()) == tag_) { file_id_to_check_ = messageDocument->document_->document_->id_; LOG(ERROR) << "GOT FILE " << to_string(messageDocument->document_->document_);