diff --git a/CMake/TdSetUpCompiler.cmake b/CMake/TdSetUpCompiler.cmake index 728615e27..d9d67da33 100644 --- a/CMake/TdSetUpCompiler.cmake +++ b/CMake/TdSetUpCompiler.cmake @@ -85,9 +85,8 @@ function(td_set_up_compiler) add_definitions(-D_DEFAULT_SOURCE=1 -DFD_SETSIZE=4096) endif() - if (NOT ANDROID) # _FILE_OFFSET_BITS is broken in NDK r15, r15b and r17 and doesn't work prior to Android 7.0 - add_definitions(-D_FILE_OFFSET_BITS=64) - endif() + # _FILE_OFFSET_BITS is broken in Android NDK r15, r15b and r17 and doesn't work prior to Android 7.0 + add_definitions(-D_FILE_OFFSET_BITS=64) if (CMAKE_SYSTEM_NAME STREQUAL "SunOS") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lsocket -lnsl") diff --git a/CMakeLists.txt b/CMakeLists.txt index f377a018b..92b8c2fc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.8.3 LANGUAGES CXX C) +project(TDLib VERSION 1.8.4 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -287,6 +287,7 @@ set(TDLIB_SOURCE td/telegram/AudiosManager.cpp td/telegram/AuthManager.cpp td/telegram/AutoDownloadSettings.cpp + td/telegram/AvailableReaction.cpp td/telegram/BackgroundManager.cpp td/telegram/BackgroundType.cpp td/telegram/BotCommand.cpp @@ -410,6 +411,7 @@ set(TDLIB_SOURCE td/telegram/PhotoSize.cpp td/telegram/PhotoSizeSource.cpp td/telegram/PollManager.cpp + td/telegram/Premium.cpp td/telegram/QueryCombiner.cpp td/telegram/RecentDialogList.cpp td/telegram/ReplyMarkup.cpp @@ -484,6 +486,7 @@ set(TDLIB_SOURCE td/telegram/AudiosManager.h td/telegram/AuthManager.h td/telegram/AutoDownloadSettings.h + td/telegram/AvailableReaction.h td/telegram/BackgroundId.h td/telegram/BackgroundManager.h td/telegram/BackgroundType.h @@ -644,6 +647,7 @@ set(TDLIB_SOURCE td/telegram/PhotoSizeSource.h td/telegram/PollId.h td/telegram/PollManager.h + td/telegram/Premium.h td/telegram/PrivacyManager.h td/telegram/PtsManager.h td/telegram/PublicDialogType.h diff --git a/README.md b/README.md index e57e23316..60a925e5a 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic) Or you could install `TDLib` and then reference it in your CMakeLists.txt like this: ``` -find_package(Td 1.8.3 REQUIRED) +find_package(Td 1.8.4 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlight-team/tdlight/tree/master/example/cpp/CMakeLists.txt). diff --git a/build.html b/build.html index e16968de2..614b93356 100644 --- a/build.html +++ b/build.html @@ -928,6 +928,7 @@ function onOptionsChanged() { if (use_vcpkg) { commands.push('git clone https://github.com/Microsoft/vcpkg.git'); commands.push('cd vcpkg'); + commands.push('git checkout 1b1ae50e1a69f7c659bd7d731e80b358d21c86ad'); commands.push(local + 'bootstrap-vcpkg.bat'); if (target === 'C++/CX') { commands.push(local + 'vcpkg.exe install gperf:x86-windows openssl:arm-uwp openssl:arm64-uwp openssl:x64-uwp openssl:x86-uwp zlib:arm-uwp zlib:arm64-uwp zlib:x64-uwp zlib:x86-uwp'); diff --git a/example/README.md b/example/README.md index 4d0b6c741..302e0104c 100644 --- a/example/README.md +++ b/example/README.md @@ -166,8 +166,7 @@ TDLib can be used from the Dart programming language through the [JSON](https:// See [dart_tdlib](https://github.com/periodicaidan/dart_tdlib) or [Dart wrapper for TDLib](https://github.com/tdlight-team/tdlight/pull/708/commits/237060abd4c205768153180e9f814298d1aa9d49) for an example of a TDLib Dart bindings through FFI. -See [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](https://github.com/i-Naji/tdlib), [tdlib](https://github.com/pqhaz/tdlib), -[tdlib-dart](https://github.com/drewpayment/tdlib-dart), [tdlib-dart](https://github.com/triedcatched/tdlib-dart), or [telegram-service](https://github.com/igorder-dev/telegram-service) for examples of using TDLib from Dart. +See [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](https://github.com/i-Naji/tdlib), [tdlib-dart](https://github.com/drewpayment/tdlib-dart), [tdlib-dart](https://github.com/triedcatched/tdlib-dart), or [telegram-service](https://github.com/igorder-dev/telegram-service) for examples of using TDLib from Dart. ## Using TDLib in Rust projects diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index a766bc3c7..e2801ef75 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.8.3 REQUIRED) +find_package(Td 1.8.4 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/java/CMakeLists.txt b/example/java/CMakeLists.txt index 602a5fa99..2c685a774 100644 --- a/example/java/CMakeLists.txt +++ b/example/java/CMakeLists.txt @@ -65,7 +65,7 @@ add_custom_target(build_java ) add_custom_target(generate_javadoc - COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib + COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib WORKING_DIRECTORY ${TD_API_JAVA_PATH} COMMENT "Generating Javadoc documentation" DEPENDS td_generate_java_api diff --git a/example/uwp/extension.vsixmanifest b/example/uwp/extension.vsixmanifest index 5b33a6ab4..0a3ba73bb 100644 --- a/example/uwp/extension.vsixmanifest +++ b/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/td/generate/scheme/secret_api.tl b/td/generate/scheme/secret_api.tl index c47756a2e..4dd5e8345 100644 --- a/td/generate/scheme/secret_api.tl +++ b/td/generate/scheme/secret_api.tl @@ -78,7 +78,7 @@ documentAttributeAudio45#ded218e0 duration:int title:string performer:string = D decryptedMessage46#36b091de flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector via_bot_name:flags.11?string reply_to_random_id:flags.3?long = DecryptedMessage; decryptedMessageMediaPhoto#f1fa8d78 thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes caption:string = DecryptedMessageMedia; decryptedMessageMediaVideo#970c8c0e thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes caption:string = DecryptedMessageMedia; -decryptedMessageMediaDocument#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector caption:string = DecryptedMessageMedia; +decryptedMessageMediaDocument46#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector caption:string = DecryptedMessageMedia; documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity; @@ -117,6 +117,10 @@ messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; +// layer 143 + +decryptedMessageMediaDocument#6abd9782 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:long key:bytes iv:bytes attributes:Vector caption:string = DecryptedMessageMedia; + ---functions--- test.dummyFunction = Bool; diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 370fc4bdd..e34aa3775 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -142,7 +142,7 @@ temporaryPasswordState has_password:Bool valid_for:int32 = TemporaryPasswordStat //@download_offset Download will be started from this offset. downloaded_prefix_size is calculated from this offset //@downloaded_prefix_size If is_downloading_completed is false, then only some prefix of the file starting from download_offset is ready to be read. downloaded_prefix_size is the size of that prefix in bytes //@downloaded_size Total downloaded file size, in bytes. Can be used only for calculating download progress. The actual file size may be bigger, and some parts of it may contain garbage -localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_active:Bool is_downloading_completed:Bool download_offset:int32 downloaded_prefix_size:int32 downloaded_size:int32 = LocalFile; +localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_active:Bool is_downloading_completed:Bool download_offset:int53 downloaded_prefix_size:int53 downloaded_size:int53 = LocalFile; //@description Represents a remote file //@id Remote file identifier; may be empty. Can be used by the current user across application restarts or even from other devices. Uniquely identifies a file, but a file can have a lot of different valid identifiers. @@ -152,7 +152,7 @@ localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_ //@is_uploading_active True, if the file is currently being uploaded (or a remote copy is being generated by some other means) //@is_uploading_completed True, if a remote copy is fully available //@uploaded_size Size of the remote available part of the file, in bytes; 0 if unknown -remoteFile id:string unique_id:string is_uploading_active:Bool is_uploading_completed:Bool uploaded_size:int32 = RemoteFile; +remoteFile id:string unique_id:string is_uploading_active:Bool is_uploading_completed:Bool uploaded_size:int53 = RemoteFile; //@description Represents a file //@id Unique file identifier @@ -160,7 +160,7 @@ remoteFile id:string unique_id:string is_uploading_active:Bool is_uploading_comp //@expected_size Approximate file size in bytes in case the exact file size is unknown. Can be used to show download/upload progress //@local Information about the local copy of the file //@remote Information about the remote copy of the file -file id:int32 size:int32 expected_size:int32 local:localFile remote:remoteFile = File; +file id:int32 size:int53 expected_size:int53 local:localFile remote:remoteFile = File; //@class InputFile @description Points to a file @@ -179,7 +179,7 @@ inputFileLocal path:string = InputFile; //@description A file generated by the application @original_path Local path to a file from which the file is generated; may be empty if there is no such file //@conversion String specifying the conversion applied to the original file; must be persistent across application restarts. Conversions beginning with '#' are reserved for internal TDLib usage //@expected_size Expected size of the generated file, in bytes; 0 if unknown -inputFileGenerated original_path:string conversion:string expected_size:int32 = InputFile; +inputFileGenerated original_path:string conversion:string expected_size:int53 = InputFile; //@description Describes an image in JPEG format @type Image type (see https://core.telegram.org/constructor/photoSize) @@ -296,8 +296,8 @@ photo has_stickers:Bool minithumbnail:minithumbnail sizes:vector = Ph //@description Describes a sticker @set_id The identifier of the sticker set to which the sticker belongs; 0 if none @width Sticker width; as defined by the sender @height Sticker height; as defined by the sender //@emoji Emoji corresponding to the sticker @type Sticker type @outline Sticker's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner -//@thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker -sticker set_id:int64 width:int32 height:int32 emoji:string type:StickerType outline:vector thumbnail:thumbnail sticker:file = Sticker; +//@thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @premium_animation Premium animation of the sticker; may be null. If present, only Premium users can send the sticker @sticker File containing the sticker +sticker set_id:int64 width:int32 height:int32 emoji:string type:StickerType outline:vector thumbnail:thumbnail premium_animation:file sticker:file = Sticker; //@description Describes a video file @duration Duration of the video, in seconds; as defined by the sender @width Video width; as defined by the sender @height Video height; as defined by the sender //@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender @@ -311,9 +311,11 @@ video duration:int32 width:int32 height:int32 file_name:string mime_type:string //@thumbnail Video thumbnail in JPEG format; as defined by the sender; may be null @video File containing the video videoNote duration:int32 length:int32 minithumbnail:minithumbnail thumbnail:thumbnail video:file = VideoNote; -//@description Describes a voice note. The voice note must be encoded with the Opus codec, and stored inside an OGG container. Voice notes can have only a single audio channel @duration Duration of the voice note, in seconds; as defined by the sender -//@waveform A waveform representation of the voice note in 5-bit format @mime_type MIME type of the file; as defined by the sender @voice File containing the voice note -voiceNote duration:int32 waveform:bytes mime_type:string voice:file = VoiceNote; +//@description Describes a voice note. The voice note must be encoded with the Opus codec, and stored inside an OGG container. Voice notes can have only a single audio channel +//@duration Duration of the voice note, in seconds; as defined by the sender @waveform A waveform representation of the voice note in 5-bit format +//@mime_type MIME type of the file; as defined by the sender @is_recognized True, if speech recognition is completed; Premium users only +//@recognized_text Recognized text of the voice note; Premium users only. Call recognizeSpeech to get recognized text of the voice note @voice File containing the voice note +voiceNote duration:int32 waveform:bytes mime_type:string is_recognized:Bool recognized_text:string voice:file = VoiceNote; //@description Describes an animated representation of an emoji //@sticker Animated sticker for the emoji @@ -419,8 +421,9 @@ animatedChatPhoto length:int32 file:file main_frame_timestamp:double = AnimatedC //@added_date Point in time (Unix timestamp) when the photo has been added //@minithumbnail Photo minithumbnail; may be null //@sizes Available variants of the photo in JPEG format, in different size -//@animation Animated variant of the photo in MPEG4 format; may be null -chatPhoto id:int64 added_date:int32 minithumbnail:minithumbnail sizes:vector animation:animatedChatPhoto = ChatPhoto; +//@animation A big (640x640) animated variant of the photo in MPEG4 format; may be null +//@small_animation A small (160x160) animated variant of the photo in MPEG4 format; may be null even the big animation is available +chatPhoto id:int64 added_date:int32 minithumbnail:minithumbnail sizes:vector animation:animatedChatPhoto small_animation:animatedChatPhoto = ChatPhoto; //@description Contains a list of chat or user profile photos @total_count Total number of photos @photos List of photos chatPhotos total_count:int32 photos:vector = ChatPhotos; @@ -477,6 +480,7 @@ chatAdministratorRights can_manage_chat:Bool can_change_info:Bool can_post_messa //@is_contact The user is a contact of the current user //@is_mutual_contact The user is a contact of the current user and the current user is a contact of the user //@is_verified True, if the user is verified +//@is_premium True, if the user is a Telegram Premium user //@is_support True, if the user is Telegram support account //@restriction_reason If non-empty, it contains a human-readable description of the reason why access to this user must be restricted //@is_scam True, if many users reported this user as a scam @@ -484,16 +488,20 @@ chatAdministratorRights can_manage_chat:Bool can_change_info:Bool can_post_messa //@have_access If false, the user is inaccessible, and the only information known about the user is inside this class. Identifier of the user can't be passed to any method except GetUser //@type Type of the user //@language_code IETF language tag of the user's language; only available to bots -user id:int53 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string = User; +//@added_to_attachment_menu True, if the user added the current bot to attachment menu; only available to bots +user id:int53 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; //@description Contains information about a bot //@share_text The text that is shown on the bot's profile page and is sent together with the link when users share the bot //@param_description The text shown in the chat with the bot if the chat is empty +//@photo Photo shown in the chat with the bot if the chat is empty; may be null +//@animation Animation shown in the chat with the bot if the chat is empty; may be null //@menu_button Information about a button to show instead of the bot commands menu button; may be null if ordinary bot commands menu must be shown //@commands List of the bot commands //@default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; may be null //@default_channel_administrator_rights Default administrator rights for adding the bot to channels; may be null -botInfo share_text:string description:string menu_button:botMenuButton commands:vector default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights = BotInfo; +botInfo share_text:string description:string photo:photo animation:animation menu_button:botMenuButton commands:vector default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights = BotInfo; + //@description Contains full information about a user //@photo User profile photo; may be null @@ -503,10 +511,10 @@ botInfo share_text:string description:string menu_button:botMenuButton commands: //@has_private_calls True, if the user can't be called due to their privacy settings //@has_private_forwards True, if the user can't be linked in forwarded messages due to their privacy settings //@need_phone_number_privacy_exception True, if the current user needs to explicitly allow to share their phone number with the user when the method addContact is used -//@bio A short user bio +//@bio A short user bio; may be null for bots //@group_in_common_count Number of group chats where both the other user and the current user are a member; 0 for the current user //@bot_info For bots, information about the bot; may be null -userFullInfo photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool need_phone_number_privacy_exception:Bool bio:string group_in_common_count:int32 bot_info:botInfo = UserFullInfo; +userFullInfo photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool need_phone_number_privacy_exception:Bool bio:formattedText group_in_common_count:int32 bot_info:botInfo = UserFullInfo; //@description Represents a list of users @total_count Approximate total number of users found @user_ids A list of user identifiers users total_count:int32 user_ids:vector = Users; @@ -695,6 +703,8 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@has_linked_chat True, if the channel has a discussion group, or the supergroup is the designated discussion group for a channel //@has_location True, if the supergroup is connected to a location, i.e. the supergroup is a location-based supergroup //@sign_messages True, if messages sent to the channel need to contain information about the sender. This field is only applicable to channels +//@join_to_send_messages True, if users need to join the supergroup before they can send messages. Always true for channels and non-discussion supergroups +//@join_by_request True, if all users directly joining the supergroup need to be approved by supergroup administrators. Always false for channels and supergroups without username, location, or a linked chat //@is_slow_mode_enabled True, if the slow mode is enabled in the supergroup //@is_channel True, if the supergroup is a channel //@is_broadcast_group True, if the supergroup is a broadcast group, i.e. only administrators can send messages and there is no limit on the number of members @@ -702,7 +712,7 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this supergroup or channel must be restricted //@is_scam True, if many users reported this supergroup or channel as a scam //@is_fake True, if many users reported this supergroup or channel as a fake account -supergroup id:int53 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool = Supergroup; +supergroup id:int53 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool join_to_send_messages:Bool join_by_request:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool = Supergroup; //@description Contains full information about a supergroup or channel //@photo Chat photo; may be null @@ -748,7 +758,7 @@ secretChatStateClosed = SecretChatState; //@is_outbound True, if the chat was created by the current user; otherwise false //@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 little-endian bytes, which must be split into groups of 2 bits, each denoting a pixel of one of 4 colors FFFFFF, D5E6F3, 2D5775, and 2F99C9. //-The pixels must be used to make a 12x12 square image filled from left to right, top to bottom. Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers -//@layer Secret chat layer; determines features supported by the chat partner's application. Nested text entities and underline and strikethrough entities are supported if the layer >= 101 +//@layer Secret chat layer; determines features supported by the chat partner's application. Nested text entities and underline and strikethrough entities are supported if the layer >= 101, files bigger than 2000MB are supported if the layer >= 143 secretChat id:int32 user_id:int53 state:SecretChatState is_outbound:Bool key_hash:bytes layer:int32 = SecretChat; @@ -897,11 +907,12 @@ messageCalendar total_count:int32 days:vector = MessageCalen //@description Describes a sponsored message //@message_id Message identifier; unique for the chat to which the sponsored message belongs among both ordinary and sponsored messages +//@is_recommended True, if the message needs to be labeled as "recommended" instead of "sponsored" //@sponsor_chat_id Sponsor chat identifier; 0 if the sponsor chat is accessible through an invite link //@sponsor_chat_info Information about the sponsor chat; may be null unless sponsor_chat_id == 0 //@link An internal link to be opened when the sponsored message is clicked; may be null if the sponsor chat needs to be opened instead //@content Content of the message. Currently, can be only of the type messageText -sponsoredMessage message_id:int53 sponsor_chat_id:int53 sponsor_chat_info:chatInviteLinkInfo link:InternalLinkType content:MessageContent = SponsoredMessage; +sponsoredMessage message_id:int53 is_recommended:Bool sponsor_chat_id:int53 sponsor_chat_info:chatInviteLinkInfo link:InternalLinkType content:MessageContent = SponsoredMessage; //@description Describes a file added to file download list @@ -980,9 +991,9 @@ chatTypeSecret secret_chat_id:int32 user_id:int53 = ChatType; //@title The title of the filter; 1-12 characters without line feeds //@icon_name The chosen icon name for short filter representation. If non-empty, must be one of "All", "Unread", "Unmuted", "Bots", "Channels", "Groups", "Private", "Custom", "Setup", "Cat", "Crown", "Favorite", "Flower", "Game", "Home", "Love", "Mask", "Party", "Sport", "Study", "Trade", "Travel", "Work". //-If empty, use getChatFilterDefaultIconName to get default icon name for the filter -//@pinned_chat_ids The chat identifiers of pinned chats in the filtered chat list -//@included_chat_ids The chat identifiers of always included chats in the filtered chat list -//@excluded_chat_ids The chat identifiers of always excluded chats in the filtered chat list +//@pinned_chat_ids The chat identifiers of pinned chats in the filtered chat list. There can be up to GetOption("chat_filter_chosen_chat_count_max") pinned and always included non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium +//@included_chat_ids The chat identifiers of always included chats in the filtered chat list. There can be up to GetOption("chat_filter_chosen_chat_count_max") pinned and always included non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium +//@excluded_chat_ids The chat identifiers of always excluded chats in the filtered chat list. There can be up to GetOption("chat_filter_chosen_chat_count_max") always excluded non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium //@exclude_muted True, if muted chats need to be excluded //@exclude_read True, if read chats need to be excluded //@exclude_archived True, if archived chats need to be excluded @@ -1143,7 +1154,7 @@ keyboardButtonTypeRequestLocation = KeyboardButtonType; //@description A button that allows the user to create and send a poll when pressed; available only in private chats @force_regular If true, only regular polls must be allowed to create @force_quiz If true, only polls in quiz mode must be allowed to create keyboardButtonTypeRequestPoll force_regular:Bool force_quiz:Bool = KeyboardButtonType; -//@description A button that opens a web app by calling getWebAppUrl @url An HTTP URL to pass to getWebAppUrl +//@description A button that opens a Web App by calling getWebAppUrl @url An HTTP URL to pass to getWebAppUrl keyboardButtonTypeWebApp url:string = KeyboardButtonType; @@ -1159,7 +1170,7 @@ inlineKeyboardButtonTypeUrl url:string = InlineKeyboardButtonType; //@description A button that opens a specified URL and automatically authorize the current user by calling getLoginUrlInfo @url An HTTP URL to pass to getLoginUrlInfo @id Unique button identifier @forward_text If non-empty, new text of the button in forwarded messages inlineKeyboardButtonTypeLoginUrl url:string id:int53 forward_text:string = InlineKeyboardButtonType; -//@description A button that opens a web app by calling openWebApp @url An HTTP URL to pass to openWebApp +//@description A button that opens a Web App by calling openWebApp @url An HTTP URL to pass to openWebApp inlineKeyboardButtonTypeWebApp url:string = InlineKeyboardButtonType; //@description A button that sends a callback query to a bot @data Data to be sent to the bot via a callback query @@ -1219,7 +1230,7 @@ loginUrlInfoOpen url:string skip_confirm:Bool = LoginUrlInfo; loginUrlInfoRequestConfirmation url:string domain:string bot_user_id:int53 request_write_access:Bool = LoginUrlInfo; -//@description Contains information about a web app @launch_id Unique identifier for the web app launch @url A web app URL to open in a web view +//@description Contains information about a Web App @launch_id Unique identifier for the Web App launch @url A Web App URL to open in a web view webAppInfo launch_id:int64 url:string = WebAppInfo; @@ -1481,19 +1492,21 @@ bankCardInfo title:string actions:vector = BankCardInfo; address country_code:string state:string city:string street_line1:string street_line2:string postal_code:string = Address; -//@description Contains parameters of the app theme @background_color A color of the background in the RGB24 format @text_color A color of text in the RGB24 format -//@hint_color A color of hints in the RGB24 format @link_color A color of links in the RGB24 format @button_color A color of the buttons in the RGB24 format +//@description Contains parameters of the application theme @background_color A color of the background in the RGB24 format @secondary_background_color A secondary color for the background in the RGB24 format +//@text_color A color of text in the RGB24 format @hint_color A color of hints in the RGB24 format @link_color A color of links in the RGB24 format @button_color A color of the buttons in the RGB24 format //@button_text_color A color of text on the buttons in the RGB24 format -themeParameters background_color:int32 text_color:int32 hint_color:int32 link_color:int32 button_color:int32 button_text_color:int32 = ThemeParameters; +themeParameters background_color:int32 secondary_background_color:int32 text_color:int32 hint_color:int32 link_color:int32 button_color:int32 button_text_color:int32 = ThemeParameters; //@description Portion of the price of a product (e.g., "delivery cost", "tax amount") @label Label for this portion of the product price @amount Currency amount in the smallest units of the currency labeledPricePart label:string amount:int53 = LabeledPricePart; -//@description Product invoice @currency ISO 4217 currency code +//@description Product invoice +//@currency ISO 4217 currency code //@price_parts A list of objects used to calculate the total price of the product //@max_tip_amount The maximum allowed amount of tip in the smallest units of the currency //@suggested_tip_amounts Suggested amounts of tip in the smallest units of the currency +//@recurring_payment_terms_of_service_url An HTTP URL with terms of service for recurring payments. If non-empty, the invoice payment will result in recurring payments and the user must accept the terms of service before allowed to pay //@is_test True, if the payment is a test payment //@need_name True, if the user's name is needed for payment //@need_phone_number True, if the user's phone number is needed for payment @@ -1502,7 +1515,7 @@ labeledPricePart label:string amount:int53 = LabeledPricePart; //@send_phone_number_to_provider True, if the user's phone number will be sent to the provider //@send_email_address_to_provider True, if the user's email address will be sent to the provider //@is_flexible True, if the total price depends on the shipping method -invoice currency:string price_parts:vector max_tip_amount:int53 suggested_tip_amounts:vector is_test:Bool need_name:Bool need_phone_number:Bool need_email_address:Bool need_shipping_address:Bool send_phone_number_to_provider:Bool send_email_address_to_provider:Bool is_flexible:Bool = Invoice; +invoice currency:string price_parts:vector max_tip_amount:int53 suggested_tip_amounts:vector recurring_payment_terms_of_service_url:string is_test:Bool need_name:Bool need_phone_number:Bool need_email_address:Bool need_shipping_address:Bool send_phone_number_to_provider:Bool send_email_address_to_provider:Bool is_flexible:Bool = Invoice; //@description Order information @name Name of the user @phone_number Phone number of the user @email_address Email address of the user @shipping_address Shipping address for this order; may be null orderInfo name:string phone_number:string email_address:string shipping_address:address = OrderInfo; @@ -1513,6 +1526,7 @@ shippingOption id:string title:string price_parts:vector = Shi //@description Contains information about saved card credentials @id Unique identifier of the saved credentials @title Title of the saved credentials savedCredentials id:string title:string = SavedCredentials; + //@class InputCredentials @description Contains information about the payment method chosen by the user //@description Applies if a user chooses some previously saved payment credentials. To use their previously saved credentials, the user must have a valid temporary password @saved_credentials_id Identifier of the saved credentials @@ -1527,21 +1541,33 @@ inputCredentialsApplePay data:string = InputCredentials; //@description Applies if a user enters new credentials using Google Pay @data JSON-encoded data with the credential identifier inputCredentialsGooglePay data:string = InputCredentials; + +//@class PaymentProvider @description Contains information about a payment provider + +//@description Smart Glocal payment provider @public_token Public payment token +paymentProviderSmartGlocal public_token:string = PaymentProvider; + //@description Stripe payment provider @publishable_key Stripe API publishable key @need_country True, if the user country must be provided @need_postal_code True, if the user ZIP/postal code must be provided @need_cardholder_name True, if the cardholder name must be provided -paymentsProviderStripe publishable_key:string need_country:Bool need_postal_code:Bool need_cardholder_name:Bool = PaymentsProviderStripe; +paymentProviderStripe publishable_key:string need_country:Bool need_postal_code:Bool need_cardholder_name:Bool = PaymentProvider; + +//@description Some other payment provider, for which a web payment form must be shown @url Payment form URL +paymentProviderOther url:string = PaymentProvider; + //@description Contains information about an invoice payment form //@id The payment form identifier -//@invoice Full information of the invoice -//@url Payment form URL +//@invoice Full information about the invoice //@seller_bot_user_id User identifier of the seller bot -//@payments_provider_user_id User identifier of the payment provider bot -//@payments_provider Information about the payment provider, if available, to support it natively without the need for opening the URL; may be null +//@payment_provider_user_id User identifier of the payment provider bot +//@payment_provider Information about the payment provider //@saved_order_info Saved server-side order information; may be null //@saved_credentials Information about saved card credentials; may be null //@can_save_credentials True, if the user can choose to save credentials //@need_password True, if the user will be able to save credentials protected by a password they set up -paymentForm id:int64 invoice:invoice url:string seller_bot_user_id:int53 payments_provider_user_id:int53 payments_provider:paymentsProviderStripe saved_order_info:orderInfo saved_credentials:savedCredentials can_save_credentials:Bool need_password:Bool = PaymentForm; +//@product_title Product title +//@product_description Product description +//@product_photo Product photo; may be null +paymentForm id:int64 invoice:invoice seller_bot_user_id:int53 payment_provider_user_id:int53 payment_provider:PaymentProvider saved_order_info:orderInfo saved_credentials:savedCredentials can_save_credentials:Bool need_password:Bool product_title:string product_description:formattedText product_photo:photo = PaymentForm; //@description Contains a temporary identifier of validated order information, which is stored for one hour. Also contains the available shipping options @order_info_id Temporary identifier of the order information @shipping_options Available shipping options validatedOrderInfo order_info_id:string shipping_options:vector = ValidatedOrderInfo; @@ -1555,13 +1581,22 @@ paymentResult success:Bool verification_url:string = PaymentResult; //@photo Product photo; may be null //@date Point in time (Unix timestamp) when the payment was made //@seller_bot_user_id User identifier of the seller bot -//@payments_provider_user_id User identifier of the payment provider bot +//@payment_provider_user_id User identifier of the payment provider bot //@invoice Information about the invoice //@order_info Order information; may be null //@shipping_option Chosen shipping option; may be null //@credentials_title Title of the saved credentials chosen by the buyer //@tip_amount The amount of tip chosen by the buyer in the smallest units of the currency -paymentReceipt title:string description:string photo:photo date:int32 seller_bot_user_id:int53 payments_provider_user_id:int53 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string tip_amount:int53 = PaymentReceipt; +paymentReceipt title:string description:formattedText photo:photo date:int32 seller_bot_user_id:int53 payment_provider_user_id:int53 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string tip_amount:int53 = PaymentReceipt; + + +//@class InputInvoice @description Describe an invoice to process + +//@description An invoice from a message of the type messageInvoice @chat_id Chat identifier of the message @message_id Message identifier +inputInvoiceMessage chat_id:int53 message_id:int53 = InputInvoice; + +//@description An invoice from a link of the type internalLinkTypeInvoice @name Name of the invoice +inputInvoiceName name:string = InputInvoice; //@description File with the date it was uploaded @file The file @date Point in time (Unix timestamp) when the file was uploaded @@ -1834,8 +1869,8 @@ messagePhoto photo:photo caption:formattedText is_secret:Bool = MessageContent; //@description An expired photo message (self-destructed after TTL has elapsed) messageExpiredPhoto = MessageContent; -//@description A sticker message @sticker The sticker description -messageSticker sticker:sticker = MessageContent; +//@description A sticker message @sticker The sticker description @is_premium True, if premium animation of the sticker must be played +messageSticker sticker:sticker is_premium:Bool = MessageContent; //@description A video message @video The video description @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped messageVideo video:video caption:formattedText is_secret:Bool = MessageContent; @@ -1881,7 +1916,7 @@ messagePoll poll:poll = MessageContent; //@description A message with an invoice from a bot @title Product title @param_description Product description @photo Product photo; may be null @currency Currency for the product price @total_amount Product total price in the smallest units of the currency //@start_parameter Unique invoice bot start_parameter. To share an invoice use the URL https://t.me/{bot_username}?start={start_parameter} @is_test True, if the invoice is a test invoice //@need_shipping_address True, if the shipping address must be specified @receipt_message_id The identifier of the message with the receipt, after the product has been purchased -messageInvoice title:string description:string photo:photo currency:string total_amount:int53 start_parameter:string is_test:Bool need_shipping_address:Bool receipt_message_id:int53 = MessageContent; +messageInvoice title:string description:formattedText photo:photo currency:string total_amount:int53 start_parameter:string is_test:Bool need_shipping_address:Bool receipt_message_id:int53 = MessageContent; //@description A message with information about an ended call @is_video True, if the call was a video call @discard_reason Reason why the call was discarded @duration Call duration, in seconds messageCall is_video:Bool discard_reason:CallDiscardReason duration:int32 = MessageContent; @@ -1949,13 +1984,16 @@ messageCustomServiceAction text:string = MessageContent; //@description A new high score was achieved in a game @game_message_id Identifier of the message with the game, can be an identifier of a deleted message @game_id Identifier of the game; may be different from the games presented in the message with the game @score New score messageGameScore game_message_id:int53 game_id:int64 score:int32 = MessageContent; -//@description A payment has been completed @invoice_chat_id Identifier of the chat, containing the corresponding invoice message; 0 if unknown @invoice_message_id Identifier of the message with the corresponding invoice; can be an identifier of a deleted message @currency Currency for the price of the product @total_amount Total price for the product, in the smallest units of the currency -messagePaymentSuccessful invoice_chat_id:int53 invoice_message_id:int53 currency:string total_amount:int53 = MessageContent; +//@description A payment has been completed @invoice_chat_id Identifier of the chat, containing the corresponding invoice message; 0 if unknown @invoice_message_id Identifier of the message with the corresponding invoice; can be 0 or an identifier of a deleted message +//@currency Currency for the price of the product @total_amount Total price for the product, in the smallest units of the currency +//@is_recurring True, if this is a recurring payment @is_first_recurring True, if this is the first recurring payment @invoice_name Name of the invoice; may be empty if unknown +messagePaymentSuccessful invoice_chat_id:int53 invoice_message_id:int53 currency:string total_amount:int53 is_recurring:Bool is_first_recurring:Bool invoice_name:string = MessageContent; -//@description A payment has been completed; for bots only @currency Currency for price of the product -//@total_amount Total price for the product, in the smallest units of the currency @invoice_payload Invoice payload @shipping_option_id Identifier of the shipping option chosen by the user; may be empty if not applicable @order_info Information about the order; may be null +//@description A payment has been completed; for bots only @currency Currency for price of the product @total_amount Total price for the product, in the smallest units of the currency +//@is_recurring True, if this is a recurring payment @is_first_recurring True, if this is the first recurring payment +//@invoice_payload Invoice payload @shipping_option_id Identifier of the shipping option chosen by the user; may be empty if not applicable @order_info Information about the order; may be null //@telegram_payment_charge_id Telegram payment identifier @provider_payment_charge_id Provider payment identifier -messagePaymentSuccessfulBot currency:string total_amount:int53 invoice_payload:bytes shipping_option_id:string order_info:orderInfo telegram_payment_charge_id:string provider_payment_charge_id:string = MessageContent; +messagePaymentSuccessfulBot currency:string total_amount:int53 is_recurring:Bool is_first_recurring:Bool invoice_payload:bytes shipping_option_id:string order_info:orderInfo telegram_payment_charge_id:string provider_payment_charge_id:string = MessageContent; //@description A contact has registered with Telegram messageContactRegistered = MessageContent; @@ -1963,10 +2001,10 @@ messageContactRegistered = MessageContent; //@description The current user has connected a website by logging in using Telegram Login Widget on it @domain_name Domain name of the connected website messageWebsiteConnected domain_name:string = MessageContent; -//@description Data from a web app has been sent to a bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the web app +//@description Data from a Web App has been sent to a bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App messageWebAppDataSent button_text:string = MessageContent; -//@description Data from a web app has been received; for bots only @button_text Text of the keyboardButtonTypeWebApp button, which opened the web app @data Received data +//@description Data from a Web App has been received; for bots only @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App @data Received data messageWebAppDataReceived button_text:string data:string = MessageContent; //@description Telegram Passport data has been sent to a bot @types List of Telegram Passport element types sent @@ -2089,7 +2127,7 @@ inputMessageAudio audio:InputFile album_cover_thumbnail:inputThumbnail duration: //@description A document message (general file) @document Document to be sent @thumbnail Document thumbnail; pass null to skip thumbnail uploading @disable_content_type_detection If true, automatic file type detection will be disabled and the document will be always sent as file. Always true for files sent to secret chats @caption Document caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters inputMessageDocument document:InputFile thumbnail:inputThumbnail disable_content_type_detection:Bool caption:formattedText = InputMessageContent; -//@description A photo message @photo Photo to send @thumbnail Photo thumbnail to be sent; pass null to skip thumbnail uploading. The thumbnail is sent to the other party only in secret chats @added_sticker_file_ids File identifiers of the stickers added to the photo, if applicable @width Photo width @height Photo height @caption Photo caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters +//@description A photo message @photo Photo to send. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20 @thumbnail Photo thumbnail to be sent; pass null to skip thumbnail uploading. The thumbnail is sent to the other party only in secret chats @added_sticker_file_ids File identifiers of the stickers added to the photo, if applicable @width Photo width @height Photo height @caption Photo caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") 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:formattedText ttl:int32 = InputMessageContent; @@ -2291,6 +2329,9 @@ stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_o //@description Represents a list of sticker sets @total_count Approximate total number of sticker sets found @sets List of sticker sets stickerSets total_count:int32 sets:vector = StickerSets; +//@description Represents a list of trending sticker sets @total_count Approximate total number of trending sticker sets @sets List of trending sticker sets @is_premium True, if the list contains sticker sets with premium stickers +trendingStickerSets total_count:int32 sets:vector is_premium:Bool = TrendingStickerSets; + //@class CallDiscardReason @description Describes the reason why a call was discarded @@ -2321,8 +2362,8 @@ callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 lib //@class CallServerType @description Describes the type of a call server -//@description A Telegram call reflector @peer_tag A peer tag to be used with the reflector -callServerTypeTelegramReflector peer_tag:bytes = CallServerType; +//@description A Telegram call reflector @peer_tag A peer tag to be used with the reflector @is_tcp True, if the server uses TCP instead of UDP +callServerTypeTelegramReflector peer_tag:bytes is_tcp:Bool = CallServerType; //@description A WebRTC server @username Username to be used for authentication @password Authentication password @supports_turn True, if the server supports TURN @supports_stun True, if the server supports STUN callServerTypeWebrtc username:string password:string supports_turn:Bool supports_stun:Bool = CallServerType; @@ -2490,14 +2531,18 @@ addedReaction reaction:string sender_id:MessageSender = AddedReaction; //@description Represents a list of reactions added to a message @total_count The total number of found reactions @reactions The list of added reactions @next_offset The offset for the next request. If empty, there are no more results addedReactions total_count:int32 reactions:vector next_offset:string = AddedReactions; +//@description Represents an available reaction @reaction Text representation of the reaction @needs_premium True, if Telegram Premium is needed to send the reaction +availableReaction reaction:string needs_premium:Bool = AvailableReaction; + //@description Represents a list of available reactions @reactions List of reactions -availableReactions reactions:vector = AvailableReactions; +availableReactions reactions:vector = AvailableReactions; //@description Contains stickers which must be used for reaction animation rendering //@reaction Text representation of the reaction //@title Reaction title //@is_active True, if the reaction can be added to new messages and enabled in chats +//@is_premium True, if the reaction is available only for Premium users //@static_icon Static icon for the reaction //@appear_animation Appear animation for the reaction //@select_animation Select animation for the reaction @@ -2505,7 +2550,7 @@ availableReactions reactions:vector = AvailableReactions; //@effect_animation Effect animation for the reaction //@around_animation Around animation for the reaction; may be null //@center_animation Center animation for the reaction; may be null -reaction reaction:string title:string is_active:Bool static_icon:sticker appear_animation:sticker select_animation:sticker activate_animation:sticker effect_animation:sticker around_animation:sticker center_animation:sticker = Reaction; +reaction reaction:string title:string is_active:Bool is_premium:Bool static_icon:sticker appear_animation:sticker select_animation:sticker activate_animation:sticker effect_animation:sticker around_animation:sticker center_animation:sticker = Reaction; //@description Represents a list of animations @animations List of animations @@ -2536,6 +2581,12 @@ attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotCol //@description Represents a bot added to attachment menu //@bot_user_id User identifier of the bot added to attachment menu +//@supports_self_chat True, if the bot supports opening from attachment menu in the chat with the bot +//@supports_user_chats True, if the bot supports opening from attachment menu in private chats with ordinary users +//@supports_bot_chats True, if the bot supports opening from attachment menu in private chats with other bots +//@supports_group_chats True, if the bot supports opening from attachment menu in basic group and supergroup chats +//@supports_channel_chats True, if the bot supports opening from attachment menu in channel chats +//@supports_settings True, if the bot supports "settings_button_pressed" event //@name Name for the bot in attachment menu //@name_color Color to highlight selected name of the bot if appropriate; may be null //@default_icon Default attachment menu icon for the bot in SVG format; may be null @@ -2544,7 +2595,8 @@ attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotCol //@android_icon Attachment menu icon for the bot in TGS format for the official Android app; may be null //@macos_icon Attachment menu icon for the bot in TGS format for the official native macOS app; may be null //@icon_color Color to highlight selected icon of the bot if appropriate; may be null -attachmentMenuBot bot_user_id:int53 name:string name_color:attachmentMenuBotColor default_icon:file ios_static_icon:file ios_animated_icon:file android_icon:file macos_icon:file icon_color:attachmentMenuBotColor = AttachmentMenuBot; +//@web_app_placeholder Default placeholder for opened Web Apps in SVG format; may be null +attachmentMenuBot bot_user_id:int53 supports_self_chat:Bool supports_user_chats:Bool supports_bot_chats:Bool supports_group_chats:Bool supports_channel_chats:Bool supports_settings:Bool name:string name_color:attachmentMenuBotColor default_icon:file ios_static_icon:file ios_animated_icon:file android_icon:file macos_icon:file icon_color:attachmentMenuBotColor web_app_placeholder:file = AttachmentMenuBot; //@description Information about the message sent by answerWebAppQuery @inline_message_id Identifier of the sent inline message, if known sentWebAppMessage inline_message_id:string = SentWebAppMessage; @@ -2864,6 +2916,109 @@ languagePackInfo id:string base_language_pack_id:string name:string native_name: localizationTargetInfo language_packs:vector = LocalizationTargetInfo; +//@class PremiumLimitType @description Describes type of a limit, increased for Premium users + +//@description The maximum number of joined supergroups and channels +premiumLimitTypeSupergroupCount = PremiumLimitType; + +//@description The maximum number of pinned chats in the main chat list +premiumLimitTypePinnedChatCount = PremiumLimitType; + +//@description The maximum number of created public chats +premiumLimitTypeCreatedPublicChatCount = PremiumLimitType; + +//@description The maximum number of saved animations +premiumLimitTypeSavedAnimationCount = PremiumLimitType; + +//@description The maximum number of favorite stickers +premiumLimitTypeFavoriteStickerCount = PremiumLimitType; + +//@description The maximum number of chat filters +premiumLimitTypeChatFilterCount = PremiumLimitType; + +//@description The maximum number of pinned and always included, or always excluded chats in a chat filter +premiumLimitTypeChatFilterChosenChatCount = PremiumLimitType; + +//@description The maximum number of pinned chats in the archive chat list +premiumLimitTypePinnedArchivedChatCount = PremiumLimitType; + +//@description The maximum length of sent media caption +premiumLimitTypeCaptionLength = PremiumLimitType; + +//@description The maximum length of the user's bio +premiumLimitTypeBioLength = PremiumLimitType; + + +//@class PremiumFeature @description Describes a feature available to Premium users + +//@description Increased limits +premiumFeatureIncreasedLimits = PremiumFeature; + +//@description Increased maximum upload file size +premiumFeatureIncreasedUploadFileSize = PremiumFeature; + +//@description Improved download speed +premiumFeatureImprovedDownloadSpeed = PremiumFeature; + +//@description The ability to convert voice notes to text +premiumFeatureVoiceRecognition = PremiumFeature; + +//@description Disabled ads +premiumFeatureDisabledAds = PremiumFeature; + +//@description Allowed to use more reactions +premiumFeatureUniqueReactions = PremiumFeature; + +//@description Allowed to use premium stickers with unique effects +premiumFeatureUniqueStickers = PremiumFeature; + +//@description Ability to change position of the main chat list, archive and mute all new chats from non-contacts, and completely disable notifications about the user's contacts joined Telegram +premiumFeatureAdvancedChatManagement = PremiumFeature; + +//@description A badge in the user's profile +premiumFeatureProfileBadge = PremiumFeature; + +//@description Profile photo animation on message and chat screens +premiumFeatureAnimatedProfilePhoto = PremiumFeature; + +//@description Allowed to set a premium appllication icons +premiumFeatureAppIcons = PremiumFeature; + + +//@description Contains information about a limit, increased for Premium users @type The type of the limit @default_value Default value of the limit @premium_value Value of the limit for Premium users +premiumLimit type:PremiumLimitType default_value:int32 premium_value:int32 = PremiumLimit; + +//@description Contains information about features, available to Premium users @features The list of available features @limits The list of limits, increased for Premium users +//@payment_link An internal link to be opened to pay for Telegram Premium if store payment isn't possible; may be null if direct payment isn't available. If the link has type internalLinkTypeBotStart, then sendBotStartMessage must be called automatically +premiumFeatures features:vector limits:vector payment_link:InternalLinkType = PremiumFeatures; + + +//@class PremiumSource @description Describes a source from which the Premium features screen is opened + +//@description A limit was exceeded @limit_type Type of the exceeded limit +premiumSourceLimitExceeded limit_type:PremiumLimitType = PremiumSource; + +//@description A user tried to use a Premium feature @feature The used feature +premiumSourceFeature feature:PremiumFeature = PremiumSource; + +//@description A user opened an internal link of the type internalLinkTypePremiumFeatures @referrer The referrer from the link +premiumSourceLink referrer:string = PremiumSource; + +//@description A user opened the Premium features screen from settings +premiumSourceSettings = PremiumSource; + + +//@description Describes a promotion animation for a Premium feature @feature Premium feature @animation Promotion animation for the feature +premiumFeaturePromotionAnimation feature:PremiumFeature animation:animation = PremiumFeaturePromotionAnimation; + +//@description Contains state of Telegram Premium subscription and promotion videos for Premium features +//@state Text description of the state of the current Premium subscription; may be empty if the current user has no Telegram Premium subscription +//@currency ISO 4217 currency code for Telegram Premium subscription payment +//@monthly_amount Monthly subscription payment for Telegram Premium subscription, in the smallest units of the currency +//@animations The list of available promotion animations for Premium features +premiumState state:formattedText currency:string monthly_amount:int53 animations:vector = PremiumState; + + //@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org //@description A token for Firebase Cloud Messaging @token Device registration token; may be empty to deregister a device @encrypt True, if push notifications must be additionally encrypted @@ -3130,6 +3285,9 @@ pushMessageContentChatJoinByLink = PushMessageContent; //@description A new member was accepted to the chat by an administrator pushMessageContentChatJoinByRequest = PushMessageContent; +//@description A new recurrent payment was made by the current user @amount The paid amount +pushMessageContentRecurringPayment amount:string = PushMessageContent; + //@description A forwarded messages @total_count Number of forwarded messages pushMessageContentMessageForwards total_count:int32 = PushMessageContent; @@ -3140,8 +3298,8 @@ pushMessageContentMediaAlbum total_count:int32 has_photos:Bool has_videos:Bool h //@class NotificationType @description Contains detailed information about a notification -//@description New message was received @message The message -notificationTypeNewMessage message:message = NotificationType; +//@description New message was received @message The message @show_preview True, if message content must be displayed in notifications +notificationTypeNewMessage message:message show_preview:Bool = NotificationType; //@description New secret chat was created notificationTypeNewSecretChat = NotificationType; @@ -3416,17 +3574,33 @@ chatReportReasonPersonalDetails = ChatReportReason; chatReportReasonCustom = ChatReportReason; -//@class InternalLinkType @description Describes an internal https://t.me or tg: link, which must be processed by the app in a special way +//@class TargetChat @description Describes the target chat to be opened -//@description The link is a link to the active sessions section of the app. Use getActiveSessions to handle the link +//@description The currently opened chat needs to be kept +targetChatCurrent = TargetChat; + +//@description The chat needs to be chosen by the user among chats of the specified types +//@allow_user_chats True, if private chats with ordinary users are allowed +//@allow_bot_chats True, if private chats with other bots are allowed +//@allow_group_chats True, if basic group and supergroup chats are allowed +//@allow_channel_chats True, if channel chats are allowed +targetChatChosen allow_user_chats:Bool allow_bot_chats:Bool allow_group_chats:Bool allow_channel_chats:Bool = TargetChat; + +//@description The chat needs to be open with the provided internal link @link An internal link pointing to the chat +targetChatInternalLink link:InternalLinkType = TargetChat; + + +//@class InternalLinkType @description Describes an internal https://t.me or tg: link, which must be processed by the application in a special way + +//@description The link is a link to the active sessions section of the application. Use getActiveSessions to handle the link internalLinkTypeActiveSessions = InternalLinkType; -//@description The link is a link to an attachment menu bot to be opened in the specified chat. Process given chat_link to open corresponding chat. +//@description The link is a link to an attachment menu bot to be opened in the specified or a chosen chat. Process given target_chat to open the chat. //-Then call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then use getAttachmentMenuBot to receive information about the bot. //-If the bot isn't added to attachment menu, then user needs to confirm adding the bot to attachment menu. If user confirms adding, then use toggleBotIsAddedToAttachmentMenu to add it. -//-If attachment menu bots can't be used in the current chat, show an error to the user. If the bot is added to attachment menu, then use openWebApp with the given URL -//@chat_link An internal link pointing to a chat; may be null if the current chat needs to be kept @bot_username Username of the bot @url URL to be passed to openWebApp -internalLinkTypeAttachmentMenuBot chat_link:InternalLinkType bot_username:string url:string = InternalLinkType; +//-If the attachment menu bot can't be used in the opened chat, show an error to the user. If the bot is added to attachment menu and can be used in the chat, then use openWebApp with the given URL +//@target_chat Target chat to be opened @bot_username Username of the bot @url URL to be passed to openWebApp +internalLinkTypeAttachmentMenuBot target_chat:TargetChat bot_username:string url:string = InternalLinkType; //@description The link contains an authentication code. Call checkAuthenticationCode with the code if the current authorization state is authorizationStateWaitCode @code The authentication code internalLinkTypeAuthenticationCode code:string = InternalLinkType; @@ -3467,6 +3641,9 @@ internalLinkTypeFilterSettings = InternalLinkType; //@bot_username Username of the bot that owns the game @game_short_name Short name of the game internalLinkTypeGame bot_username:string game_short_name:string = InternalLinkType; +//@description The link is a link to an invoice. Call getPaymentForm with the given invoice name to process the link @invoice_name Name of the invoice +internalLinkTypeInvoice invoice_name:string = InternalLinkType; + //@description The link is a link to a language pack. Call getLanguagePackInfo with the given language pack identifier to process the link @language_pack_id Language pack identifier internalLinkTypeLanguagePack language_pack_id:string = InternalLinkType; @@ -3480,7 +3657,7 @@ internalLinkTypeMessage url:string = InternalLinkType; //@text Message draft text @contains_link True, if the first line of the text contains a link. If true, the input field needs to be focused and the text after the link must be selected internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLinkType; -//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the app, otherwise ignore it +//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application, otherwise ignore it //@bot_user_id User identifier of the service's bot @scope Telegram Passport element types requested by the service @public_key Service's public key @nonce Unique request identifier provided by the service //@callback_url An HTTP URL to open once the request is finished or canceled with the parameter tg_passport=success or tg_passport=cancel respectively. If empty, then the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel needs to be opened instead internalLinkTypePassportDataRequest bot_user_id:int53 scope:string public_key:string nonce:string callback_url:string = InternalLinkType; @@ -3489,6 +3666,9 @@ internalLinkTypePassportDataRequest bot_user_id:int53 scope:string public_key:st //@hash Hash value from the link @phone_number Phone number value from the link internalLinkTypePhoneNumberConfirmation hash:string phone_number:string = InternalLinkType; +//@description The link is a link to the Premium features screen of the applcation from which the user can subscribe to Telegram Premium. Call getPremiumFeatures with the given referrer to process the link @referrer Referrer specified in the link +internalLinkTypePremiumFeatures referrer:string = InternalLinkType; + //@description The link is a link to the privacy and security settings section of the app internalLinkTypePrivacyAndSecuritySettings = InternalLinkType; @@ -3503,7 +3683,7 @@ internalLinkTypePublicChat chat_username:string = InternalLinkType; //-"This code can be used to allow someone to log in to your Telegram account. To confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code" needs to be shown internalLinkTypeQrCodeAuthentication = InternalLinkType; -//@description The link is a link to app settings +//@description The link is a link to application settings internalLinkTypeSettings = InternalLinkType; //@description The link is a link to a sticker set. Call searchStickerSet with the given sticker set name to process the link and show the sticker set @sticker_set_name Name of the sticker set @@ -3669,7 +3849,7 @@ networkStatistics since_date:int32 entries:vector = Netw //@preload_large_videos True, if the beginning of video files needs to be preloaded for instant playback //@preload_next_audio True, if the next audio track needs to be preloaded while the user is listening to an audio file //@use_less_data_for_calls True, if "use less data for calls" option needs to be enabled -autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int32 max_other_file_size:int32 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool use_less_data_for_calls:Bool = AutoDownloadSettings; +autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int53 max_other_file_size:int53 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool use_less_data_for_calls:Bool = AutoDownloadSettings; //@description Contains auto-download settings presets for the current user //@low Preset with lowest settings; supposed to be used by default when roaming @@ -3771,6 +3951,9 @@ text text:string = Text; //@description Contains a value representing a number of seconds @seconds Number of seconds seconds seconds:double = Seconds; +//@description Contains size of downloaded prefix of a file @size The prefix size, in bytes +fileDownloadedPrefixSize size:int53 = FileDownloadedPrefixSize; + //@description Contains information about a tg: deep link @text Text to be shown to the user @need_update_application True, if the user must be asked to update the application deepLinkInfo text:formattedText need_update_application:Bool = DeepLinkInfo; @@ -4058,8 +4241,8 @@ updateChatIsBlocked chat_id:int53 is_blocked:Bool = Update; //@description A chat was marked as unread or was read @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread updateChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Update; -//@description The list of chat filters or a chat filter has changed @chat_filters The new list of chat filters -updateChatFilters chat_filters:vector = Update; +//@description The list of chat filters or a chat filter has changed @chat_filters The new list of chat filters @main_chat_list_position Position of the main chat list among chat filters, 0-based +updateChatFilters chat_filters:vector main_chat_list_position:int32 = Update; //@description The number of online group members has changed. This update with non-zero number of online group members is sent only for currently opened chats. There is no guarantee that it will be sent just after the number of online users has changed @chat_id Identifier of the chat @online_member_count New number of online members in the chat, or 0 if unknown updateChatOnlineMemberCount chat_id:int53 online_member_count:int32 = Update; @@ -4196,7 +4379,7 @@ updateStickerSet sticker_set:stickerSet = Update; updateInstalledStickerSets is_masks:Bool sticker_set_ids:vector = Update; //@description The list of trending sticker sets was updated or some of them were viewed @sticker_sets The prefix of the list of trending sticker sets with the newest trending sticker sets -updateTrendingStickerSets sticker_sets:stickerSets = Update; +updateTrendingStickerSets sticker_sets:trendingStickerSets = Update; //@description The list of recently used stickers was updated @is_attached True, if the list of stickers attached to photo or video files was updated, otherwise the list of sent stickers is updated @sticker_ids The new list of file identifiers of recently used stickers updateRecentStickers is_attached:Bool sticker_ids:vector = Update; @@ -4228,10 +4411,10 @@ updateTermsOfService terms_of_service_id:string terms_of_service:termsOfService //@description The list of users nearby has changed. The update is guaranteed to be sent only 60 seconds after a successful searchChatsNearby request @users_nearby The new list of users nearby updateUsersNearby users_nearby:vector = Update; -//@description The list of bots added to attachment menu has changed @bots The new list of bots added to attachment menu. The bots must be shown in attachment menu only in private chats. The bots must not be shown on scheduled messages screen +//@description The list of bots added to attachment menu has changed @bots The new list of bots added to attachment menu. The bots must not be shown on scheduled messages screen updateAttachmentMenuBots bots:vector = Update; -//@description A message was sent by an opened web app, so the web app needs to be closed @web_app_launch_id Identifier of web app launch +//@description A message was sent by an opened Web App, so the Web App needs to be closed @web_app_launch_id Identifier of Web App launch updateWebAppMessageSent web_app_launch_id:int64 = Update; //@description The list of supported reactions has changed @reactions The new list of supported reactions @@ -4558,13 +4741,13 @@ checkChatUsername chat_id:int53 username:string = CheckChatUsernameResult; //@description Returns a list of public chats of the specified type, owned by the user @type Type of the public chats to return getCreatedPublicChats type:PublicChatType = Chats; -//@description Checks whether the maximum number of owned public chats has been reached. Returns corresponding error if the limit was reached @type Type of the public chats, for which to check the limit +//@description Checks whether the maximum number of owned public chats has been reached. Returns corresponding error if the limit was reached. The limit can be increased with Telegram Premium @type Type of the public chats, for which to check the limit checkCreatedPublicChatsLimit type:PublicChatType = Ok; //@description Returns a list of basic group and supergroup chats, which can be used as a discussion group for a channel. Returned basic group chats must be first upgraded to supergroups before they can be set as a discussion group. To set a returned supergroup as a discussion group, access to its old messages must be enabled using toggleSupergroupIsAllHistoryAvailable first getSuitableDiscussionChats = Chats; -//@description Returns a list of recently inactive supergroups and channels. Can be used when user reaches limit on the number of joined supergroups and channels and receives CHANNELS_TOO_MUCH error +//@description Returns a list of recently inactive supergroups and channels. Can be used when user reaches limit on the number of joined supergroups and channels and receives CHANNELS_TOO_MUCH error. Also, the limit can be increased with Telegram Premium getInactiveSupergroupChats = Chats; @@ -4715,6 +4898,14 @@ getMessageLinkInfo url:string = MessageLinkInfo; //@to_language_code A two-letter ISO 639-1 language code of the language to which the message is translated translateText text:string from_language_code:string to_language_code:string = Text; +//@description Recognizes speech in a voice note message. The message must be successfully sent and must not be scheduled. May return an error with a message "MSG_VOICE_TOO_LONG" if the voice note is too long to be recognized +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +recognizeSpeech chat_id:int53 message_id:int53 = Ok; + +//@description Rates recognized speech in a voice note message @chat_id Identifier of the chat to which the message belongs @message_id Identifier of the message @is_good Pass true if the speech recognition is good +rateSpeechRecognition chat_id:int53 message_id:int53 is_good:Bool = Ok; + //@description Returns list of message sender identifiers, which can be used to send messages in a chat @chat_id Chat identifier getChatAvailableMessageSenders chat_id:int53 = MessageSenders; @@ -4866,7 +5057,7 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok; -//@description Returns reactions, which can be added to a message. The list can change after updateReactions, updateChatAvailableReactions for the chat, or updateMessageInteractionInfo for the message +//@description Returns reactions, which can be added to a message. The list can change after updateReactions, updateChatAvailableReactions for the chat, or updateMessageInteractionInfo for the message. The method will return Premium reactions, even the current user has no Premium subscription //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message getMessageAvailableReactions chat_id:int53 message_id:int53 = AvailableReactions; @@ -4978,30 +5169,30 @@ getInlineQueryResults bot_user_id:int53 chat_id:int53 user_location:location que answerInlineQuery inline_query_id:int64 is_personal:Bool results:vector cache_time:int32 next_offset:string switch_pm_text:string switch_pm_parameter:string = Ok; -//@description Returns an HTTPS URL of a web app to open after keyboardButtonTypeWebApp button is pressed +//@description Returns an HTTPS URL of a Web App to open after keyboardButtonTypeWebApp button is pressed //@bot_user_id Identifier of the target bot //@url The URL from the keyboardButtonTypeWebApp button -//@theme Preferred web app theme; pass null to use the default theme +//@theme Preferred Web App theme; pass null to use the default theme getWebAppUrl bot_user_id:int53 url:string theme:themeParameters = HttpUrl; -//@description Sends data received from a keyboardButtonTypeWebApp web app to a bot -//@bot_user_id Identifier of the target bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the web app @data Received data +//@description Sends data received from a keyboardButtonTypeWebApp Web App to a bot +//@bot_user_id Identifier of the target bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App @data Received data sendWebAppData bot_user_id:int53 button_text:string data:string = Ok; -//@description Informs TDLib that a web app is being opened from attachment menu, a botMenuButton button, an internalLinkTypeAttachmentMenuBot link, or an inlineKeyboardButtonTypeWebApp button. +//@description Informs TDLib that a Web App is being opened from attachment menu, a botMenuButton button, an internalLinkTypeAttachmentMenuBot link, or an inlineKeyboardButtonTypeWebApp button. //-For each bot, a confirmation alert about data sent to the bot must be shown once -//@chat_id Identifier of the chat in which the web app is opened. Web apps can be opened only in private chats for now -//@bot_user_id Identifier of the bot, providing the web app +//@chat_id Identifier of the chat in which the Web App is opened +//@bot_user_id Identifier of the bot, providing the Web App //@url The URL from an inlineKeyboardButtonTypeWebApp button, a botMenuButton button, or an internalLinkTypeAttachmentMenuBot link, or an empty string otherwise -//@theme Preferred web app theme; pass null to use the default theme -//@reply_to_message_id Identifier of the replied message for the message sent by the web app; 0 if none +//@theme Preferred Web App theme; pass null to use the default theme +//@reply_to_message_id Identifier of the replied message for the message sent by the Web App; 0 if none openWebApp chat_id:int53 bot_user_id:int53 url:string theme:themeParameters reply_to_message_id:int53 = WebAppInfo; -//@description Informs TDLib that a previously opened web app was closed @web_app_launch_id Identifier of web app launch, received from openWebApp +//@description Informs TDLib that a previously opened Web App was closed @web_app_launch_id Identifier of Web App launch, received from openWebApp closeWebApp web_app_launch_id:int64 = Ok; -//@description Sets the result of interaction with a web app and sends corresponding message on behalf of the user to the chat from which the query originated; for bots only -//@web_app_query_id Identifier of the web app query +//@description Sets the result of interaction with a Web App and sends corresponding message on behalf of the user to the chat from which the query originated; for bots only +//@web_app_query_id Identifier of the Web App query //@result The result of the query answerWebAppQuery web_app_query_id:string result:InputInlineQueryResult = SentWebAppMessage; @@ -5123,7 +5314,7 @@ addChatToList chat_id:int53 chat_list:ChatList = Ok; //@description Returns information about a chat filter by its identifier @chat_filter_id Chat filter identifier getChatFilter chat_filter_id:int32 = ChatFilter; -//@description Creates new chat filter. Returns information about the created chat filter @filter Chat filter +//@description Creates new chat filter. Returns information about the created chat filter. There can be up to GetOption("chat_filter_count_max") chat filters, but the limit can be increased with Telegram Premium @filter Chat filter createChatFilter filter:chatFilter = ChatFilterInfo; //@description Edits existing chat filter. Returns information about the edited chat filter @chat_filter_id Chat filter identifier @filter The edited chat filter @@ -5132,8 +5323,8 @@ editChatFilter chat_filter_id:int32 filter:chatFilter = ChatFilterInfo; //@description Deletes existing chat filter @chat_filter_id Chat filter identifier deleteChatFilter chat_filter_id:int32 = Ok; -//@description Changes the order of chat filters @chat_filter_ids Identifiers of chat filters in the new correct order -reorderChatFilters chat_filter_ids:vector = Ok; +//@description Changes the order of chat filters @chat_filter_ids Identifiers of chat filters in the new correct order @main_chat_list_position Position of the main chat list among chat filters, 0-based. Can be non-zero only for Premium users +reorderChatFilters chat_filter_ids:vector main_chat_list_position:int32 = Ok; //@description Returns recommended chat filters for the current user getRecommendedChatFilters = RecommendedChatFilters; @@ -5212,7 +5403,7 @@ unpinChatMessage chat_id:int53 message_id:int53 = Ok; unpinAllChatMessages chat_id:int53 = Ok; -//@description Adds the current user as a new member to a chat. Private and secret chats can't be joined using this method @chat_id Chat identifier +//@description Adds the current user as a new member to a chat. Private and secret chats can't be joined using this method. May return an error with a message "INVITE_REQUEST_SENT" if only a join request was created @chat_id Chat identifier joinChat chat_id:int53 = Ok; //@description Removes the current user from chat members. Private and secret chats can't be left using this method @chat_id Chat identifier @@ -5290,7 +5481,7 @@ setScopeNotificationSettings scope:NotificationSettingsScope notification_settin resetAllNotificationSettings = Ok; -//@description Changes the pinned state of a chat. There can be up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") pinned non-secret chats and the same number of secret chats in the main/archive chat list +//@description Changes the pinned state of a chat. There can be up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") pinned non-secret chats and the same number of secret chats in the main/archive chat list. The limit can be increased with Telegram Premium //@chat_list Chat list in which to change the pinned state of the chat @chat_id Chat identifier @is_pinned Pass true to pin the chat; pass false to unpin it toggleChatIsPinned chat_list:ChatList chat_id:int53 is_pinned:Bool = Ok; @@ -5311,10 +5502,10 @@ toggleBotIsAddedToAttachmentMenu bot_user_id:int53 is_added:Bool = Ok; //@offset The starting position from which the file needs to be downloaded //@limit Number of bytes which need to be downloaded starting from the "offset" position before the download will automatically be canceled; use 0 to download without a limit //@synchronous Pass true to return response only after the file download has succeeded, has failed, has been canceled, or a new downloadFile request with different offset/limit parameters was sent; pass false to return file state immediately, just after the download has been started -downloadFile file_id:int32 priority:int32 offset:int32 limit:int32 synchronous:Bool = File; +downloadFile file_id:int32 priority:int32 offset:int53 limit:int53 synchronous:Bool = File; //@description Returns file downloaded prefix size from a given offset, in bytes @file_id Identifier of the file @offset Offset from which downloaded prefix size needs to be calculated -getFileDownloadedPrefixSize file_id:int32 offset:int32 = Count; +getFileDownloadedPrefixSize file_id:int32 offset:int53 = FileDownloadedPrefixSize; //@description Stops the downloading of a file. If a file has already been downloaded, does nothing @file_id Identifier of a file to stop downloading @only_if_pending Pass true to stop downloading only if it hasn't been started, i.e. request hasn't been sent to server cancelDownloadFile file_id:int32 only_if_pending:Bool = Ok; @@ -5333,13 +5524,13 @@ cancelUploadFile file_id:int32 = Ok; //@description Writes a part of a generated file. This method is intended to be used only if the application has no direct access to TDLib's file system, because it is usually slower than a direct write to the destination file //@generation_id The identifier of the generation process @offset The offset from which to write the data to the file @data The data to write -writeGeneratedFilePart generation_id:int64 offset:int32 data:bytes = Ok; +writeGeneratedFilePart generation_id:int64 offset:int53 data:bytes = Ok; //@description Informs TDLib on a file generation progress //@generation_id The identifier of the generation process //@expected_size Expected size of the generated file, in bytes; 0 if unknown //@local_prefix_size The number of bytes already generated -setFileGenerationProgress generation_id:int64 expected_size:int32 local_prefix_size:int32 = Ok; +setFileGenerationProgress generation_id:int64 expected_size:int53 local_prefix_size:int53 = Ok; //@description Finishes the file generation //@generation_id The identifier of the generation process @@ -5350,7 +5541,7 @@ finishFileGeneration generation_id:int64 error:error = Ok; //@file_id Identifier of the file. The file must be located in the TDLib file cache //@offset The offset from which to read the file //@count Number of bytes to read. An error will be returned if there are not enough bytes available in the file from the specified position. Pass 0 to read all available data from the specified position -readFilePart file_id:int32 offset:int32 count:int32 = FilePart; +readFilePart file_id:int32 offset:int53 count:int53 = FilePart; //@description Deletes a file from the TDLib file cache @file_id Identifier of the file to delete deleteFile file_id:int32 = Ok; @@ -5389,7 +5580,7 @@ removeAllFilesFromDownloads only_active:Bool only_completed:Bool delete_from_cac searchFileDownloads query:string only_active:Bool only_completed:Bool offset:string limit:int32 = FoundFileDownloads; -//@description Returns information about a file with messages exported from another app @message_file_head Beginning of the message file; up to 100 first lines +//@description Returns information about a file with messages exported from another application @message_file_head Beginning of the message file; up to 100 first lines getMessageFileType message_file_head:string = MessageFileType; //@description Returns a confirmation text to be shown to the user before starting message import @@ -5461,7 +5652,7 @@ deleteAllRevokedChatInviteLinks chat_id:int53 creator_user_id:int53 = Ok; //@description Checks the validity of an invite link for a chat and returns information about the corresponding chat @invite_link Invite link to be checked checkChatInviteLink invite_link:string = ChatInviteLinkInfo; -//@description Uses an invite link to add the current user to the chat if possible @invite_link Invite link to use +//@description Uses an invite link to add the current user to the chat if possible. May return an error with a message "INVITE_REQUEST_SENT" if only a join request was created @invite_link Invite link to use joinChatByInviteLink invite_link:string = Chat; //@description Returns pending join requests in a chat @@ -5694,7 +5885,7 @@ getArchivedStickerSets is_masks:Bool offset_sticker_set_id:int64 limit:int32 = S //@description Returns a list of trending sticker sets. For optimal performance, the number of returned sticker sets is chosen by TDLib //@offset The offset from which to return the sticker sets; must be non-negative //@limit The maximum number of sticker sets to be returned; up to 100. For optimal performance, the number of returned sticker sets is chosen by TDLib and can be smaller than the specified limit, even if the end of the list has not been reached -getTrendingStickerSets offset:int32 limit:int32 = StickerSets; +getTrendingStickerSets offset:int32 limit:int32 = TrendingStickerSets; //@description Returns a list of sticker sets attached to a file. Currently, only photos and videos can have attached sticker sets @file_id File identifier getAttachedStickerSets file_id:int32 = StickerSets; @@ -5752,6 +5943,9 @@ searchEmojis text:string exact_match:Bool input_language_codes:vector = //@description Returns an animated emoji corresponding to a given emoji. Returns a 404 error if the emoji has no animated emoji @emoji The emoji getAnimatedEmoji emoji:string = AnimatedEmoji; +//@description Returns all emojis, which has a corresponding animated emoji +getAllAnimatedEmojis = Emojis; + //@description Returns an HTTP URL which can be used to automatically log in to the translation platform and suggest new emoji replacements. The URL will be valid for 30 seconds after generation @language_code Language code for which the emoji replacements will be suggested getEmojiSuggestionsUrl language_code:string = HttpUrl; @@ -5794,7 +5988,7 @@ deleteProfilePhoto profile_photo_id:int64 = Ok; //@description Changes the first and last name of the current user @first_name The new value of the first name for the current user; 1-64 characters @last_name The new value of the optional last name for the current user; 0-64 characters setName first_name:string last_name:string = Ok; -//@description Changes the bio of the current user @bio The new value of the user bio; 0-70 characters without line feeds +//@description Changes the bio of the current user @bio The new value of the user bio; 0-GetOption("bio_length_max") characters without line feeds setBio bio:string = Ok; //@description Changes the username of the current user @username The new value of the username. Use an empty string to remove the username @@ -5883,6 +6077,12 @@ setSupergroupStickerSet supergroup_id:int53 sticker_set_id:int64 = Ok; //@description Toggles whether sender signature is added to sent messages in a channel; requires can_change_info administrator right @supergroup_id Identifier of the channel @sign_messages New value of sign_messages toggleSupergroupSignMessages supergroup_id:int53 sign_messages:Bool = Ok; +//@description Toggles whether joining is mandatory to send messages to a discussion supergroup; requires can_restrict_members administrator right @supergroup_id Identifier of the supergroup @join_to_send_messages New value of join_to_send_messages +toggleSupergroupJoinToSendMessages supergroup_id:int53 join_to_send_messages:Bool = Ok; + +//@description Toggles whether all users directly joining the supergroup need to be approved by supergroup administrators; requires can_restrict_members administrator right @supergroup_id Identifier of the channel @join_by_request New value of join_by_request +toggleSupergroupJoinByRequest supergroup_id:int53 join_by_request:Bool = Ok; + //@description Toggles whether the message history of a supergroup is available to new members; requires can_change_info administrator right @supergroup_id The identifier of the supergroup @is_all_history_available The new value of is_all_history_available toggleSupergroupIsAllHistoryAvailable supergroup_id:int53 is_all_history_available:Bool = Ok; @@ -5908,22 +6108,20 @@ getChatEventLog chat_id:int53 query:string from_event_id:int64 limit:int32 filte //@description Returns an invoice payment form. This method must be called when the user presses inlineKeyboardButtonBuy -//@chat_id Chat identifier of the Invoice message -//@message_id Message identifier +//@input_invoice The invoice //@theme Preferred payment form theme; pass null to use the default theme -getPaymentForm chat_id:int53 message_id:int53 theme:themeParameters = PaymentForm; +getPaymentForm input_invoice:InputInvoice theme:themeParameters = PaymentForm; //@description Validates the order information provided by a user and returns the available shipping options for a flexible invoice -//@chat_id Chat identifier of the Invoice message -//@message_id Message identifier +//@input_invoice The invoice //@order_info The order information, provided by the user; pass null if empty //@allow_save Pass true to save the order information -validateOrderInfo chat_id:int53 message_id:int53 order_info:orderInfo allow_save:Bool = ValidatedOrderInfo; +validateOrderInfo input_invoice:InputInvoice order_info:orderInfo allow_save:Bool = ValidatedOrderInfo; -//@description Sends a filled-out payment form to the bot for final verification @chat_id Chat identifier of the Invoice message @message_id Message identifier +//@description Sends a filled-out payment form to the bot for final verification @input_invoice The invoice //@payment_form_id Payment form identifier returned by getPaymentForm @order_info_id Identifier returned by validateOrderInfo, or an empty string @shipping_option_id Identifier of a chosen shipping option, if applicable //@credentials The credentials chosen by user for payment @tip_amount Chosen by the user amount of tip in the smallest units of the currency -sendPaymentForm chat_id:int53 message_id:int53 payment_form_id:int64 order_info_id:string shipping_option_id:string credentials:InputCredentials tip_amount:int53 = PaymentResult; +sendPaymentForm input_invoice:InputInvoice payment_form_id:int64 order_info_id:string shipping_option_id:string credentials:InputCredentials tip_amount:int53 = PaymentResult; //@description Returns information about a successful payment @chat_id Chat identifier of the PaymentSuccessful message @message_id Message identifier getPaymentReceipt chat_id:int53 message_id:int53 = PaymentReceipt; @@ -5938,6 +6136,10 @@ deleteSavedOrderInfo = Ok; deleteSavedCredentials = Ok; +//@description Creates a link for the given invoice; for bots only @invoice Information about the invoice of the type inputMessageInvoice +createInvoiceLink invoice:InputMessageContent = HttpUrl; + + //@description Returns a user that can be contacted to get support getSupportUser = User; @@ -6197,7 +6399,7 @@ addStickerToSet user_id:int53 name:string sticker:inputSticker = StickerSet; setStickerSetThumbnail user_id:int53 name:string thumbnail:InputFile = StickerSet; //@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot -//@sticker Sticker @position New position of the sticker in the set, zero-based +//@sticker Sticker @position New position of the sticker in the set, 0-based setStickerPositionInSet sticker:InputFile position:int32 = Ok; //@description Removes a sticker from the set to which it belongs; for bots only. The sticker set must have been created by the bot @sticker Sticker @@ -6208,6 +6410,25 @@ removeStickerFromSet sticker:InputFile = Ok; getMapThumbnailFile location:location zoom:int32 width:int32 height:int32 scale:int32 chat_id:int53 = File; +//@description Returns information about a limit, increased for Premium users. Returns a 404 error if the limit is unknown @limit_type Type of the limit +getPremiumLimit limit_type:PremiumLimitType = PremiumLimit; + +//@description Returns information about features, available to Premium users @source Source of the request; pass null if the method is called from some non-standard source +getPremiumFeatures source:PremiumSource = PremiumFeatures; + +//@description Returns examples of premium stickers for demonstration purposes +getPremiumStickers = Stickers; + +//@description Informs TDLib that the user viewed detailed information about a Premium feature on the Premium features screen @feature The viewed premium feature +viewPremiumFeature feature:PremiumFeature = Ok; + +//@description Informs TDLib that the user clicked Premium subscription button on the Premium features screen +clickPremiumSubscriptionButton = Ok; + +//@description Returns state of Telegram Premium subscription and promotion videos for Premium features +getPremiumState = PremiumState; + + //@description Accepts Telegram terms of services @terms_of_service_id Terms of service identifier acceptTermsOfService terms_of_service_id:string = Ok; diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index 5d6f3d2b1..61e68c5f9 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -100,7 +100,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -115,7 +115,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?Vector = ChatFull; @@ -140,7 +140,7 @@ messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true 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; @@ -163,8 +163,8 @@ messageActionChannelMigrateFrom#ea3948e9 title:string chat_id:long = MessageActi messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; messageActionGameScore#92a72876 game_id:long score:int = MessageAction; -messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; -messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction; +messageActionPaymentSentMe#8f31b327 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; +messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long invoice_slug:flags.0?string = MessageAction; messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; @@ -380,6 +380,7 @@ updateAttachMenuBots#17b7a20b = Update; updateWebViewResultSent#1592b79d query_id:long = Update; updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; updateSavedRingtones#74d8be99 = Update; +updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -404,9 +405,9 @@ photos.photo#20212ca8 photo:Photo users:Vector = photos.Photo; upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector = upload.File; -dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; +dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; +config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -424,7 +425,7 @@ encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = En inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; encryptedFileEmpty#c21f497e = EncryptedFile; -encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile; +encryptedFile#a8008cd8 id:long access_hash:long size:long dc_id:int key_fingerprint:int = EncryptedFile; inputEncryptedFileEmpty#1837c364 = InputEncryptedFile; inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile; @@ -444,7 +445,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; +document#8fd4c4d8 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:long thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -552,6 +553,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite; +chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; @@ -571,7 +573,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; -botInfo#e4169b5d user_id:long description:string commands:Vector menu_button:BotMenuButton = BotInfo; +botInfo#8f300b57 flags:# user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector menu_button:flags.3?BotMenuButton = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; @@ -730,7 +732,7 @@ draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; -messages.featuredStickers#84c02310 hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#88d37c56 hash:long packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; @@ -813,7 +815,7 @@ dataJSON#7d748d04 data:string = DataJSON; labeledPrice#cb296bf8 label:string amount:long = LabeledPrice; -invoice#cd886e0 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector = Invoice; +invoice#3e85a91b flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true recurring:flags.9?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector recurring_terms_url:flags.9?string = Invoice; paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge; @@ -833,7 +835,7 @@ inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#1694761b flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#b0133b37 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -864,7 +866,7 @@ phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long d phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; -phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; +phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; @@ -962,7 +964,7 @@ dialogPeerFolder#514519e2 folder_id:int = DialogPeer; messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets; messages.foundStickerSets#8af09dd2 hash:long sets:Vector = messages.FoundStickerSets; -fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; +fileHash#f39b035c offset:long limit:int hash:bytes = FileHash; inputClientProxy#75588b3f address:string port:int = InputClientProxy; @@ -973,7 +975,7 @@ inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile; secureFileEmpty#64199744 = SecureFile; -secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile; +secureFile#7d09c27e id:long access_hash:long size:long dc_id:int date:int file_hash:bytes secret:bytes = SecureFile; secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData; @@ -1100,7 +1102,7 @@ codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags. wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; -autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings; +autoDownloadSettings#8efab953 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:long file_size_max:long video_upload_maxbitrate:int = AutoDownloadSettings; account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings; @@ -1172,6 +1174,7 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; +dialogFilterDefault#363293ae = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1217,7 +1220,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; +messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1285,7 +1288,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#3a836df8 flags:# random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; +sponsoredMessage#3a836df8 flags:# recommended:flags.5?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; @@ -1311,7 +1314,7 @@ messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true res messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList; -availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction; +availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction; messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; @@ -1331,7 +1334,7 @@ attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; -attachMenuBot#e93cb772 flags:# inactive:flags.0?true bot_id:long short_name:string icons:Vector = AttachMenuBot; +attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; @@ -1359,6 +1362,21 @@ notificationSoundRingtone#ff6c8049 id:long = NotificationSound; account.savedRingtone#b7263f6d = account.SavedRingtone; account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone; +attachMenuPeerTypeSameBotPM#7d6be90e = AttachMenuPeerType; +attachMenuPeerTypeBotPM#c32bfa1a = AttachMenuPeerType; +attachMenuPeerTypePM#f146d31f = AttachMenuPeerType; +attachMenuPeerTypeChat#509113f = AttachMenuPeerType; +attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; + +inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; +inputInvoiceSlug#c326caef slug:string = InputInvoice; + +payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; + +messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id:long text:string = messages.TranscribedAudio; + +help.premiumPromo#8a4f3c29 status_text:string status_entities:Vector video_sections:Vector videos:Vector currency:string monthly_amount:long users:Vector = help.PremiumPromo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1429,7 +1447,7 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; account.verifyEmail#ecba39db email:string code:string = Bool; -account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout; +account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout; account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool; account.confirmPasswordEmail#8fdf1920 code:string = Bool; account.resendPasswordEmail#7a7f2a15 = Bool; @@ -1541,7 +1559,7 @@ messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = B messages.migrateChat#a2875319 chat_id:long = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; -messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; +messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Document; messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; @@ -1654,11 +1672,13 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool; -messages.requestWebView#fa04dff flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int = WebViewResult; -messages.prolongWebView#d22ad148 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int = Bool; +messages.requestWebView#91b15831 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult; +messages.prolongWebView#ea5fbcce flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = Bool; messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; +messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; +messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_id:long good:Bool = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1670,13 +1690,13 @@ photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; -upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File; +upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File; upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool; upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile; -upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile; +upload.getCdnFile#395f69da file_token:bytes offset:long limit:int = upload.CdnFile; upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector; -upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector; -upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector; +upload.getCdnFileHashes#91dc3f31 file_token:bytes offset:long = Vector; +upload.getFileHashes#9156982a location:InputFileLocation offset:long = Vector; help.getConfig#c4f9186b = Config; help.getNearestDc#1fb33026 = NearestDc; @@ -1700,6 +1720,7 @@ help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; help.dismissSuggestion#f50dbaa1 peer:InputPeer suggestion:string = Bool; help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; +help.getPremiumPromo#b81b93d4 = help.PremiumPromo; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1740,6 +1761,8 @@ channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bo channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages; channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers; channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory; +channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates; +channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1751,13 +1774,18 @@ bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton; bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool; bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool; -payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm; +payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; -payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; -payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; +payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; +payments.sendPaymentForm#2d03522f flags:# form_id:long invoice:InputInvoice requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; +payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; +payments.assignAppStoreTransaction#d5ccfd0 flags:# restore:flags.0?true receipt:bytes = Updates; +payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates; +payments.canPurchasePremium#aa6a90c8 = Bool; +payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/td/telegram/AnimationsManager.cpp b/td/telegram/AnimationsManager.cpp index dc52706da..b14ba5e76 100644 --- a/td/telegram/AnimationsManager.cpp +++ b/td/telegram/AnimationsManager.cpp @@ -389,7 +389,8 @@ tl_object_ptr AnimationsManager::get_input_media( SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const { + const string &caption, BufferSlice thumbnail, + int32 layer) const { auto *animation = get_animation(animation_file_id); CHECK(animation != nullptr); auto file_view = td_->file_manager_->get_file_view(animation_file_id); @@ -425,7 +426,8 @@ SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file animation->mime_type, file_view, std::move(attributes), - caption}; + caption, + layer}; } void AnimationsManager::on_update_animation_search_emojis(string animation_search_emojis) { diff --git a/td/telegram/AnimationsManager.h b/td/telegram/AnimationsManager.h index f0d4b3832..54383b94e 100644 --- a/td/telegram/AnimationsManager.h +++ b/td/telegram/AnimationsManager.h @@ -46,7 +46,7 @@ class AnimationsManager final : public Actor { SecretInputMedia get_secret_input_media(FileId animation_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const; + const string &caption, BufferSlice thumbnail, int32 layer) const; FileId get_animation_thumbnail_file_id(FileId file_id) const; diff --git a/td/telegram/Application.cpp b/td/telegram/Application.cpp index 7c33fa646..8f4e559d4 100644 --- a/td/telegram/Application.cpp +++ b/td/telegram/Application.cpp @@ -11,6 +11,7 @@ #include "td/utils/buffer.h" #include "td/utils/logging.h" +#include "td/utils/Status.h" namespace td { diff --git a/td/telegram/AttachMenuManager.cpp b/td/telegram/AttachMenuManager.cpp index 84cc6bfc4..f4d8cf2ea 100644 --- a/td/telegram/AttachMenuManager.cpp +++ b/td/telegram/AttachMenuManager.cpp @@ -39,6 +39,7 @@ class RequestWebViewQuery final : public Td::ResultHandler { DialogId dialog_id_; UserId bot_user_id_; MessageId reply_to_message_id_; + DialogId as_dialog_id_; bool from_attach_menu_ = false; public: @@ -47,10 +48,12 @@ class RequestWebViewQuery final : public Td::ResultHandler { } void send(DialogId dialog_id, UserId bot_user_id, tl_object_ptr &&input_user, string &&url, - td_api::object_ptr &&theme, MessageId reply_to_message_id, bool silent) { + td_api::object_ptr &&theme, MessageId reply_to_message_id, bool silent, + DialogId as_dialog_id) { dialog_id_ = dialog_id; bot_user_id_ = bot_user_id; reply_to_message_id_ = reply_to_message_id; + as_dialog_id_ = as_dialog_id; int32 flags = 0; @@ -90,9 +93,17 @@ class RequestWebViewQuery final : public Td::ResultHandler { flags |= telegram_api::messages_requestWebView::SILENT_MASK; } + tl_object_ptr as_input_peer; + if (as_dialog_id.is_valid()) { + as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Write); + if (as_input_peer != nullptr) { + flags |= telegram_api::messages_requestWebView::SEND_AS_MASK; + } + } + send_query(G()->net_query_creator().create(telegram_api::messages_requestWebView( flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(input_user), url, start_parameter, - std::move(theme_parameters), reply_to_message_id.get_server_message_id().get()))); + std::move(theme_parameters), reply_to_message_id.get_server_message_id().get(), std::move(as_input_peer)))); } void on_result(BufferSlice packet) final { @@ -102,7 +113,8 @@ class RequestWebViewQuery final : public Td::ResultHandler { } auto ptr = result_ptr.move_as_ok(); - td_->attach_menu_manager_->open_web_view(ptr->query_id_, dialog_id_, bot_user_id_, reply_to_message_id_); + td_->attach_menu_manager_->open_web_view(ptr->query_id_, dialog_id_, bot_user_id_, reply_to_message_id_, + as_dialog_id_); promise_.set_value(td_api::make_object(ptr->query_id_, ptr->url_)); } @@ -120,7 +132,8 @@ class ProlongWebViewQuery final : public Td::ResultHandler { DialogId dialog_id_; public: - void send(DialogId dialog_id, UserId bot_user_id, int64 query_id, MessageId reply_to_message_id, bool silent) { + void send(DialogId dialog_id, UserId bot_user_id, int64 query_id, MessageId reply_to_message_id, bool silent, + DialogId as_dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); @@ -133,13 +146,22 @@ class ProlongWebViewQuery final : public Td::ResultHandler { if (reply_to_message_id.is_valid()) { flags |= telegram_api::messages_prolongWebView::REPLY_TO_MSG_ID_MASK; } + if (silent) { flags |= telegram_api::messages_prolongWebView::SILENT_MASK; } + tl_object_ptr as_input_peer; + if (as_dialog_id.is_valid()) { + as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Write); + if (as_input_peer != nullptr) { + flags |= telegram_api::messages_prolongWebView::SEND_AS_MASK; + } + } + send_query(G()->net_query_creator().create(telegram_api::messages_prolongWebView( flags, false /*ignored*/, std::move(input_peer), r_input_user.move_as_ok(), query_id, - reply_to_message_id.get_server_message_id().get()))); + reply_to_message_id.get_server_message_id().get(), std::move(as_input_peer)))); } void on_result(BufferSlice packet) final { @@ -268,12 +290,18 @@ void AttachMenuManager::AttachMenuBotColor::parse(ParserT &parser) { } bool operator==(const AttachMenuManager::AttachMenuBot &lhs, const AttachMenuManager::AttachMenuBot &rhs) { - return lhs.user_id_ == rhs.user_id_ && lhs.name_ == rhs.name_ && + return lhs.user_id_ == rhs.user_id_ && lhs.supports_self_dialog_ == rhs.supports_self_dialog_ && + lhs.supports_user_dialogs_ == rhs.supports_user_dialogs_ && + lhs.supports_bot_dialogs_ == rhs.supports_bot_dialogs_ && + lhs.supports_group_dialogs_ == rhs.supports_group_dialogs_ && + lhs.supports_broadcast_dialogs_ == rhs.supports_broadcast_dialogs_ && + lhs.supports_settings_ == rhs.supports_settings_ && lhs.name_ == rhs.name_ && lhs.default_icon_file_id_ == rhs.default_icon_file_id_ && lhs.ios_static_icon_file_id_ == rhs.ios_static_icon_file_id_ && lhs.ios_animated_icon_file_id_ == rhs.ios_animated_icon_file_id_ && lhs.android_icon_file_id_ == rhs.android_icon_file_id_ && lhs.macos_icon_file_id_ == rhs.macos_icon_file_id_ && - lhs.is_added_ == rhs.is_added_ && lhs.name_color_ == rhs.name_color_ && lhs.icon_color_ == rhs.icon_color_; + lhs.is_added_ == rhs.is_added_ && lhs.name_color_ == rhs.name_color_ && lhs.icon_color_ == rhs.icon_color_ && + lhs.placeholder_file_id_ == rhs.placeholder_file_id_; } bool operator!=(const AttachMenuManager::AttachMenuBot &lhs, const AttachMenuManager::AttachMenuBot &rhs) { @@ -288,6 +316,9 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { bool has_macos_icon_file_id = macos_icon_file_id_.is_valid(); bool has_name_color = name_color_ != AttachMenuBotColor(); bool has_icon_color = icon_color_ != AttachMenuBotColor(); + bool has_support_flags = true; + bool has_placeholder_file_id = placeholder_file_id_.is_valid(); + bool has_cache_version = cache_version_ != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(has_ios_static_icon_file_id); STORE_FLAG(has_ios_animated_icon_file_id); @@ -296,6 +327,15 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { STORE_FLAG(is_added_); STORE_FLAG(has_name_color); STORE_FLAG(has_icon_color); + STORE_FLAG(has_support_flags); + STORE_FLAG(supports_self_dialog_); + STORE_FLAG(supports_user_dialogs_); + STORE_FLAG(supports_bot_dialogs_); + STORE_FLAG(supports_group_dialogs_); + STORE_FLAG(supports_broadcast_dialogs_); + STORE_FLAG(supports_settings_); + STORE_FLAG(has_placeholder_file_id); + STORE_FLAG(has_cache_version); END_STORE_FLAGS(); td::store(user_id_, storer); td::store(name_, storer); @@ -318,6 +358,12 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { if (has_icon_color) { td::store(icon_color_, storer); } + if (has_placeholder_file_id) { + td::store(placeholder_file_id_, storer); + } + if (has_cache_version) { + td::store(cache_version_, storer); + } } template @@ -328,6 +374,9 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { bool has_macos_icon_file_id; bool has_name_color; bool has_icon_color; + bool has_support_flags; + bool has_placeholder_file_id; + bool has_cache_version; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_ios_static_icon_file_id); PARSE_FLAG(has_ios_animated_icon_file_id); @@ -336,6 +385,15 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { PARSE_FLAG(is_added_); PARSE_FLAG(has_name_color); PARSE_FLAG(has_icon_color); + PARSE_FLAG(has_support_flags); + PARSE_FLAG(supports_self_dialog_); + PARSE_FLAG(supports_user_dialogs_); + PARSE_FLAG(supports_bot_dialogs_); + PARSE_FLAG(supports_group_dialogs_); + PARSE_FLAG(supports_broadcast_dialogs_); + PARSE_FLAG(supports_settings_); + PARSE_FLAG(has_placeholder_file_id); + PARSE_FLAG(has_cache_version); END_PARSE_FLAGS(); td::parse(user_id_, parser); td::parse(name_, parser); @@ -358,6 +416,18 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { if (has_icon_color) { td::parse(icon_color_, parser); } + if (has_placeholder_file_id) { + td::parse(placeholder_file_id_, parser); + } + if (has_cache_version) { + td::parse(cache_version_, parser); + } + + if (!has_support_flags) { + supports_self_dialog_ = true; + supports_user_dialogs_ = true; + supports_bot_dialogs_ = true; + } } class AttachMenuManager::AttachMenuBotsLogEvent { @@ -425,7 +495,13 @@ void AttachMenuManager::init() { dependencies.add(attach_menu_bot.user_id_); } if (is_valid && dependencies.resolve_force(td_, "AttachMenuBotsLogEvent")) { - hash_ = attach_menu_bots_log_event.hash_; + bool is_cache_outdated = false; + for (auto &bot : attach_menu_bots_log_event.attach_menu_bots_) { + if (bot.cache_version_ != AttachMenuBot::CACHE_VERSION) { + is_cache_outdated = true; + } + } + hash_ = is_cache_outdated ? 0 : attach_menu_bots_log_event.hash_; attach_menu_bots_ = std::move(attach_menu_bots_log_event.attach_menu_bots_); } else { LOG(ERROR) << "Ignore invalid attachment menu bots log event"; @@ -493,7 +569,8 @@ void AttachMenuManager::ping_web_view() { const auto &opened_web_view = it.second; bool silent = td_->messages_manager_->get_dialog_silent_send_message(opened_web_view.dialog_id_); td_->create_handler()->send(opened_web_view.dialog_id_, opened_web_view.bot_user_id_, it.first, - opened_web_view.reply_to_message_id_, silent); + opened_web_view.reply_to_message_id_, silent, + opened_web_view.as_dialog_id_); } schedule_ping_web_view(); @@ -517,12 +594,12 @@ void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, switch (dialog_id.get_type()) { case DialogType::User: - // ok - break; case DialogType::Chat: case DialogType::Channel: + // ok + break; case DialogType::SecretChat: - return promise.set_error(Status::Error(400, "Web apps can be opened only in private chats")); + return promise.set_error(Status::Error(400, "Web Apps can't be opened in secret chats")); case DialogType::None: default: UNREACHABLE(); @@ -538,16 +615,17 @@ void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, } bool silent = td_->messages_manager_->get_dialog_silent_send_message(dialog_id); + DialogId as_dialog_id = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id); td_->create_handler(std::move(promise)) ->send(dialog_id, bot_user_id, std::move(input_user), std::move(url), std::move(theme), reply_to_message_id, - silent); + silent, as_dialog_id); } void AttachMenuManager::open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, - MessageId reply_to_message_id) { + MessageId reply_to_message_id, DialogId as_dialog_id) { if (query_id == 0) { - LOG(ERROR) << "Receive web app query identifier == 0"; + LOG(ERROR) << "Receive Web App query identifier == 0"; return; } @@ -558,6 +636,7 @@ void AttachMenuManager::open_web_view(int64 query_id, DialogId dialog_id, UserId opened_web_view.dialog_id_ = dialog_id; opened_web_view.bot_user_id_ = bot_user_id; opened_web_view.reply_to_message_id_ = reply_to_message_id; + opened_web_view.as_dialog_id_ = as_dialog_id; opened_web_views_.emplace(query_id, std::move(opened_web_view)); } @@ -589,7 +668,7 @@ Result AttachMenuManager::get_attach_menu_bot( CHECK(document_id == telegram_api::document::ID); if (name != "default_static" && name != "ios_static" && name != "ios_animated" && name != "android_animated" && - name != "macos_animated") { + name != "macos_animated" && name != "placeholder_static") { LOG(ERROR) << "Have icon for " << user_id << " with name " << name; continue; } @@ -598,9 +677,7 @@ Result AttachMenuManager::get_attach_menu_bot( auto parsed_document = td_->documents_manager_->on_get_document(move_tl_object_as(icon->icon_), DialogId()); if (parsed_document.type != expected_document_type) { - if (user_id != UserId(static_cast(5000860301)) || !G()->is_test_dc() || name != "macos_animated") { - LOG(ERROR) << "Receive wrong attachment menu bot icon \"" << name << "\" for " << user_id; - } + LOG(ERROR) << "Receive wrong attachment menu bot icon \"" << name << "\" for " << user_id; continue; } bool expect_colors = false; @@ -621,6 +698,9 @@ Result AttachMenuManager::get_attach_menu_bot( case '_': attach_menu_bot.macos_icon_file_id_ = parsed_document.file_id; break; + case 'h': + attach_menu_bot.placeholder_file_id_ = parsed_document.file_id; + break; default: UNREACHABLE(); } @@ -671,10 +751,33 @@ Result AttachMenuManager::get_attach_menu_bot( } } } - + for (auto &peer_type : bot->peer_types_) { + switch (peer_type->get_id()) { + case telegram_api::attachMenuPeerTypeSameBotPM::ID: + attach_menu_bot.supports_self_dialog_ = true; + break; + case telegram_api::attachMenuPeerTypeBotPM::ID: + attach_menu_bot.supports_bot_dialogs_ = true; + break; + case telegram_api::attachMenuPeerTypePM::ID: + attach_menu_bot.supports_user_dialogs_ = true; + break; + case telegram_api::attachMenuPeerTypeChat::ID: + attach_menu_bot.supports_group_dialogs_ = true; + break; + case telegram_api::attachMenuPeerTypeBroadcast::ID: + attach_menu_bot.supports_broadcast_dialogs_ = true; + break; + default: + UNREACHABLE(); + break; + } + } + attach_menu_bot.supports_settings_ = bot->has_settings_; if (!attach_menu_bot.default_icon_file_id_.is_valid()) { return Status::Error(PSLICE() << "Have no default icon for " << user_id); } + attach_menu_bot.cache_version_ = AttachMenuBot::CACHE_VERSION; return std::move(attach_menu_bot); } @@ -872,11 +975,13 @@ td_api::object_ptr AttachMenuManager::get_attachment_ }; return td_api::make_object( - td_->contacts_manager_->get_user_id_object(bot.user_id_, "get_attachment_menu_bot_object"), bot.name_, + td_->contacts_manager_->get_user_id_object(bot.user_id_, "get_attachment_menu_bot_object"), + bot.supports_self_dialog_, bot.supports_user_dialogs_, bot.supports_bot_dialogs_, bot.supports_group_dialogs_, + bot.supports_broadcast_dialogs_, bot.supports_settings_, bot.name_, get_attach_menu_bot_color_object(bot.name_color_), get_file(bot.default_icon_file_id_), get_file(bot.ios_static_icon_file_id_), get_file(bot.ios_animated_icon_file_id_), get_file(bot.android_icon_file_id_), get_file(bot.macos_icon_file_id_), - get_attach_menu_bot_color_object(bot.icon_color_)); + get_attach_menu_bot_color_object(bot.icon_color_), get_file(bot.placeholder_file_id_)); } td_api::object_ptr AttachMenuManager::get_update_attachment_menu_bots_object() const { diff --git a/td/telegram/AttachMenuManager.h b/td/telegram/AttachMenuManager.h index 80b39085b..8bc10e2fd 100644 --- a/td/telegram/AttachMenuManager.h +++ b/td/telegram/AttachMenuManager.h @@ -35,7 +35,8 @@ class AttachMenuManager final : public Actor { td_api::object_ptr &&theme, Promise> &&promise); - void open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, MessageId reply_to_message_id); + void open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, MessageId reply_to_message_id, + DialogId as_dialog_id); void close_web_view(int64 query_id, Promise &&promise); @@ -74,6 +75,12 @@ class AttachMenuManager final : public Actor { struct AttachMenuBot { bool is_added_ = false; UserId user_id_; + bool supports_self_dialog_ = false; + bool supports_user_dialogs_ = false; + bool supports_bot_dialogs_ = false; + bool supports_group_dialogs_ = false; + bool supports_broadcast_dialogs_ = false; + bool supports_settings_ = false; string name_; AttachMenuBotColor name_color_; FileId default_icon_file_id_; @@ -82,6 +89,10 @@ class AttachMenuManager final : public Actor { FileId android_icon_file_id_; FileId macos_icon_file_id_; AttachMenuBotColor icon_color_; + FileId placeholder_file_id_; + + static constexpr uint32 CACHE_VERSION = 1; + uint32 cache_version_ = 0; template void store(StorerT &storer) const; @@ -138,6 +149,7 @@ class AttachMenuManager final : public Actor { DialogId dialog_id_; UserId bot_user_id_; MessageId reply_to_message_id_; + DialogId as_dialog_id_; }; FlatHashMap opened_web_views_; Timeout ping_web_view_timeout_; diff --git a/td/telegram/AudiosManager.cpp b/td/telegram/AudiosManager.cpp index bae9e6b99..73b6d8a56 100644 --- a/td/telegram/AudiosManager.cpp +++ b/td/telegram/AudiosManager.cpp @@ -8,6 +8,7 @@ #include "td/telegram/AuthManager.h" #include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" #include "td/telegram/PhotoFormat.h" #include "td/telegram/secret_api.h" #include "td/telegram/Td.h" @@ -206,7 +207,8 @@ void AudiosManager::create_audio(FileId file_id, string minithumbnail, PhotoSize SecretInputMedia AudiosManager::get_secret_input_media(FileId audio_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const { + const string &caption, BufferSlice thumbnail, + int32 layer) const { auto *audio = get_audio(audio_file_id); CHECK(audio != nullptr); auto file_view = td_->file_manager_->get_file_view(audio_file_id); @@ -236,7 +238,8 @@ SecretInputMedia AudiosManager::get_secret_input_media(FileId audio_file_id, audio->mime_type, file_view, std::move(attributes), - caption}; + caption, + layer}; } tl_object_ptr AudiosManager::get_input_media( diff --git a/td/telegram/AudiosManager.h b/td/telegram/AudiosManager.h index 305c88ad2..b0232f68d 100644 --- a/td/telegram/AudiosManager.h +++ b/td/telegram/AudiosManager.h @@ -41,7 +41,7 @@ class AudiosManager { SecretInputMedia get_secret_input_media(FileId audio_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const; + const string &caption, BufferSlice thumbnail, int32 layer) const; FileId get_audio_thumbnail_file_id(FileId file_id) const; diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index 24702f2f6..fb34fb4dd 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -685,7 +685,7 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) { } else { status = std::move(result->error()); } - LOG_IF(ERROR, status.is_error()) << "Receive error for auth.logOut: " << status; + LOG_IF(ERROR, status.is_error() && status.error().code() != 401) << "Receive error for auth.logOut: " << status; // state_ will stay LoggingOut, so no queries will work. destroy_auth_keys(); if (query_id_ != 0) { @@ -693,6 +693,10 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) { } } void AuthManager::on_authorization_lost(string source) { + if (state_ == State::LoggingOut && net_query_type_ == NetQueryType::LogOut) { + LOG(INFO) << "Ignore authorization loss because of " << source << ", while logging out"; + return; + } LOG(WARNING) << "Lost authorization because of " << source; destroy_auth_keys(); } @@ -803,7 +807,7 @@ void AuthManager::on_get_authorization(tl_object_ptrset_is_bot_online(true); } - send_closure(G()->config_manager(), &ConfigManager::request_config); + send_closure(G()->config_manager(), &ConfigManager::request_config, false); if (query_id_ != 0) { on_query_ok(); } diff --git a/td/telegram/AutoDownloadSettings.cpp b/td/telegram/AutoDownloadSettings.cpp index 70ceffa07..d2ff4c5ad 100644 --- a/td/telegram/AutoDownloadSettings.cpp +++ b/td/telegram/AutoDownloadSettings.cpp @@ -13,6 +13,7 @@ #include "td/utils/buffer.h" #include "td/utils/logging.h" +#include "td/utils/misc.h" #include "td/utils/Status.h" namespace td { @@ -25,9 +26,13 @@ static td_api::object_ptr convert_auto_download_se auto video_preload_large = (flags & telegram_api::autoDownloadSettings::VIDEO_PRELOAD_LARGE_MASK) != 0; auto audio_preload_next = (flags & telegram_api::autoDownloadSettings::AUDIO_PRELOAD_NEXT_MASK) != 0; auto phonecalls_less_data = (flags & telegram_api::autoDownloadSettings::PHONECALLS_LESS_DATA_MASK) != 0; + constexpr int32 MAX_PHOTO_SIZE = 10 * (1 << 20) /* 10 MB */; + constexpr int64 MAX_DOCUMENT_SIZE = (static_cast(1) << 52); return td_api::make_object( - !disabled, settings->photo_size_max_, settings->video_size_max_, settings->file_size_max_, - settings->video_upload_maxbitrate_, video_preload_large, audio_preload_next, phonecalls_less_data); + !disabled, clamp(settings->photo_size_max_, static_cast(0), MAX_PHOTO_SIZE), + clamp(settings->video_size_max_, static_cast(0), MAX_DOCUMENT_SIZE), + clamp(settings->file_size_max_, static_cast(0), MAX_DOCUMENT_SIZE), settings->video_upload_maxbitrate_, + video_preload_large, audio_preload_next, phonecalls_less_data); } class GetAutoDownloadSettingsQuery final : public Td::ResultHandler { diff --git a/td/telegram/AutoDownloadSettings.h b/td/telegram/AutoDownloadSettings.h index 5cc92c6b5..9efce1ffe 100644 --- a/td/telegram/AutoDownloadSettings.h +++ b/td/telegram/AutoDownloadSettings.h @@ -20,8 +20,8 @@ class Td; class AutoDownloadSettings { public: int32 max_photo_file_size = 0; - int32 max_video_file_size = 0; - int32 max_other_file_size = 0; + int64 max_video_file_size = 0; + int64 max_other_file_size = 0; int32 video_upload_bitrate = 0; bool is_enabled = false; bool preload_large_videos = false; diff --git a/td/telegram/AvailableReaction.cpp b/td/telegram/AvailableReaction.cpp new file mode 100644 index 000000000..dfd794437 --- /dev/null +++ b/td/telegram/AvailableReaction.cpp @@ -0,0 +1,61 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/AvailableReaction.h" + +#include "td/utils/algorithm.h" + +namespace td { + +AvailableReactionType get_reaction_type(const vector &available_reactions, const string &reaction) { + for (auto &available_reaction : available_reactions) { + if (available_reaction.reaction_ == reaction) { + if (available_reaction.is_premium_) { + return AvailableReactionType::NeedsPremium; + } + return AvailableReactionType::Available; + } + } + return AvailableReactionType::Unavailable; +} + +vector get_active_reactions(const vector &available_reactions, + const vector &active_reactions) { + if (available_reactions.empty()) { + // fast path + return available_reactions; + } + if (available_reactions.size() == active_reactions.size()) { + size_t i; + for (i = 0; i < available_reactions.size(); i++) { + if (available_reactions[i] != active_reactions[i].reaction_) { + break; + } + } + if (i == available_reactions.size()) { + // fast path + return available_reactions; + } + } + + vector result; + for (const auto &active_reaction : active_reactions) { + if (td::contains(available_reactions, active_reaction.reaction_)) { + result.push_back(active_reaction.reaction_); + } + } + return result; +} + +td_api::object_ptr AvailableReaction::get_available_reaction_object() const { + return td_api::make_object(reaction_, is_premium_); +} + +bool operator==(const AvailableReaction &lhs, const AvailableReaction &rhs) { + return lhs.reaction_ == rhs.reaction_ && lhs.is_premium_ == rhs.is_premium_; +} + +} // namespace td diff --git a/td/telegram/AvailableReaction.h b/td/telegram/AvailableReaction.h new file mode 100644 index 000000000..e3196382d --- /dev/null +++ b/td/telegram/AvailableReaction.h @@ -0,0 +1,38 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" + +#include "td/utils/common.h" + +namespace td { + +struct AvailableReaction { + string reaction_; + bool is_premium_; + + AvailableReaction(const string &reaction, bool is_premium) : reaction_(reaction), is_premium_(is_premium) { + } + + td_api::object_ptr get_available_reaction_object() const; +}; + +bool operator==(const AvailableReaction &lhs, const AvailableReaction &rhs); + +inline bool operator!=(const AvailableReaction &lhs, const AvailableReaction &rhs) { + return !(lhs == rhs); +} + +enum class AvailableReactionType : int32 { Unavailable, Available, NeedsPremium }; + +AvailableReactionType get_reaction_type(const vector &reactions, const string &reaction); + +vector get_active_reactions(const vector &available_reactions, + const vector &active_reactions); + +} // namespace td diff --git a/td/telegram/BackgroundManager.cpp b/td/telegram/BackgroundManager.cpp index 8244ae5bf..cd881176f 100644 --- a/td/telegram/BackgroundManager.cpp +++ b/td/telegram/BackgroundManager.cpp @@ -35,6 +35,8 @@ #include "td/utils/SliceBuilder.h" #include "td/utils/tl_helpers.h" +#include + namespace td { class GetBackgroundQuery final : public Td::ResultHandler { diff --git a/td/telegram/BotMenuButton.cpp b/td/telegram/BotMenuButton.cpp index 4676f856d..32c18c418 100644 --- a/td/telegram/BotMenuButton.cpp +++ b/td/telegram/BotMenuButton.cpp @@ -84,7 +84,9 @@ class GetBotMenuButtonQuery final : public Td::ResultHandler { }; unique_ptr get_bot_menu_button(telegram_api::object_ptr &&bot_menu_button) { - CHECK(bot_menu_button != nullptr); + if (bot_menu_button == nullptr) { + return nullptr; + } switch (bot_menu_button->get_id()) { case telegram_api::botMenuButtonCommands::ID: return nullptr; diff --git a/td/telegram/CallActor.cpp b/td/telegram/CallActor.cpp index 7e23512dc..45c7ade10 100644 --- a/td/telegram/CallActor.cpp +++ b/td/telegram/CallActor.cpp @@ -11,6 +11,7 @@ #include "td/telegram/DhCache.h" #include "td/telegram/DialogId.h" #include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryCreator.h" @@ -74,6 +75,7 @@ CallConnection::CallConnection(const telegram_api::PhoneConnection &connection) ipv6 = conn.ipv6_; port = conn.port_; peer_tag = conn.peer_tag_.as_slice().str(); + is_tcp = conn.tcp_; break; } case telegram_api::phoneConnectionWebrtc::ID: { @@ -103,7 +105,7 @@ tl_object_ptr CallConnection::get_call_server_object() const auto server_type = [&]() -> tl_object_ptr { switch (type) { case Type::Telegram: - return make_tl_object(peer_tag); + return make_tl_object(peer_tag, is_tcp); case Type::Webrtc: return make_tl_object(username, password, supports_turn, supports_stun); default: diff --git a/td/telegram/CallActor.h b/td/telegram/CallActor.h index 0c936099b..97186f35f 100644 --- a/td/telegram/CallActor.h +++ b/td/telegram/CallActor.h @@ -20,6 +20,7 @@ #include "td/actor/actor.h" #include "td/actor/PromiseFuture.h" +#include "td/utils/common.h" #include "td/utils/Container.h" #include "td/utils/Status.h" @@ -55,6 +56,7 @@ struct CallConnection { // Telegram string peer_tag; + bool is_tcp = false; // WebRTC string username; diff --git a/td/telegram/CallManager.h b/td/telegram/CallManager.h index 2430fdc65..54a16f32b 100644 --- a/td/telegram/CallManager.h +++ b/td/telegram/CallManager.h @@ -13,6 +13,8 @@ #include "td/actor/actor.h" #include "td/actor/PromiseFuture.h" + +#include "td/utils/common.h" #include "td/utils/FlatHashMap.h" #include "td/utils/Status.h" diff --git a/td/telegram/Client.cpp b/td/telegram/Client.cpp index a102efbfd..b6d9fea60 100644 --- a/td/telegram/Client.cpp +++ b/td/telegram/Client.cpp @@ -24,6 +24,7 @@ #include "td/utils/port/RwMutex.h" #include "td/utils/port/thread.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" #include "td/utils/utf8.h" diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index a8ba77049..57c8faf16 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -22,6 +22,7 @@ #include "td/telegram/net/NetType.h" #include "td/telegram/net/PublicRsaKeyShared.h" #include "td/telegram/net/Session.h" +#include "td/telegram/Premium.h" #include "td/telegram/StateManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" @@ -908,7 +909,7 @@ void ConfigManager::start_up() { auto expire_time = load_config_expire_time(); if (expire_time.is_in_past() || true) { - request_config(); + request_config(false); } else { expire_time_ = expire_time; set_timeout_in(expire_time_.in()); @@ -934,7 +935,7 @@ void ConfigManager::hangup() { void ConfigManager::loop() { if (expire_time_ && expire_time_.is_in_past()) { - request_config(); + request_config(reopen_sessions_after_get_config_); expire_time_ = {}; } } @@ -945,17 +946,17 @@ void ConfigManager::try_stop() { } } -void ConfigManager::request_config() { +void ConfigManager::request_config(bool reopen_sessions) { if (G()->close_flag()) { return; } - if (config_sent_cnt_ != 0) { + if (config_sent_cnt_ != 0 && !reopen_sessions) { return; } lazy_request_flood_control_.add_event(static_cast(Timestamp::now().at())); - request_config_from_dc_impl(DcId::main()); + request_config_from_dc_impl(DcId::main(), reopen_sessions); } void ConfigManager::lazy_request_config() { @@ -1085,11 +1086,13 @@ void ConfigManager::on_dc_options_update(DcOptions dc_options) { send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, std::move(dc_options)); } -void ConfigManager::request_config_from_dc_impl(DcId dc_id) { +void ConfigManager::request_config_from_dc_impl(DcId dc_id, bool reopen_sessions) { config_sent_cnt_++; + reopen_sessions_after_get_config_ |= reopen_sessions; auto query = G()->net_query_creator().create_unauth(telegram_api::help_getConfig(), dc_id); query->total_timeout_limit_ = 60 * 60 * 24; - G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, 8)); + G()->net_query_dispatcher().dispatch_with_callback(std::move(query), + actor_shared(this, 8 + static_cast(reopen_sessions))); } void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions) { @@ -1256,7 +1259,7 @@ void ConfigManager::on_result(NetQueryPtr res) { return; } - CHECK(token == 8); + CHECK(token == 8 || token == 9); CHECK(config_sent_cnt_ > 0); config_sent_cnt_--; auto r_config = fetch_result(std::move(res)); @@ -1269,6 +1272,9 @@ void ConfigManager::on_result(NetQueryPtr res) { } else { on_dc_options_update(DcOptions()); process_config(r_config.move_as_ok()); + if (token == 9) { + G()->net_query_dispatcher().update_mtproto_header(); + } } } @@ -1472,12 +1478,20 @@ void ConfigManager::process_app_config(tl_object_ptr &c double animated_emoji_zoom = 0.0; string default_reaction; int64 reactions_uniq_max = 0; + vector premium_features; + auto &premium_limit_keys = get_premium_limit_keys(); + string premium_bot_username; + string premium_invoice_slug; + bool is_premium_available = false; + int32 stickers_premium_by_emoji_num = 0; + int32 stickers_normal_by_emoji_per_premium_num = 2; if (config->get_id() == telegram_api::jsonObject::ID) { for (auto &key_value : static_cast(config.get())->value_) { Slice key = key_value->key_; telegram_api::JSONValue *value = key_value->value_.get(); - if (key == "test" || key == "wallet_enabled" || key == "wallet_blockchain_name" || key == "wallet_config" || - key == "stickers_emoji_cache_time") { + if (key == "message_animated_emoji_max" || key == "stickers_emoji_cache_time" || key == "test" || + key == "upload_max_fileparts_default" || key == "upload_max_fileparts_premium" || + key == "wallet_blockchain_name" || key == "wallet_config" || key == "wallet_enabled") { continue; } if (key == "ignore_restriction_reasons") { @@ -1733,6 +1747,59 @@ void ConfigManager::process_app_config(tl_object_ptr &c G()->shared_config().set_option_integer("notification_sound_count_max", setting_value); continue; } + if (key == "premium_promo_order") { + if (value->get_id() == telegram_api::jsonArray::ID) { + auto features = std::move(static_cast(value)->value_); + for (auto &feature : features) { + auto premium_feature = get_json_value_string(std::move(feature), key); + if (!td::contains(premium_feature, ',')) { + premium_features.push_back(std::move(premium_feature)); + } + } + } else { + LOG(ERROR) << "Receive unexpected premium_promo_order " << to_string(*value); + } + continue; + } + bool is_premium_limit_key = false; + for (auto premium_limit_key : premium_limit_keys) { + if (begins_with(key, premium_limit_key)) { + auto suffix = key.substr(premium_limit_key.size()); + if (suffix == "_limit_default" || suffix == "_limit_premium") { + auto setting_value = get_json_value_int(std::move(key_value->value_), key); + if (setting_value > 0) { + G()->shared_config().set_option_integer(key, setting_value); + } else { + LOG(ERROR) << "Receive invalid value " << setting_value << " for " << key; + } + is_premium_limit_key = true; + break; + } + } + } + if (is_premium_limit_key) { + continue; + } + if (key == "premium_bot_username") { + premium_bot_username = get_json_value_string(std::move(key_value->value_), key); + continue; + } + if (key == "premium_invoice_slug") { + premium_invoice_slug = get_json_value_string(std::move(key_value->value_), key); + continue; + } + if (key == "premium_purchase_blocked") { + is_premium_available = !get_json_value_bool(std::move(key_value->value_), key); + continue; + } + if (key == "stickers_premium_by_emoji_num") { + stickers_premium_by_emoji_num = get_json_value_int(std::move(key_value->value_), key); + continue; + } + if (key == "stickers_normal_by_emoji_per_premium_num") { + stickers_normal_by_emoji_per_premium_num = get_json_value_int(std::move(key_value->value_), key); + continue; + } new_values.push_back(std::move(key_value)); } @@ -1816,6 +1883,46 @@ void ConfigManager::process_app_config(tl_object_ptr &c shared_config.set_option_integer("reactions_uniq_max", reactions_uniq_max); } + bool is_premium = shared_config.get_option_boolean("is_premium"); + + auto chat_filter_count_max = shared_config.get_option_integer( + is_premium ? Slice("dialog_filters_limit_premium") : Slice("dialog_filters_limit_default"), is_premium ? 20 : 10); + shared_config.set_option_integer("chat_filter_count_max", static_cast(chat_filter_count_max)); + + auto chat_filter_chosen_chat_count_max = shared_config.get_option_integer( + is_premium ? Slice("dialog_filters_chats_limit_premium") : Slice("dialog_filters_chats_limit_default"), + is_premium ? 200 : 100); + shared_config.set_option_integer("chat_filter_chosen_chat_count_max", + static_cast(chat_filter_chosen_chat_count_max)); + + auto bio_length_max = shared_config.get_option_integer( + is_premium ? Slice("about_length_limit_premium") : Slice("about_length_limit_default"), is_premium ? 140 : 70); + shared_config.set_option_integer("bio_length_max", bio_length_max); + + if (!is_premium_available) { + premium_bot_username.clear(); // just in case + premium_invoice_slug.clear(); // just in case + premium_features.clear(); // just in case + shared_config.set_option_empty("is_premium_available"); + } else { + shared_config.set_option_boolean("is_premium_available", is_premium_available); + } + shared_config.set_option_string("premium_features", implode(premium_features, ',')); + if (premium_bot_username.empty()) { + shared_config.set_option_empty("premium_bot_username"); + } else { + shared_config.set_option_string("premium_bot_username", premium_bot_username); + } + if (premium_invoice_slug.empty()) { + shared_config.set_option_empty("premium_invoice_slug"); + } else { + shared_config.set_option_string("premium_invoice_slug", premium_invoice_slug); + } + + shared_config.set_option_integer("stickers_premium_by_emoji_num", stickers_premium_by_emoji_num); + shared_config.set_option_integer("stickers_normal_by_emoji_per_premium_num", + stickers_normal_by_emoji_per_premium_num); + shared_config.set_option_empty("default_ton_blockchain_config"); shared_config.set_option_empty("default_ton_blockchain_name"); diff --git a/td/telegram/ConfigManager.h b/td/telegram/ConfigManager.h index 472982cea..b04be2133 100644 --- a/td/telegram/ConfigManager.h +++ b/td/telegram/ConfigManager.h @@ -81,7 +81,7 @@ class ConfigManager final : public NetQueryCallback { public: explicit ConfigManager(ActorShared<> parent); - void request_config(); + void request_config(bool reopen_sessions); void lazy_request_config(); @@ -108,6 +108,7 @@ class ConfigManager final : public NetQueryCallback { private: ActorShared<> parent_; int32 config_sent_cnt_{0}; + bool reopen_sessions_after_get_config_{false}; ActorOwn config_recoverer_; int ref_cnt_{1}; Timestamp expire_time_; @@ -141,7 +142,7 @@ class ConfigManager final : public NetQueryCallback { void on_result(NetQueryPtr res) final; - void request_config_from_dc_impl(DcId dc_id); + void request_config_from_dc_impl(DcId dc_id, bool reopen_sessions); void process_config(tl_object_ptr config); void try_request_app_config(); diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 29b374f39..2a00b9ac5 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -6,13 +6,17 @@ // #include "td/telegram/ContactsManager.h" +#include "td/telegram/AnimationsManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BotMenuButton.h" #include "td/telegram/ChannelParticipantFilter.h" +#include "td/telegram/ConfigManager.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogLocation.h" +#include "td/telegram/Document.h" +#include "td/telegram/DocumentsManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" @@ -843,6 +847,86 @@ class ToggleChannelSignaturesQuery final : public Td::ResultHandler { } }; +class ToggleChannelJoinToSendQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ToggleChannelJoinToSendQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool join_to_send) { + channel_id_ = channel_id; + auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleJoinToSend(std::move(input_channel), join_to_send))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleChannelJoinToSendQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinToSendQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleChannelJoinRequestQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ToggleChannelJoinRequestQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool join_request) { + channel_id_ = channel_id; + auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleJoinRequest(std::move(input_channel), join_request))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleChannelJoinRequestQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinRequestQuery"); + } + promise_.set_error(std::move(status)); + } +}; + class TogglePrehistoryHiddenQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; @@ -1339,7 +1423,7 @@ class ExportChatInviteQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ExportChatInviteQuery: " << to_string(ptr); - DialogInviteLink invite_link(std::move(ptr)); + DialogInviteLink invite_link(std::move(ptr), "ExportChatInviteQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -1401,7 +1485,7 @@ class EditChatInviteLinkQuery final : public Td::ResultHandler { td_->contacts_manager_->on_get_users(std::move(invite->users_), "EditChatInviteLinkQuery"); - DialogInviteLink invite_link(std::move(invite->invite_)); + DialogInviteLink invite_link(std::move(invite->invite_), "EditChatInviteLinkQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -1450,7 +1534,7 @@ class GetExportedChatInviteQuery final : public Td::ResultHandler { td_->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInviteQuery"); - DialogInviteLink invite_link(std::move(result->invite_)); + DialogInviteLink invite_link(std::move(result->invite_), "GetExportedChatInviteQuery"); if (!invite_link.is_valid()) { LOG(ERROR) << "Receive invalid invite link in " << dialog_id_; return on_error(Status::Error(500, "Receive invalid invite link")); @@ -1512,7 +1596,7 @@ class GetExportedChatInvitesQuery final : public Td::ResultHandler { } vector> invite_links; for (auto &invite : result->invites_) { - DialogInviteLink invite_link(std::move(invite)); + DialogInviteLink invite_link(std::move(invite), "GetExportedChatInvitesQuery"); if (!invite_link.is_valid()) { LOG(ERROR) << "Receive invalid invite link in " << dialog_id_; total_count--; @@ -1853,7 +1937,7 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { td_->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery"); - DialogInviteLink invite_link(std::move(invite->invite_)); + DialogInviteLink invite_link(std::move(invite->invite_), "RevokeChatInviteLinkQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -1865,8 +1949,8 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { td_->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery replaced"); - DialogInviteLink invite_link(std::move(invite->invite_)); - DialogInviteLink new_invite_link(std::move(invite->new_invite_)); + DialogInviteLink invite_link(std::move(invite->invite_), "RevokeChatInviteLinkQuery replaced"); + DialogInviteLink new_invite_link(std::move(invite->new_invite_), "RevokeChatInviteLinkQuery new replaced"); if (!invite_link.is_valid() || !new_invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -3528,26 +3612,28 @@ void ContactsManager::User::store(StorerT &storer) const { STORE_FLAG(is_deleted); STORE_FLAG(is_bot); STORE_FLAG(can_join_groups); - STORE_FLAG(can_read_all_group_messages); + STORE_FLAG(can_read_all_group_messages); // 5 STORE_FLAG(is_inline_bot); STORE_FLAG(need_location_bot); STORE_FLAG(has_last_name); STORE_FLAG(has_username); - STORE_FLAG(has_photo); - STORE_FLAG(false); // legacy is_restricted + STORE_FLAG(has_photo); // 10 + STORE_FLAG(false); // legacy is_restricted STORE_FLAG(has_language_code); STORE_FLAG(have_access_hash); STORE_FLAG(is_support); - STORE_FLAG(is_min_access_hash); + STORE_FLAG(is_min_access_hash); // 15 STORE_FLAG(is_scam); STORE_FLAG(has_cache_version); STORE_FLAG(has_is_contact); STORE_FLAG(is_contact); - STORE_FLAG(is_mutual_contact); + STORE_FLAG(is_mutual_contact); // 20 STORE_FLAG(has_restriction_reasons); STORE_FLAG(need_apply_min_photo); STORE_FLAG(is_fake); STORE_FLAG(can_be_added_to_attach_menu); + STORE_FLAG(is_premium); // 25 + STORE_FLAG(attach_menu_enabled); END_STORE_FLAGS(); store(first_name, storer); if (has_last_name) { @@ -3619,6 +3705,8 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(need_apply_min_photo); PARSE_FLAG(is_fake); PARSE_FLAG(can_be_added_to_attach_menu); + PARSE_FLAG(is_premium); + PARSE_FLAG(attach_menu_enabled); END_PARSE_FLAGS(); parse(first_name, parser); if (has_last_name) { @@ -3709,6 +3797,8 @@ void ContactsManager::UserFull::store(StorerT &storer) const { bool has_group_administrator_rights = group_administrator_rights != AdministratorRights(); bool has_broadcast_administrator_rights = broadcast_administrator_rights != AdministratorRights(); bool has_menu_button = menu_button != nullptr; + bool has_description_photo = !description_photo.is_empty(); + bool has_description_animation = description_animation_file_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_about); STORE_FLAG(is_blocked); @@ -3724,6 +3814,8 @@ void ContactsManager::UserFull::store(StorerT &storer) const { STORE_FLAG(has_group_administrator_rights); STORE_FLAG(has_broadcast_administrator_rights); STORE_FLAG(has_menu_button); + STORE_FLAG(has_description_photo); + STORE_FLAG(has_description_animation); END_STORE_FLAGS(); if (has_about) { store(about, storer); @@ -3751,6 +3843,13 @@ void ContactsManager::UserFull::store(StorerT &storer) const { if (has_menu_button) { store(menu_button, storer); } + if (has_description_photo) { + store(description_photo, storer); + } + if (has_description_animation) { + storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(description_animation_file_id, + storer); + } } template @@ -3764,6 +3863,8 @@ void ContactsManager::UserFull::parse(ParserT &parser) { bool has_group_administrator_rights; bool has_broadcast_administrator_rights; bool has_menu_button; + bool has_description_photo; + bool has_description_animation; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_about); PARSE_FLAG(is_blocked); @@ -3779,6 +3880,8 @@ void ContactsManager::UserFull::parse(ParserT &parser) { PARSE_FLAG(has_group_administrator_rights); PARSE_FLAG(has_broadcast_administrator_rights); PARSE_FLAG(has_menu_button); + PARSE_FLAG(has_description_photo); + PARSE_FLAG(has_description_animation); END_PARSE_FLAGS(); if (has_about) { parse(about, parser); @@ -3806,6 +3909,13 @@ void ContactsManager::UserFull::parse(ParserT &parser) { if (has_menu_button) { parse(menu_button, parser); } + if (has_description_photo) { + parse(description_photo, parser); + } + if (has_description_animation) { + description_animation_file_id = + parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser); + } } template @@ -4042,6 +4152,8 @@ void ContactsManager::Channel::store(StorerT &storer) const { STORE_FLAG(is_gigagroup); STORE_FLAG(noforwards); STORE_FLAG(can_be_deleted); // 25 + STORE_FLAG(join_to_send); + STORE_FLAG(join_request); END_STORE_FLAGS(); store(status, storer); @@ -4113,6 +4225,8 @@ void ContactsManager::Channel::parse(ParserT &parser) { PARSE_FLAG(is_gigagroup); PARSE_FLAG(noforwards); PARSE_FLAG(can_be_deleted); + PARSE_FLAG(join_to_send); + PARSE_FLAG(join_request); END_PARSE_FLAGS(); if (use_new_rights) { @@ -6345,7 +6459,8 @@ void ContactsManager::set_name(const string &first_name, const string &last_name } void ContactsManager::set_bio(const string &bio, Promise &&promise) { - auto new_bio = strip_empty_characters(bio, MAX_BIO_LENGTH); + auto max_bio_length = static_cast(G()->shared_config().get_option_integer("bio_length_max")); + auto new_bio = strip_empty_characters(bio, max_bio_length); for (auto &c : new_bio) { if (c == '\n') { c = ' '; @@ -6482,6 +6597,36 @@ void ContactsManager::toggle_channel_sign_messages(ChannelId channel_id, bool si td_->create_handler(std::move(promise))->send(channel_id, sign_messages); } +void ContactsManager::toggle_channel_join_to_send(ChannelId channel_id, bool join_to_send, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { + return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); + } + if (!get_channel_permissions(c).can_restrict_members()) { + return promise.set_error(Status::Error(400, "Not enough rights")); + } + + td_->create_handler(std::move(promise))->send(channel_id, join_to_send); +} + +void ContactsManager::toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { + return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); + } + if (!get_channel_permissions(c).can_restrict_members()) { + return promise.set_error(Status::Error(400, "Not enough rights")); + } + + td_->create_handler(std::move(promise))->send(channel_id, join_request); +} + void ContactsManager::toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, Promise &&promise) { auto c = get_channel(channel_id); @@ -8493,11 +8638,13 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, } bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0; + bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0; bool is_support = (flags & USER_FLAG_IS_SUPPORT) != 0; bool is_deleted = (flags & USER_FLAG_IS_DELETED) != 0; bool can_join_groups = (flags & USER_FLAG_IS_PRIVATE_BOT) == 0; bool can_read_all_group_messages = (flags & USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED) != 0; bool can_be_added_to_attach_menu = (flags & USER_FLAG_IS_ATTACH_MENU_BOT) != 0; + bool attach_menu_enabled = (flags & USER_FLAG_ATTACH_MENU_ENABLED) != 0; auto restriction_reasons = get_restriction_reasons(std::move(user->restriction_reason_)); bool is_scam = (flags & USER_FLAG_IS_SCAM) != 0; bool is_inline_bot = (flags & USER_FLAG_IS_INLINE_BOT) != 0; @@ -8520,6 +8667,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, if (is_deleted) { // just in case is_verified = false; + is_premium = false; is_support = false; is_bot = false; can_join_groups = false; @@ -8536,15 +8684,17 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, << "Receive not bot " << user_id << " which has bot info version from " << source; int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1; - if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot || - can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages || - restriction_reasons != u->restriction_reasons || is_scam != u->is_scam || is_fake != u->is_fake || - is_inline_bot != u->is_inline_bot || inline_query_placeholder != u->inline_query_placeholder || - need_location_bot != u->need_location_bot || can_be_added_to_attach_menu != u->can_be_added_to_attach_menu) { + if (is_verified != u->is_verified || is_premium != u->is_premium || is_support != u->is_support || + is_bot != u->is_bot || can_join_groups != u->can_join_groups || + can_read_all_group_messages != u->can_read_all_group_messages || restriction_reasons != u->restriction_reasons || + is_scam != u->is_scam || is_fake != u->is_fake || is_inline_bot != u->is_inline_bot || + inline_query_placeholder != u->inline_query_placeholder || need_location_bot != u->need_location_bot || + can_be_added_to_attach_menu != u->can_be_added_to_attach_menu || attach_menu_enabled != u->attach_menu_enabled) { LOG_IF(ERROR, is_bot != u->is_bot && !is_deleted && !u->is_deleted && u->is_received) << "User.is_bot has changed for " << user_id << "/" << u->username << " from " << source << " from " << u->is_bot << " to " << is_bot; u->is_verified = is_verified; + u->is_premium = is_premium; u->is_support = is_support; u->is_bot = is_bot; u->can_join_groups = can_join_groups; @@ -8556,6 +8706,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, u->inline_query_placeholder = std::move(inline_query_placeholder); u->need_location_bot = need_location_bot; u->can_be_added_to_attach_menu = can_be_added_to_attach_menu; + u->attach_menu_enabled = attach_menu_enabled; LOG(DEBUG) << "Info has changed for " << user_id; u->is_changed = true; @@ -8888,8 +9039,8 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id) { flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, user_id.get(), 1, first_name, string(), username, phone_number, std::move(profile_photo), - nullptr, bot_info_version, Auto(), string(), string()); + false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, first_name, string(), username, + phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), string()); on_get_user(std::move(user), "get_user_force"); u = get_user(user_id); CHECK(u != nullptr && u->is_received); @@ -10018,6 +10169,12 @@ void ContactsManager::for_each_secret_chat_with_user(UserId user_id, const std:: void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, bool from_database) { CHECK(u != nullptr); + if (user_id == get_my_id()) { + if (G()->shared_config().get_option_boolean("is_premium") != u->is_premium) { + G()->shared_config().set_option_boolean("is_premium", u->is_premium); + send_closure(td_->config_manager_, &ConfigManager::request_config, true); + } + } if (u->is_name_changed || u->is_username_changed || u->is_is_contact_changed) { update_contacts_hints(u, user_id, from_database); u->is_username_changed = false; @@ -10625,14 +10782,34 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); } string description; + Photo description_photo; + FileId description_animation_file_id; if (user->bot_info_ != nullptr && !td_->auth_manager_->is_bot()) { description = std::move(user->bot_info_->description_); + description_photo = + get_photo(td_->file_manager_.get(), std::move(user->bot_info_->description_photo_), DialogId(user_id)); + auto document = std::move(user->bot_info_->description_document_); + if (document != nullptr) { + int32 document_id = document->get_id(); + if (document_id == telegram_api::document::ID) { + auto parsed_document = td_->documents_manager_->on_get_document( + move_tl_object_as(document), DialogId(user_id)); + if (parsed_document.type == Document::Type::Animation) { + description_animation_file_id = parsed_document.file_id; + } else { + LOG(ERROR) << "Receive non-animation document in bot description"; + } + } + } on_update_user_full_commands(user_full, user_id, std::move(user->bot_info_->commands_)); on_update_user_full_menu_button(user_full, user_id, std::move(user->bot_info_->menu_button_)); } - if (user_full->description != description) { + if (user_full->description != description || user_full->description_photo != description_photo || + user_full->description_animation_file_id != description_animation_file_id) { user_full->description = std::move(description); + user_full->description_photo = std::move(description_photo); + user_full->description_animation_file_id = description_animation_file_id; user_full->is_changed = true; } @@ -11642,7 +11819,6 @@ void ContactsManager::on_update_user_full_commands(UserFull *user_full, UserId u void ContactsManager::on_update_user_full_menu_button(UserFull *user_full, UserId user_id, tl_object_ptr &&bot_menu_button) { CHECK(user_full != nullptr); - CHECK(bot_menu_button != nullptr); auto new_button = get_bot_menu_button(std::move(bot_menu_button)); bool is_changed; if (user_full->menu_button == nullptr) { @@ -11906,6 +12082,8 @@ void ContactsManager::drop_user_full(UserId user_id) { user_full->need_phone_number_privacy_exception = false; user_full->about = string(); user_full->description = string(); + user_full->description_photo = Photo(); + user_full->description_animation_file_id = FileId(); user_full->menu_button = nullptr; user_full->commands.clear(); user_full->common_chat_count = 0; @@ -12788,17 +12966,18 @@ void ContactsManager::on_get_permanent_dialog_invite_link(DialogId dialog_id, co } void ContactsManager::on_update_chat_full_invite_link(ChatFull *chat_full, - tl_object_ptr &&invite_link) { + tl_object_ptr &&invite_link) { CHECK(chat_full != nullptr); - if (update_permanent_invite_link(chat_full->invite_link, DialogInviteLink(std::move(invite_link)))) { + if (update_permanent_invite_link(chat_full->invite_link, DialogInviteLink(std::move(invite_link), "ChatFull"))) { chat_full->is_changed = true; } } void ContactsManager::on_update_channel_full_invite_link( - ChannelFull *channel_full, tl_object_ptr &&invite_link) { + ChannelFull *channel_full, tl_object_ptr &&invite_link) { CHECK(channel_full != nullptr); - if (update_permanent_invite_link(channel_full->invite_link, DialogInviteLink(std::move(invite_link)))) { + if (update_permanent_invite_link(channel_full->invite_link, + DialogInviteLink(std::move(invite_link), "ChannelFull"))) { channel_full->is_changed = true; } } @@ -14154,6 +14333,11 @@ bool ContactsManager::have_min_user(UserId user_id) const { return users_.count(user_id) > 0; } +bool ContactsManager::is_user_premium(UserId user_id) const { + auto u = get_user(user_id); + return u != nullptr && u->is_premium; +} + bool ContactsManager::is_user_deleted(UserId user_id) const { auto u = get_user(user_id); return u == nullptr || u->is_deleted; @@ -14657,7 +14841,8 @@ ContactsManager::ChatFull *ContactsManager::add_chat_full(ChatId chat_id) { return chat_full_ptr.get(); } -bool ContactsManager::is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id) { +bool ContactsManager::is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, + bool only_participants) const { CHECK(c != nullptr); CHECK(chat_full != nullptr); if (!c->is_active && chat_full->version == -1) { @@ -14670,11 +14855,17 @@ bool ContactsManager::is_chat_full_outdated(const ChatFull *chat_full, const Cha return true; } - if (c->is_active && c->status.can_manage_invite_links() && !chat_full->invite_link.is_valid()) { + if (!only_participants && c->is_active && c->status.can_manage_invite_links() && !chat_full->invite_link.is_valid()) { LOG(INFO) << "Have outdated invite link in " << chat_id; return true; } + if (!only_participants && + !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo)) { + LOG(INFO) << "Have outdated chat photo in " << chat_id; + return true; + } + LOG(DEBUG) << "Full " << chat_id << " is up-to-date with version " << chat_full->version; return false; } @@ -14691,7 +14882,7 @@ void ContactsManager::load_chat_full(ChatId chat_id, bool force, Promise & return send_get_chat_full_query(chat_id, std::move(promise), source); } - if (is_chat_full_outdated(chat_full, c, chat_id)) { + if (is_chat_full_outdated(chat_full, c, chat_id, false)) { LOG(INFO) << "Have outdated full " << chat_id; if (td_->auth_manager_->is_bot() && !force) { return send_get_chat_full_query(chat_id, std::move(promise), source); @@ -14908,6 +15099,14 @@ bool ContactsManager::get_channel_can_be_deleted(const Channel *c) { return c->can_be_deleted; } +bool ContactsManager::get_channel_join_to_send(const Channel *c) { + return c->join_to_send || !c->is_megagroup || !c->has_linked_channel; +} + +bool ContactsManager::get_channel_join_request(const Channel *c) { + return c->join_request && c->is_megagroup && (is_channel_public(c) || c->has_linked_channel); +} + ChannelId ContactsManager::get_channel_linked_channel_id(ChannelId channel_id) { auto channel_full = get_channel_full_const(channel_id); if (channel_full == nullptr) { @@ -15505,7 +15704,7 @@ void ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, Promi } auto chat_full = get_chat_full_force(chat_id, "get_chat_participant"); - if (chat_full == nullptr || (td_->auth_manager_->is_bot() && is_chat_full_outdated(chat_full, c, chat_id))) { + if (chat_full == nullptr || (td_->auth_manager_->is_bot() && is_chat_full_outdated(chat_full, c, chat_id, true))) { auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result &&result) mutable { TRY_STATUS_PROMISE(promise, std::move(result)); @@ -15515,7 +15714,7 @@ void ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, Promi return; } - if (is_chat_full_outdated(chat_full, c, chat_id)) { + if (is_chat_full_outdated(chat_full, c, chat_id, true)) { send_get_chat_full_query(chat_id, Auto(), "get_chat_participant lazy"); } @@ -16011,6 +16210,8 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char bool has_linked_channel = (channel.flags_ & CHANNEL_FLAG_HAS_LINKED_CHAT) != 0; bool sign_messages = (channel.flags_ & CHANNEL_FLAG_SIGN_MESSAGES) != 0; + bool join_to_send = (channel.flags_ & CHANNEL_FLAG_JOIN_TO_SEND) != 0; + bool join_request = (channel.flags_ & CHANNEL_FLAG_JOIN_REQUEST) != 0; bool is_slow_mode_enabled = (channel.flags_ & CHANNEL_FLAG_IS_SLOW_MODE_ENABLED) != 0; bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; bool is_verified = (channel.flags_ & CHANNEL_FLAG_IS_VERIFIED) != 0; @@ -16072,6 +16273,9 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char Channel *c = get_channel_force(channel_id); if (c != nullptr) { LOG(DEBUG) << "Receive known min " << channel_id; + + auto old_join_to_send = get_channel_join_to_send(c); + auto old_join_request = get_channel_join_request(c); on_update_channel_title(c, channel_id, std::move(channel.title_)); on_update_channel_username(c, channel_id, std::move(channel.username_)); on_update_channel_photo(c, channel_id, std::move(channel.photo_)); @@ -16093,12 +16297,21 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char c->is_changed = true; invalidate_channel_full(channel_id, !c->is_slow_mode_enabled); } + if (c->join_to_send != join_to_send || c->join_request != join_request) { + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->need_save_to_database = true; + } // sign_messages isn't known for min-channels if (c->is_verified != is_verified) { c->is_verified = is_verified; c->is_changed = true; } + if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { + c->is_changed = true; + } update_channel(c, channel_id); } else { @@ -16129,6 +16342,8 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char if (c->status.is_banned()) { // possibly uninited channel min_channels_.erase(channel_id); } + auto old_join_to_send = get_channel_join_to_send(c); + auto old_join_request = get_channel_join_request(c); if (c->access_hash != access_hash) { c->access_hash = access_hash; c->need_save_to_database = true; @@ -16162,16 +16377,27 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char c->is_scam = is_scam; c->is_fake = is_fake; c->is_gigagroup = is_gigagroup; + c->join_to_send = join_to_send; + c->join_request = join_request; c->is_changed = true; need_invalidate_channel_full = true; } + if (c->join_to_send != join_to_send || c->join_request != join_request) { + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->need_save_to_database = true; + } if (c->is_verified != is_verified || c->sign_messages != sign_messages) { c->is_verified = is_verified; c->sign_messages = sign_messages; c->is_changed = true; } + if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { + c->is_changed = true; + } if (c->cache_version != Channel::CACHE_VERSION) { c->cache_version = Channel::CACHE_VERSION; @@ -16220,6 +16446,8 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co if (c->status.is_banned()) { // possibly uninited channel min_channels_.erase(channel_id); } + auto old_join_to_send = get_channel_join_to_send(c); + auto old_join_request = get_channel_join_request(c); if (c->access_hash != channel.access_hash_) { c->access_hash = channel.access_hash_; c->need_save_to_database = true; @@ -16240,6 +16468,8 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false, "receive channelForbidden"); bool sign_messages = false; + bool join_to_send = false; + bool join_request = false; bool is_slow_mode_enabled = false; bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; bool is_verified = false; @@ -16259,23 +16489,35 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co bool need_invalidate_channel_full = false; if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || - !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake) { + !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake || + c->join_to_send != join_to_send || c->join_request != join_request) { // c->has_linked_channel = has_linked_channel; c->is_slow_mode_enabled = is_slow_mode_enabled; c->is_megagroup = is_megagroup; c->restriction_reasons.clear(); c->is_scam = is_scam; c->is_fake = is_fake; + c->join_to_send = join_to_send; + c->join_request = join_request; c->is_changed = true; need_invalidate_channel_full = true; } + if (c->join_to_send != join_to_send || c->join_request != join_request) { + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->need_save_to_database = true; + } if (c->sign_messages != sign_messages || c->is_verified != is_verified) { c->sign_messages = sign_messages; c->is_verified = is_verified; c->is_changed = true; } + if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { + c->is_changed = true; + } bool need_drop_participant_count = c->participant_count != 0; if (need_drop_participant_count) { @@ -16390,7 +16632,7 @@ td_api::object_ptr ContactsManager::get_user_status_object(U td_api::object_ptr ContactsManager::get_update_unknown_user_object(UserId user_id) { return td_api::make_object(td_api::make_object( user_id.get(), "", "", "", "", td_api::make_object(), nullptr, false, false, false, - false, "", false, false, false, td_api::make_object(), "")); + false, false, "", false, false, false, td_api::make_object(), "", false)); } int64 ContactsManager::get_user_id_object(UserId user_id, const char *source) const { @@ -16450,8 +16692,8 @@ tl_object_ptr ContactsManager::get_user_object(UserId user_id, con return make_tl_object( user_id.get(), u->first_name, u->last_name, u->username, u->phone_number, get_user_status_object(user_id, u), get_profile_photo_object(td_->file_manager_.get(), u->photo), u->is_contact, u->is_mutual_contact, u->is_verified, - u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->is_received, - std::move(type), u->language_code); + u->is_premium, u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, + u->is_received, std::move(type), u->language_code, u->attach_menu_enabled); } vector ContactsManager::get_user_ids_object(const vector &user_ids, const char *source) const { @@ -16475,24 +16717,33 @@ tl_object_ptr ContactsManager::get_user_full_info_object(U CHECK(user_full != nullptr); td_api::object_ptr bot_info; bool is_bot = is_user_bot(user_id); + td_api::object_ptr bio_object; if (is_bot) { auto menu_button = get_bot_menu_button_object(td_, user_full->menu_button.get()); auto commands = transform(user_full->commands, [](const auto &command) { return command.get_bot_command_object(); }); bot_info = td_api::make_object( - user_full->about, user_full->description, std::move(menu_button), std::move(commands), + user_full->about, user_full->description, + get_photo_object(td_->file_manager_.get(), user_full->description_photo), + td_->animations_manager_->get_animation_object(user_full->description_animation_file_id), + std::move(menu_button), std::move(commands), user_full->group_administrator_rights == AdministratorRights() ? nullptr : user_full->group_administrator_rights.get_chat_administrator_rights_object(), user_full->broadcast_administrator_rights == AdministratorRights() ? nullptr : user_full->broadcast_administrator_rights.get_chat_administrator_rights_object()); + } else { + FormattedText bio; + bio.text = user_full->about; + bio.entities = find_entities(bio.text, true, true, !is_user_premium(user_id)); + bio_object = get_formatted_text_object(bio, true, 0); } return make_tl_object( get_chat_photo_object(td_->file_manager_.get(), user_full->photo), user_full->is_blocked, user_full->can_be_called, user_full->supports_video_calls, user_full->has_private_calls, - !user_full->private_forward_name.empty(), user_full->need_phone_number_privacy_exception, - is_bot ? string() : user_full->about, user_full->common_chat_count, std::move(bot_info)); + !user_full->private_forward_name.empty(), user_full->need_phone_number_privacy_exception, std::move(bio_object), + user_full->common_chat_count, std::move(bot_info)); } td_api::object_ptr ContactsManager::get_update_unknown_basic_group_object(ChatId chat_id) { @@ -16550,10 +16801,10 @@ tl_object_ptr ContactsManager::get_basic_group_full_ td_api::object_ptr ContactsManager::get_update_unknown_supergroup_object( ChannelId channel_id) const { auto min_channel = get_min_channel(channel_id); + bool is_megagroup = min_channel == nullptr ? false : min_channel->is_megagroup_; return td_api::make_object(td_api::make_object( channel_id.get(), string(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, false, - false, false, false, min_channel == nullptr ? true : !min_channel->is_megagroup_, false, false, string(), false, - false)); + false, false, !is_megagroup, false, false, !is_megagroup, false, false, string(), false, false)); } int64 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { @@ -16579,9 +16830,9 @@ tl_object_ptr ContactsManager::get_supergroup_object(Channel } return td_api::make_object( channel_id.get(), c->username, c->date, get_channel_status(c).get_chat_member_status_object(), - c->participant_count, c->has_linked_channel, c->has_location, c->sign_messages, c->is_slow_mode_enabled, - !c->is_megagroup, c->is_gigagroup, c->is_verified, get_restriction_reason_description(c->restriction_reasons), - c->is_scam, c->is_fake); + c->participant_count, c->has_linked_channel, c->has_location, c->sign_messages, get_channel_join_to_send(c), + get_channel_join_request(c), c->is_slow_mode_enabled, !c->is_megagroup, c->is_gigagroup, c->is_verified, + get_restriction_reason_description(c->restriction_reasons), c->is_scam, c->is_fake); } tl_object_ptr ContactsManager::get_supergroup_full_info_object(ChannelId channel_id) const { diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index 4c5b19507..2730d2369 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -349,6 +349,10 @@ class ContactsManager final : public Actor { void toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise); + void toggle_channel_join_to_send(ChannelId channel_id, bool joint_to_send, Promise &&promise); + + void toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise); + void toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, Promise &&promise); @@ -450,6 +454,8 @@ class ContactsManager final : public Actor { bool is_user_contact(UserId user_id, bool is_mutual = false) const; + bool is_user_premium(UserId user_id) const; + bool is_user_deleted(UserId user_id) const; bool is_user_support(UserId user_id) const; @@ -654,6 +660,7 @@ class ContactsManager final : public Actor { bool is_min_access_hash = true; bool is_received = false; bool is_verified = false; + bool is_premium = false; bool is_support = false; bool is_deleted = true; bool is_bot = true; @@ -667,6 +674,7 @@ class ContactsManager final : public Actor { bool is_mutual_contact = false; bool need_apply_min_photo = false; bool can_be_added_to_attach_menu = false; + bool attach_menu_enabled = false; bool is_photo_inited = false; @@ -704,8 +712,10 @@ class ContactsManager final : public Actor { Photo photo; string about; - string description; string private_forward_name; + string description; + Photo description_photo; + FileId description_animation_file_id; unique_ptr menu_button; vector commands; @@ -826,7 +836,7 @@ class ContactsManager final : public Actor { int32 date = 0; int32 participant_count = 0; - static constexpr uint32 CACHE_VERSION = 8; + static constexpr uint32 CACHE_VERSION = 9; uint32 cache_version = 0; bool has_linked_channel = false; @@ -835,6 +845,8 @@ class ContactsManager final : public Actor { bool is_slow_mode_enabled = false; bool noforwards = false; bool can_be_deleted = false; + bool join_to_send = false; + bool join_request = false; bool is_megagroup = false; bool is_gigagroup = false; @@ -1016,7 +1028,6 @@ class ContactsManager final : public Actor { static constexpr int32 MAX_GET_PROFILE_PHOTOS = 100; // server side limit static constexpr size_t MAX_NAME_LENGTH = 64; // server side limit for first/last name static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description - static constexpr size_t MAX_BIO_LENGTH = 70; // server side limit static constexpr size_t MAX_INVITE_LINK_TITLE_LENGTH = 32; // server side limit static constexpr int32 MAX_GET_CHANNEL_PARTICIPANTS = 200; // server side limit @@ -1048,6 +1059,8 @@ class ContactsManager final : public Actor { static constexpr int32 USER_FLAG_NEED_APPLY_MIN_PHOTO = 1 << 25; static constexpr int32 USER_FLAG_IS_FAKE = 1 << 26; static constexpr int32 USER_FLAG_IS_ATTACH_MENU_BOT = 1 << 27; + static constexpr int32 USER_FLAG_IS_PREMIUM = 1 << 28; + static constexpr int32 USER_FLAG_ATTACH_MENU_ENABLED = 1 << 29; static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0; static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1; @@ -1104,6 +1117,8 @@ class ContactsManager final : public Actor { static constexpr int32 CHANNEL_FLAG_IS_FAKE = 1 << 25; static constexpr int32 CHANNEL_FLAG_IS_GIGAGROUP = 1 << 26; static constexpr int32 CHANNEL_FLAG_NOFORWARDS = 1 << 27; + static constexpr int32 CHANNEL_FLAG_JOIN_TO_SEND = 1 << 28; + static constexpr int32 CHANNEL_FLAG_JOIN_REQUEST = 1 << 29; static constexpr int32 CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 0; static constexpr int32 CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT = 1 << 1; @@ -1214,6 +1229,8 @@ class ContactsManager final : public Actor { static bool get_channel_sign_messages(const Channel *c); static bool get_channel_has_linked_channel(const Channel *c); static bool get_channel_can_be_deleted(const Channel *c); + static bool get_channel_join_to_send(const Channel *c); + static bool get_channel_join_request(const Channel *c); void set_my_id(UserId my_id); @@ -1272,7 +1289,7 @@ class ContactsManager final : public Actor { void on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, vector participants, int32 version, bool from_update); void on_update_chat_full_invite_link(ChatFull *chat_full, - tl_object_ptr &&invite_link); + tl_object_ptr &&invite_link); void on_update_channel_photo(Channel *c, ChannelId channel_id, tl_object_ptr &&chat_photo_ptr); @@ -1289,7 +1306,7 @@ class ContactsManager final : public Actor { void on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo); void on_update_channel_full_invite_link(ChannelFull *channel_full, - tl_object_ptr &&invite_link); + tl_object_ptr &&invite_link); void on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, ChannelId linked_channel_id); void on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, const DialogLocation &location); @@ -1397,7 +1414,7 @@ class ContactsManager final : public Actor { void update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, bool from_database = false); - static bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id); + bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, bool only_participants) const; bool is_user_contact(const User *u, UserId user_id, bool is_mutual) const; diff --git a/td/telegram/DialogEventLog.cpp b/td/telegram/DialogEventLog.cpp index 67bfa00d8..41d7b65c0 100644 --- a/td/telegram/DialogEventLog.cpp +++ b/td/telegram/DialogEventLog.cpp @@ -39,7 +39,7 @@ static td_api::object_ptr get_chat_event_action_object( return td_api::make_object(); case telegram_api::channelAdminLogEventActionParticipantJoinByInvite::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_)); + DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionParticipantJoinByInvite"); if (!invite_link.is_valid()) { LOG(ERROR) << "Wrong invite link: " << invite_link; return nullptr; @@ -49,11 +49,7 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionParticipantJoinByRequest::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_)); - if (!invite_link.is_valid()) { - LOG(ERROR) << "Wrong invite link: " << invite_link; - return nullptr; - } + DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionParticipantJoinByRequest"); UserId approver_user_id(action->approved_by_); if (!approver_user_id.is_valid()) { return nullptr; @@ -257,8 +253,8 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionExportedInviteEdit::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink old_invite_link(std::move(action->prev_invite_)); - DialogInviteLink new_invite_link(std::move(action->new_invite_)); + DialogInviteLink old_invite_link(std::move(action->prev_invite_), "channelAdminLogEventActionExportedInviteEdit"); + DialogInviteLink new_invite_link(std::move(action->new_invite_), "channelAdminLogEventActionExportedInviteEdit"); if (!old_invite_link.is_valid() || !new_invite_link.is_valid()) { LOG(ERROR) << "Wrong edited invite link: " << old_invite_link << " -> " << new_invite_link; return nullptr; @@ -269,7 +265,7 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionExportedInviteRevoke::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_)); + DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionExportedInviteRevoke"); if (!invite_link.is_valid()) { LOG(ERROR) << "Wrong revoked invite link: " << invite_link; return nullptr; @@ -279,7 +275,7 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionExportedInviteDelete::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_)); + DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionExportedInviteDelete"); if (!invite_link.is_valid()) { LOG(ERROR) << "Wrong deleted invite link: " << invite_link; return nullptr; diff --git a/td/telegram/DialogFilter.cpp b/td/telegram/DialogFilter.cpp index 53f9066dd..cafa924ef 100644 --- a/td/telegram/DialogFilter.cpp +++ b/td/telegram/DialogFilter.cpp @@ -6,18 +6,30 @@ // #include "td/telegram/DialogFilter.h" +#include "td/telegram/ConfigShared.h" #include "td/telegram/DialogId.h" +#include "td/telegram/Global.h" #include "td/utils/algorithm.h" #include "td/utils/emoji.h" #include "td/utils/FlatHashSet.h" #include "td/utils/format.h" #include "td/utils/logging.h" +#include "td/utils/misc.h" namespace td { -unique_ptr DialogFilter::get_dialog_filter(telegram_api::object_ptr filter, - bool with_id) { +int32 DialogFilter::get_max_filter_dialogs() { + return narrow_cast(G()->shared_config().get_option_integer("chat_filter_chosen_chat_count_max", 100)); +} + +unique_ptr DialogFilter::get_dialog_filter( + telegram_api::object_ptr filter_ptr, bool with_id) { + if (filter_ptr->get_id() != telegram_api::dialogFilter::ID) { + LOG(ERROR) << "Ignore " << to_string(filter_ptr); + return nullptr; + } + auto filter = telegram_api::move_object_as(filter_ptr); DialogFilterId dialog_filter_id(filter->id_); if (with_id && !dialog_filter_id.is_valid()) { LOG(ERROR) << "Receive invalid " << to_string(filter); @@ -87,16 +99,15 @@ Status DialogFilter::check_limits() const { auto included_secret_dialog_count = static_cast(included_dialog_ids.size()) - included_server_dialog_count; auto pinned_secret_dialog_count = static_cast(pinned_dialog_ids.size()) - pinned_server_dialog_count; - if (excluded_server_dialog_count > MAX_INCLUDED_FILTER_DIALOGS || - excluded_secret_dialog_count > MAX_INCLUDED_FILTER_DIALOGS) { + auto limit = get_max_filter_dialogs(); + if (excluded_server_dialog_count > limit || excluded_secret_dialog_count > limit) { return Status::Error(400, "The maximum number of excluded chats exceeded"); } - if (included_server_dialog_count > MAX_INCLUDED_FILTER_DIALOGS || - included_secret_dialog_count > MAX_INCLUDED_FILTER_DIALOGS) { + if (included_server_dialog_count > limit || included_secret_dialog_count > limit) { return Status::Error(400, "The maximum number of included chats exceeded"); } - if (included_server_dialog_count + pinned_server_dialog_count > MAX_INCLUDED_FILTER_DIALOGS || - included_secret_dialog_count + pinned_secret_dialog_count > MAX_INCLUDED_FILTER_DIALOGS) { + if (included_server_dialog_count + pinned_server_dialog_count > limit || + included_secret_dialog_count + pinned_secret_dialog_count > limit) { return Status::Error(400, "The maximum number of pinned chats exceeded"); } @@ -205,7 +216,7 @@ string DialogFilter::get_default_icon_name(const td_api::chatFilter *filter) { return "Custom"; } -telegram_api::object_ptr DialogFilter::get_input_dialog_filter() const { +telegram_api::object_ptr DialogFilter::get_input_dialog_filter() const { int32 flags = 0; if (!emoji.empty()) { flags |= telegram_api::dialogFilter::EMOTICON_MASK; diff --git a/td/telegram/DialogFilter.h b/td/telegram/DialogFilter.h index 73146c37f..f42759ca0 100644 --- a/td/telegram/DialogFilter.h +++ b/td/telegram/DialogFilter.h @@ -20,8 +20,6 @@ namespace td { class DialogFilter { public: - static constexpr int32 MAX_INCLUDED_FILTER_DIALOGS = 100; // server side limit - DialogFilterId dialog_filter_id; string title; string emoji; @@ -43,7 +41,9 @@ class DialogFilter { template void parse(ParserT &parser); - static unique_ptr get_dialog_filter(telegram_api::object_ptr filter, + static int32 get_max_filter_dialogs(); + + static unique_ptr get_dialog_filter(telegram_api::object_ptr filter_ptr, bool with_id); void remove_secret_chat_dialog_ids(); @@ -58,7 +58,7 @@ class DialogFilter { static string get_default_icon_name(const td_api::chatFilter *filter); - telegram_api::object_ptr get_input_dialog_filter() const; + telegram_api::object_ptr get_input_dialog_filter() const; td_api::object_ptr get_chat_filter_info_object() const; diff --git a/td/telegram/DialogInviteLink.cpp b/td/telegram/DialogInviteLink.cpp index f0760109f..736a822ac 100644 --- a/td/telegram/DialogInviteLink.cpp +++ b/td/telegram/DialogInviteLink.cpp @@ -10,14 +10,26 @@ #include "td/telegram/LinkManager.h" #include "td/utils/logging.h" +#include "td/utils/SliceBuilder.h" namespace td { -DialogInviteLink::DialogInviteLink(tl_object_ptr exported_invite) { - if (exported_invite == nullptr) { +DialogInviteLink::DialogInviteLink(tl_object_ptr exported_invite_ptr, + const char *source) { + if (exported_invite_ptr == nullptr) { + return; + } + if (exported_invite_ptr->get_id() != telegram_api::chatInviteExported::ID) { + CHECK(exported_invite_ptr->get_id() == telegram_api::chatInvitePublicJoinRequests::ID) + Slice slice(source); + if (slice != "channelAdminLogEventActionParticipantJoinByRequest" && slice != "updateChatParticipant" && + slice != "updateChannelParticipant" && slice != "updateBotChatInviteRequester") { + LOG(ERROR) << "Receive from " << source << ' ' << to_string(exported_invite_ptr); + } return; } + auto exported_invite = move_tl_object_as(exported_invite_ptr); invite_link_ = std::move(exported_invite->link_); title_ = std::move(exported_invite->title_); creator_user_id_ = UserId(exported_invite->admin_id_); @@ -31,39 +43,40 @@ DialogInviteLink::DialogInviteLink(tl_object_ptrrevoked_; is_permanent_ = exported_invite->permanent_; - LOG_IF(ERROR, !is_valid_invite_link(invite_link_)) << "Unsupported invite link " << invite_link_; + string full_source = PSTRING() << "invite link " << invite_link_ << " from " << source; + LOG_IF(ERROR, !is_valid_invite_link(invite_link_)) << "Unsupported " << full_source; if (!creator_user_id_.is_valid()) { - LOG(ERROR) << "Receive invalid " << creator_user_id_ << " as creator of a link " << invite_link_; + LOG(ERROR) << "Receive invalid " << creator_user_id_ << " as creator of " << full_source; creator_user_id_ = UserId(); } if (date_ != 0 && date_ < 1000000000) { - LOG(ERROR) << "Receive wrong date " << date_ << " as a creation date of a link " << invite_link_; + LOG(ERROR) << "Receive wrong date " << date_ << " as a creation date of " << full_source; date_ = 0; } if (expire_date_ != 0 && expire_date_ < 1000000000) { - LOG(ERROR) << "Receive wrong date " << expire_date_ << " as an expire date of a link " << invite_link_; + LOG(ERROR) << "Receive wrong date " << expire_date_ << " as an expire date of " << full_source; expire_date_ = 0; } if (usage_limit_ < 0) { - LOG(ERROR) << "Receive wrong usage limit " << usage_limit_ << " for a link " << invite_link_; + LOG(ERROR) << "Receive wrong usage limit " << usage_limit_ << " for " << full_source; usage_limit_ = 0; } if (usage_count_ < 0) { - LOG(ERROR) << "Receive wrong usage count " << usage_count_ << " for a link " << invite_link_; + LOG(ERROR) << "Receive wrong usage count " << usage_count_ << " for " << full_source; usage_count_ = 0; } if (edit_date_ != 0 && edit_date_ < 1000000000) { - LOG(ERROR) << "Receive wrong date " << edit_date_ << " as an edit date of a link " << invite_link_; + LOG(ERROR) << "Receive wrong date " << edit_date_ << " as an edit date of " << full_source; edit_date_ = 0; } if (request_count_ < 0) { - LOG(ERROR) << "Receive wrong pending join request count " << request_count_ << " for a link " << invite_link_; + LOG(ERROR) << "Receive wrong pending join request count " << request_count_ << " for " << full_source; request_count_ = 0; } if (is_permanent_ && (!title_.empty() || expire_date_ > 0 || usage_limit_ > 0 || edit_date_ > 0 || request_count_ > 0 || creates_join_request_)) { - LOG(ERROR) << "Receive wrong permanent " << *this; + LOG(ERROR) << "Receive wrong permanent " << full_source << ' ' << *this; title_.clear(); expire_date_ = 0; usage_limit_ = 0; @@ -72,7 +85,7 @@ DialogInviteLink::DialogInviteLink(tl_object_ptr 0) { - LOG(ERROR) << "Receive wrong permanent " << *this; + LOG(ERROR) << "Receive wrong permanent " << full_source << ' ' << *this; usage_limit_ = 0; } } @@ -113,7 +126,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink << invite_link.creator_user_id_ << " created at " << invite_link.date_ << " edited at " << invite_link.edit_date_ << " expiring at " << invite_link.expire_date_ << " used by " << invite_link.usage_count_ << " with usage limit " << invite_link.usage_limit_ << " and " - << invite_link.request_count_ << "pending join requests]"; + << invite_link.request_count_ << " pending join requests]"; } } // namespace td diff --git a/td/telegram/DialogInviteLink.h b/td/telegram/DialogInviteLink.h index 112762c71..813717f64 100644 --- a/td/telegram/DialogInviteLink.h +++ b/td/telegram/DialogInviteLink.h @@ -40,7 +40,7 @@ class DialogInviteLink { public: DialogInviteLink() = default; - explicit DialogInviteLink(tl_object_ptr exported_invite); + DialogInviteLink(tl_object_ptr exported_invite_ptr, const char *source); static bool is_valid_invite_link(Slice invite_link); diff --git a/td/telegram/DocumentsManager.cpp b/td/telegram/DocumentsManager.cpp index e5e4d4535..8a6fb5445 100644 --- a/td/telegram/DocumentsManager.cpp +++ b/td/telegram/DocumentsManager.cpp @@ -238,13 +238,14 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo int64 id; int64 access_hash; int32 dc_id; - int32 size; + int64 size; int32 date = 0; string mime_type; string file_reference; string minithumbnail; PhotoSize thumbnail; AnimationSize animated_thumbnail; + FileId premium_animation_file_id; FileEncryptionKey encryption_key; bool is_web = false; bool is_web_no_proxy = false; @@ -310,11 +311,17 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo } for (auto &thumb : document->video_thumbs_) { if (thumb->type_ == "v") { - animated_thumbnail = - get_animation_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0), id, - access_hash, file_reference, DcId::create(dc_id), owner_dialog_id, std::move(thumb)); - if (animated_thumbnail.file_id.is_valid()) { - break; + if (!animated_thumbnail.file_id.is_valid()) { + animated_thumbnail = + get_animation_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0), id, + access_hash, file_reference, DcId::create(dc_id), owner_dialog_id, std::move(thumb)); + } + } else if (thumb->type_ == "f") { + if (!premium_animation_file_id.is_valid()) { + premium_animation_file_id = + register_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 'f'), id, + access_hash, file_reference, owner_dialog_id, thumb->size_, DcId::create(dc_id), + get_sticker_format_photo_format(sticker_format)); } } } @@ -478,8 +485,9 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo if (thumbnail_format == PhotoFormat::Jpeg) { minithumbnail = string(); } - td_->stickers_manager_->create_sticker(file_id, std::move(minithumbnail), std::move(thumbnail), dimensions, - std::move(sticker), sticker_format, load_data_multipromise_ptr); + td_->stickers_manager_->create_sticker(file_id, premium_animation_file_id, std::move(minithumbnail), + std::move(thumbnail), dimensions, std::move(sticker), sticker_format, + load_data_multipromise_ptr); break; case Document::Type::Video: td_->videos_manager_->create_video(file_id, std::move(minithumbnail), std::move(thumbnail), @@ -590,7 +598,8 @@ bool DocumentsManager::has_input_media(FileId file_id, FileId thumbnail_file_id, SecretInputMedia DocumentsManager::get_secret_input_media(FileId document_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const { + const string &caption, BufferSlice thumbnail, + int32 layer) const { const GeneralDocument *document = get_document(document_file_id); CHECK(document != nullptr); auto file_view = td_->file_manager_->get_file_view(document_file_id); @@ -616,7 +625,8 @@ SecretInputMedia DocumentsManager::get_secret_input_media(FileId document_file_i document->mime_type, file_view, std::move(attributes), - caption}; + caption, + layer}; } tl_object_ptr DocumentsManager::get_input_media( diff --git a/td/telegram/DocumentsManager.h b/td/telegram/DocumentsManager.h index 5b8191c7c..e910da4a2 100644 --- a/td/telegram/DocumentsManager.h +++ b/td/telegram/DocumentsManager.h @@ -91,7 +91,7 @@ class DocumentsManager { SecretInputMedia get_secret_input_media(FileId document_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const; + const string &caption, BufferSlice thumbnail, int32 layer) const; tl_object_ptr get_input_media(FileId file_id, tl_object_ptr input_file, diff --git a/td/telegram/EncryptedFile.h b/td/telegram/EncryptedFile.h index e01065712..8d4937d56 100644 --- a/td/telegram/EncryptedFile.h +++ b/td/telegram/EncryptedFile.h @@ -10,22 +10,23 @@ #include "td/utils/common.h" #include "td/utils/format.h" +#include "td/utils/misc.h" #include "td/utils/StringBuilder.h" #include "td/utils/tl_helpers.h" namespace td { struct EncryptedFile { - static constexpr int32 MAGIC = 0x473d738a; int64 id_ = 0; int64 access_hash_ = 0; - int32 size_ = 0; + int64 size_ = 0; int32 dc_id_ = 0; int32 key_fingerprint_ = 0; EncryptedFile() = default; - EncryptedFile(int64 id, int64 access_hash, int32 size, int32 dc_id, int32 key_fingerprint) + EncryptedFile(int64 id, int64 access_hash, int64 size, int32 dc_id, int32 key_fingerprint) : id_(id), access_hash_(access_hash), size_(size), dc_id_(dc_id), key_fingerprint_(key_fingerprint) { + CHECK(size_ >= 0); } static unique_ptr get_encrypted_file(tl_object_ptr file_ptr) { @@ -33,16 +34,26 @@ struct EncryptedFile { return nullptr; } auto file = move_tl_object_as(file_ptr); + if (file->size_ < 0) { + return nullptr; + } return make_unique(file->id_, file->access_hash_, file->size_, file->dc_id_, file->key_fingerprint_); } template void store(StorerT &storer) const { using td::store; - store(MAGIC, storer); + bool has_64bit_size = (size_ >= (static_cast(1) << 31)); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_64bit_size); + END_STORE_FLAGS(); store(id_, storer); store(access_hash_, storer); - store(size_, storer); + if (has_64bit_size) { + store(size_, storer); + } else { + store(narrow_cast(size_), storer); + } store(dc_id_, storer); store(key_fingerprint_, storer); } @@ -50,18 +61,25 @@ struct EncryptedFile { template void parse(ParserT &parser) { using td::parse; - int32 got_magic; - - parse(got_magic, parser); + bool has_64bit_size; + BEGIN_PARSE_FLAGS(); + constexpr int32 OLD_MAGIC = 0x473d738a; + if (flags_parse == OLD_MAGIC) { + flags_parse = 0; + } + PARSE_FLAG(has_64bit_size); + END_PARSE_FLAGS(); parse(id_, parser); parse(access_hash_, parser); - parse(size_, parser); + if (has_64bit_size) { + parse(size_, parser); + } else { + int32 int_size; + parse(int_size, parser); + size_ = int_size; + } parse(dc_id_, parser); parse(key_fingerprint_, parser); - - if (got_magic != MAGIC) { - parser.set_error("EncryptedFile magic mismatch"); - } } }; diff --git a/td/telegram/Global.cpp b/td/telegram/Global.cpp index 2dcc56129..5a388fa92 100644 --- a/td/telegram/Global.cpp +++ b/td/telegram/Global.cpp @@ -8,7 +8,6 @@ #include "td/telegram/ConfigShared.h" #include "td/telegram/net/ConnectionCreator.h" -#include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/net/TempAuthKeyWatchdog.h" #include "td/telegram/OptionManager.h" diff --git a/td/telegram/Global.h b/td/telegram/Global.h index 3cdce13e2..83fbb65ec 100644 --- a/td/telegram/Global.h +++ b/td/telegram/Global.h @@ -8,6 +8,7 @@ #include "td/telegram/DhConfig.h" #include "td/telegram/net/DcId.h" +#include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/TdParameters.h" @@ -45,7 +46,6 @@ class GroupCallManager; class LanguagePackManager; class LinkManager; class MessagesManager; -class MtprotoHeader; class NetQueryDispatcher; class NotificationManager; class NotificationSettingsManager; diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index c7dc200e5..a9ce97398 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -1235,7 +1235,7 @@ template <> tl_object_ptr copy(const td_api::sticker &obj) { return td_api::make_object(obj.set_id_, obj.width_, obj.height_, obj.emoji_, copy(obj.type_), transform(obj.outline_, copy_closed_vector_path), copy(obj.thumbnail_), - copy(obj.sticker_)); + copy(obj.premium_animation_), copy(obj.sticker_)); } template <> @@ -1247,7 +1247,8 @@ tl_object_ptr copy(const td_api::video &obj) { template <> tl_object_ptr copy(const td_api::voiceNote &obj) { - return td_api::make_object(obj.duration_, obj.waveform_, obj.mime_type_, copy(obj.voice_)); + return td_api::make_object(obj.duration_, obj.waveform_, obj.mime_type_, obj.is_recognized_, + obj.recognized_text_, copy(obj.voice_)); } template <> diff --git a/td/telegram/LinkManager.cpp b/td/telegram/LinkManager.cpp index 3a942c2a4..6be73aae2 100644 --- a/td/telegram/LinkManager.cpp +++ b/td/telegram/LinkManager.cpp @@ -36,6 +36,7 @@ #include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" #include "td/utils/Time.h" +#include "td/utils/utf8.h" #include @@ -147,6 +148,28 @@ static AdministratorRights get_administrator_rights(Slice rights, bool for_chann for_channel ? ChannelType::Broadcast : ChannelType::Megagroup); } +td_api::object_ptr get_target_chat_chosen(Slice chat_types) { + bool allow_users = false; + bool allow_bots = false; + bool allow_groups = false; + bool allow_channels = false; + for (auto chat_type : full_split(chat_types, ' ')) { + if (chat_type == "users") { + allow_users = true; + } else if (chat_type == "bots") { + allow_bots = true; + } else if (chat_type == "groups") { + allow_groups = true; + } else if (chat_type == "channels") { + allow_channels = true; + } + } + if (!allow_users && !allow_bots && !allow_groups && !allow_channels) { + return nullptr; + } + return td_api::make_object(allow_users, allow_bots, allow_groups, allow_channels); +} + class LinkManager::InternalLinkActiveSessions final : public InternalLink { td_api::object_ptr get_internal_link_type_object() const final { return td_api::make_object(); @@ -154,18 +177,31 @@ class LinkManager::InternalLinkActiveSessions final : public InternalLink { }; class LinkManager::InternalLinkAttachMenuBot final : public InternalLink { + td_api::object_ptr allowed_chat_types_; unique_ptr dialog_link_; string bot_username_; string url_; td_api::object_ptr get_internal_link_type_object() const final { - return td_api::make_object( - dialog_link_ == nullptr ? nullptr : dialog_link_->get_internal_link_type_object(), bot_username_, url_); + td_api::object_ptr target_chat; + if (dialog_link_ != nullptr) { + target_chat = td_api::make_object(dialog_link_->get_internal_link_type_object()); + } else if (allowed_chat_types_ != nullptr) { + target_chat = td_api::make_object( + allowed_chat_types_->allow_user_chats_, allowed_chat_types_->allow_bot_chats_, + allowed_chat_types_->allow_group_chats_, allowed_chat_types_->allow_channel_chats_); + } else { + target_chat = td_api::make_object(); + } + return td_api::make_object(std::move(target_chat), bot_username_, url_); } public: - InternalLinkAttachMenuBot(unique_ptr dialog_link, string bot_username, Slice start_parameter) - : dialog_link_(std::move(dialog_link)), bot_username_(std::move(bot_username)) { + InternalLinkAttachMenuBot(td_api::object_ptr allowed_chat_types, + unique_ptr dialog_link, string bot_username, Slice start_parameter) + : allowed_chat_types_(std::move(allowed_chat_types)) + , dialog_link_(std::move(dialog_link)) + , bot_username_(std::move(bot_username)) { if (!start_parameter.empty()) { url_ = PSTRING() << "start://" << start_parameter; } @@ -297,6 +333,18 @@ class LinkManager::InternalLinkGame final : public InternalLink { } }; +class LinkManager::InternalLinkInvoice final : public InternalLink { + string invoice_name_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(invoice_name_); + } + + public: + explicit InternalLinkInvoice(string invoice_name) : invoice_name_(std::move(invoice_name)) { + } +}; + class LinkManager::InternalLinkLanguage final : public InternalLink { string language_pack_id_; @@ -365,6 +413,18 @@ class LinkManager::InternalLinkPassportDataRequest final : public InternalLink { } }; +class LinkManager::InternalLinkPremiumFeatures final : public InternalLink { + string referrer_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(referrer_); + } + + public: + explicit InternalLinkPremiumFeatures(string referrer) : referrer_(std::move(referrer)) { + } +}; + class LinkManager::InternalLinkPrivacyAndSecuritySettings final : public InternalLink { td_api::object_ptr get_internal_link_type_object() const final { return td_api::make_object(); @@ -963,13 +1023,13 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que // resolve?domain=&attach= // resolve?domain=&attach=&startattach= return td::make_unique( - td::make_unique(std::move(username)), url_query.get_arg("attach").str(), + nullptr, td::make_unique(std::move(username)), url_query.get_arg("attach").str(), url_query.get_arg("startattach")); } else if (url_query.has_arg("startattach")) { - // resolve?domain=&startattach - // resolve?domain=&startattach= - return td::make_unique(nullptr, std::move(username), - url_query.get_arg("startattach")); + // resolve?domain=&startattach&choose=users+bots+groups+channels + // resolve?domain=&startattach=&choose=users+bots+groups+channels + return td::make_unique(get_target_chat_chosen(url_query.get_arg("choose")), nullptr, + std::move(username), url_query.get_arg("startattach")); } if (username == "telegrampassport") { // resolve?domain=telegrampassport&bot_id=&scope=&public_key=&nonce= @@ -982,8 +1042,8 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que if (!url_query.get_arg("attach").empty()) { // resolve?phone=&attach= // resolve?phone=&attach=&startattach= - return td::make_unique(std::move(user_link), url_query.get_arg("attach").str(), - url_query.get_arg("startattach")); + return td::make_unique( + nullptr, std::move(user_link), url_query.get_arg("attach").str(), url_query.get_arg("startattach")); } // resolve?phone=12345 return user_link; @@ -1000,6 +1060,9 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que } else if (path.size() == 1 && path[0] == "passport") { // passport?bot_id=&scope=&public_key=&nonce= return get_internal_link_passport(query, url_query.args_); + } else if (path.size() == 1 && path[0] == "premium_offer") { + // premium_offer?ref= + return td::make_unique(get_arg("ref")); } else if (!path.empty() && path[0] == "settings") { if (path.size() == 2 && path[1] == "change_number") { // settings/change_number @@ -1099,6 +1162,11 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que << pass_arg("slug") << copy_arg("mode") << copy_arg("intensity") << copy_arg("bg_color") << copy_arg("rotation")); } + } else if (path.size() == 1 && path[0] == "invoice") { + // invoice?slug= + if (has_arg("slug")) { + return td::make_unique(url_query.get_arg("slug").str()); + } } else if (path.size() == 1 && (path[0] == "share" || path[0] == "msg" || path[0] == "msg_url")) { // msg_url?url= // msg_url?url=&text= @@ -1156,8 +1224,8 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q if (!url_query.get_arg("attach").empty()) { // /+?attach= // /+?attach=&startattach= - return td::make_unique(std::move(user_link), url_query.get_arg("attach").str(), - url_query.get_arg("startattach")); + return td::make_unique( + nullptr, std::move(user_link), url_query.get_arg("attach").str(), url_query.get_arg("startattach")); } // /+ return user_link; @@ -1220,6 +1288,16 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q << url_encode(path[1]) << copy_arg("mode") << copy_arg("intensity") << copy_arg("bg_color") << copy_arg("rotation")); } + } else if (path[0] == "invoice") { + if (path.size() >= 2 && !path[1].empty()) { + // /invoice/ + return td::make_unique(path[1]); + } + } else if (path[0][0] == '$') { + if (path[0].size() >= 2) { + // /$ + return td::make_unique(path[0].substr(1)); + } } else if (path[0] == "share" || path[0] == "msg") { if (!(path.size() > 1 && (path[1] == "bookmarklet" || path[1] == "embed"))) { // /share?url= @@ -1271,13 +1349,14 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q if (!url_query.get_arg("attach").empty()) { // /?attach= // /?attach=&startattach= - return td::make_unique(td::make_unique(std::move(username)), - url_query.get_arg("attach").str(), - url_query.get_arg("startattach")); + return td::make_unique( + nullptr, td::make_unique(std::move(username)), url_query.get_arg("attach").str(), + url_query.get_arg("startattach")); } else if (url_query.has_arg("startattach")) { - // /?startattach - // /?startattach= - return td::make_unique(nullptr, std::move(username), url_query.get_arg("startattach")); + // /?startattach&choose=users+bots+groups+channels + // /?startattach=&choose=users+bots+groups+channels + return td::make_unique(get_target_chat_chosen(url_query.get_arg("choose")), nullptr, + std::move(username), url_query.get_arg("startattach")); } // / diff --git a/td/telegram/LinkManager.h b/td/telegram/LinkManager.h index 9fcf91e16..38d675ac5 100644 --- a/td/telegram/LinkManager.h +++ b/td/telegram/LinkManager.h @@ -96,11 +96,13 @@ class LinkManager final : public Actor { class InternalLinkDialogInvite; class InternalLinkFilterSettings; class InternalLinkGame; + class InternalLinkInvoice; class InternalLinkLanguage; class InternalLinkLanguageSettings; class InternalLinkMessage; class InternalLinkMessageDraft; class InternalLinkPassportDataRequest; + class InternalLinkPremiumFeatures; class InternalLinkPrivacyAndSecuritySettings; class InternalLinkProxy; class InternalLinkPublicDialog; diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 704d7abe9..b4d0d030b 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -14,6 +14,7 @@ #include "td/telegram/CallDiscardReason.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatId.h" +#include "td/telegram/ConfigShared.h" #include "td/telegram/Contact.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" @@ -172,9 +173,10 @@ class MessagePhoto final : public MessageContent { class MessageSticker final : public MessageContent { public: FileId file_id; + bool is_premium = false; MessageSticker() = default; - explicit MessageSticker(FileId file_id) : file_id(file_id) { + MessageSticker(FileId file_id, bool is_premium) : file_id(file_id), is_premium(is_premium) { } MessageContentType get_type() const final { @@ -500,9 +502,11 @@ class MessagePaymentSuccessful final : public MessageContent { MessageId invoice_message_id; string currency; int64 total_amount = 0; + string invoice_payload; // or invoice_slug for users + bool is_recurring = false; + bool is_first_recurring = false; // bots only part - string invoice_payload; string shipping_option_id; unique_ptr order_info; string telegram_payment_charge_id; @@ -510,11 +514,14 @@ class MessagePaymentSuccessful final : public MessageContent { MessagePaymentSuccessful() = default; MessagePaymentSuccessful(DialogId invoice_dialog_id, MessageId invoice_message_id, string &¤cy, - int64 total_amount) + int64 total_amount, string &&invoice_payload, bool is_recurring, bool is_first_recurring) : invoice_dialog_id(invoice_dialog_id) , invoice_message_id(invoice_message_id) , currency(std::move(currency)) - , total_amount(total_amount) { + , total_amount(total_amount) + , invoice_payload(std::move(invoice_payload)) + , is_recurring(is_recurring || is_first_recurring) + , is_first_recurring(is_first_recurring) { } MessageContentType get_type() const final { @@ -843,6 +850,9 @@ static void store(const MessageContent *content, StorerT &storer) { case MessageContentType::Sticker: { const auto *m = static_cast(content); td->stickers_manager_->store_sticker(m->file_id, false, storer, "MessageSticker"); + BEGIN_STORE_FLAGS(); + STORE_FLAG(m->is_premium); + END_STORE_FLAGS(); break; } case MessageContentType::Text: { @@ -980,6 +990,8 @@ static void store(const MessageContent *content, StorerT &storer) { STORE_FLAG(has_invoice_message_id); STORE_FLAG(is_correctly_stored); STORE_FLAG(has_invoice_dialog_id); + STORE_FLAG(m->is_recurring); + STORE_FLAG(m->is_first_recurring); END_STORE_FLAGS(); store(m->currency, storer); store(m->total_amount, storer); @@ -1203,6 +1215,11 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageContentType::Sticker: { auto m = make_unique(); m->file_id = td->stickers_manager_->parse_sticker(false, parser); + if (parser.version() >= static_cast(Version::AddMessageStickerFlags)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(m->is_premium); + END_PARSE_FLAGS(); + } is_bad = !m->file_id.is_valid(); content = std::move(m); break; @@ -1383,6 +1400,8 @@ static void parse(unique_ptr &content, ParserT &parser) { PARSE_FLAG(has_invoice_message_id); PARSE_FLAG(is_correctly_stored); PARSE_FLAG(has_invoice_dialog_id); + PARSE_FLAG(m->is_recurring); + PARSE_FLAG(m->is_first_recurring); END_PARSE_FLAGS(); parse(m->currency, parser); parse(m->total_amount, parser); @@ -1645,7 +1664,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, } else if (allowed_media_content_id == td_api::inputMessagePhoto::ID) { result.message_content = make_unique(std::move(*photo), std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageSticker::ID) { - result.message_content = make_unique(file_id); + result.message_content = make_unique(file_id, false); } else if (allowed_media_content_id == td_api::inputMessageVideo::ID) { result.message_content = make_unique(file_id, std::move(caption)); } else if (allowed_media_content_id == td_api::inputMessageVoiceNote::ID) { @@ -1683,7 +1702,7 @@ unique_ptr create_chat_set_ttl_message_content(int32 ttl) { static Result create_input_message_content( DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td, - FormattedText caption, FileId file_id, PhotoSize thumbnail, vector sticker_file_ids) { + FormattedText caption, FileId file_id, PhotoSize thumbnail, vector sticker_file_ids, bool is_premium) { CHECK(input_message_content != nullptr); LOG(INFO) << "Create InputMessageContent with file " << file_id << " and thumbnail " << thumbnail.file_id; @@ -1801,7 +1820,11 @@ static Result create_input_message_content( PhotoSize s; s.type = type; s.dimensions = get_dimensions(input_photo->width_, input_photo->height_, nullptr); - s.size = static_cast(file_view.size()); + auto size = file_view.size(); + if (size < 0 || size >= 1000000000) { + return Status::Error(400, "Wrong photo size"); + } + s.size = static_cast(size); s.file_id = file_id; if (thumbnail.file_id.is_valid()) { @@ -1823,11 +1846,11 @@ static Result create_input_message_content( emoji = std::move(input_sticker->emoji_); - td->stickers_manager_->create_sticker(file_id, string(), thumbnail, + td->stickers_manager_->create_sticker(file_id, FileId(), string(), thumbnail, get_dimensions(input_sticker->width_, input_sticker->height_, nullptr), nullptr, StickerFormat::Unknown, nullptr); - content = make_unique(file_id); + content = make_unique(file_id, is_premium); break; } case td_api::inputMessageVideo::ID: { @@ -1990,7 +2013,7 @@ static Result create_input_message_content( } Result get_input_message_content( - DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td) { + DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td, bool is_premium) { bool is_secret = dialog_id.get_type() == DialogType::SecretChat; LOG(INFO) << "Get input message content from " << to_string(input_message_content); @@ -2103,7 +2126,7 @@ Result get_input_message_content( TRY_RESULT(caption, process_input_caption(td->contacts_manager_.get(), dialog_id, extract_input_caption(input_message_content), td->auth_manager_->is_bot())); return create_input_message_content(dialog_id, std::move(input_message_content), td, std::move(caption), file_id, - std::move(thumbnail), std::move(sticker_file_ids)); + std::move(thumbnail), std::move(sticker_file_ids), is_premium); } bool can_have_input_media(const Td *td, const MessageContent *content, bool is_server) { @@ -2167,17 +2190,17 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, tl_object_ptr input_file, - BufferSlice thumbnail) { + BufferSlice thumbnail, int32 layer) { switch (content->get_type()) { case MessageContentType::Animation: { const auto *m = static_cast(content); return td->animations_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, - std::move(thumbnail)); + std::move(thumbnail), layer); } case MessageContentType::Audio: { const auto *m = static_cast(content); return td->audios_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, - std::move(thumbnail)); + std::move(thumbnail), layer); } case MessageContentType::Contact: { const auto *m = static_cast(content); @@ -2186,7 +2209,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::Document: { const auto *m = static_cast(content); return td->documents_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, - std::move(thumbnail)); + std::move(thumbnail), layer); } case MessageContentType::Location: { const auto *m = static_cast(content); @@ -2199,7 +2222,8 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, } case MessageContentType::Sticker: { const auto *m = static_cast(content); - return td->stickers_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail)); + return td->stickers_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail), + layer); } case MessageContentType::Text: { CHECK(input_file == nullptr); @@ -2214,15 +2238,17 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::Video: { const auto *m = static_cast(content); return td->videos_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, - std::move(thumbnail)); + std::move(thumbnail), layer); } case MessageContentType::VideoNote: { const auto *m = static_cast(content); - return td->video_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail)); + return td->video_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail), + layer); } case MessageContentType::VoiceNote: { const auto *m = static_cast(content); - return td->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text); + return td->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text, + layer); } case MessageContentType::Call: case MessageContentType::Dice: @@ -3078,7 +3104,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Animation: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->animations_manager_->merge_animations(new_->file_id, old_->file_id, false); } @@ -3092,7 +3118,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Audio: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->audios_manager_->merge_audios(new_->file_id, old_->file_id, false); } @@ -3114,7 +3140,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Document: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->documents_manager_->merge_documents(new_->file_id, old_->file_id, false); } @@ -3252,12 +3278,15 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Sticker: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->stickers_manager_->merge_stickers(new_->file_id, old_->file_id, false); } need_update = true; } + if (old_->is_premium != new_->is_premium) { + need_update = true; + } break; } case MessageContentType::Venue: { @@ -3275,7 +3304,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Video: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->videos_manager_->merge_videos(new_->file_id, old_->file_id, false); } @@ -3289,7 +3318,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::VideoNote: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->video_notes_manager_->merge_video_notes(new_->file_id, old_->file_id, false); } @@ -3303,7 +3332,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::VoiceNote: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (new_->file_id != old_->file_id) { + if (old_->file_id != new_->file_id) { if (need_merge_files) { td->voice_notes_manager_->merge_voice_notes(new_->file_id, old_->file_id, false); } @@ -3438,7 +3467,8 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo old_->telegram_payment_charge_id != new_->telegram_payment_charge_id || old_->provider_payment_charge_id != new_->provider_payment_charge_id || ((old_->order_info != nullptr || new_->order_info != nullptr) && - (old_->order_info == nullptr || new_->order_info == nullptr || *old_->order_info != *new_->order_info))) { + (old_->order_info == nullptr || new_->order_info == nullptr || *old_->order_info != *new_->order_info || + old_->is_recurring != new_->is_recurring || old_->is_first_recurring != new_->is_first_recurring))) { need_update = true; } break; @@ -3716,6 +3746,9 @@ void register_message_content(Td *td, const MessageContent *content, FullMessage } return; } + case MessageContentType::VoiceNote: + return td->voice_notes_manager_->register_voice_note(static_cast(content)->file_id, + full_message_id, source); case MessageContentType::Poll: return td->poll_manager_->register_poll(static_cast(content)->poll_id, full_message_id, source); @@ -3744,6 +3777,12 @@ void reregister_message_content(Td *td, const MessageContent *old_content, const } break; } + case MessageContentType::VoiceNote: + if (static_cast(old_content)->file_id == + static_cast(new_content)->file_id) { + return; + } + break; case MessageContentType::Poll: if (static_cast(old_content)->poll_id == static_cast(new_content)->poll_id) { @@ -3778,6 +3817,9 @@ void unregister_message_content(Td *td, const MessageContent *content, FullMessa } return; } + case MessageContentType::VoiceNote: + return td->voice_notes_manager_->unregister_voice_note(static_cast(content)->file_id, + full_message_id, source); case MessageContentType::Poll: return td->poll_manager_->unregister_poll(static_cast(content)->poll_id, full_message_id, source); @@ -3952,7 +3994,7 @@ static tl_object_ptr secret_to_telegram(FromT &from) { } static unique_ptr get_document_message_content(Document &&parsed_document, FormattedText &&caption, - bool is_opened) { + bool is_opened, bool is_premium) { auto file_id = parsed_document.file_id; if (!parsed_document.empty()) { CHECK(file_id.is_valid()); @@ -3965,7 +4007,7 @@ static unique_ptr get_document_message_content(Document &&parsed case Document::Type::General: return make_unique(file_id, std::move(caption)); case Document::Type::Sticker: - return make_unique(file_id); + return make_unique(file_id, is_premium); case Document::Type::Unknown: return make_unique(); case Document::Type::Video: @@ -3982,18 +4024,18 @@ static unique_ptr get_document_message_content(Document &&parsed static unique_ptr get_document_message_content(Td *td, tl_object_ptr &&document, DialogId owner_dialog_id, FormattedText &&caption, - bool is_opened, + bool is_opened, bool is_premium, MultiPromiseActor *load_data_multipromise_ptr) { return get_document_message_content( td->documents_manager_->on_get_document(std::move(document), owner_dialog_id, load_data_multipromise_ptr), - std::move(caption), is_opened); + std::move(caption), is_opened, is_premium); } unique_ptr get_secret_message_content( Td *td, string message_text, unique_ptr file, tl_object_ptr &&media, vector> &&secret_entities, DialogId owner_dialog_id, - MultiPromiseActor &load_data_multipromise) { + MultiPromiseActor &load_data_multipromise, bool is_premium) { int32 constructor_id = media == nullptr ? secret_api::decryptedMessageMediaEmpty::ID : media->get_id(); auto caption = [&] { switch (constructor_id) { @@ -4005,6 +4047,10 @@ unique_ptr get_secret_message_content( auto photo = static_cast(media.get()); return std::move(photo->caption_); } + case secret_api::decryptedMessageMediaDocument46::ID: { + auto document = static_cast(media.get()); + return std::move(document->caption_); + } case secret_api::decryptedMessageMediaDocument::ID: { auto document = static_cast(media.get()); return std::move(document->caption_); @@ -4036,9 +4082,18 @@ unique_ptr get_secret_message_content( // support of old layer and old constructions switch (constructor_id) { + case secret_api::decryptedMessageMediaDocument46::ID: { + auto document = move_tl_object_as(media); + media = make_tl_object( + std::move(document->thumb_), document->thumb_w_, document->thumb_h_, document->mime_type_, document->size_, + std::move(document->key_), std::move(document->iv_), std::move(document->attributes_), string()); + + constructor_id = secret_api::decryptedMessageMediaDocument::ID; + break; + } case secret_api::decryptedMessageMediaVideo::ID: { auto video = move_tl_object_as(media); - std::vector> attributes; + vector> attributes; attributes.emplace_back( make_tl_object(video->duration_, video->w_, video->h_)); media = make_tl_object( @@ -4142,7 +4197,7 @@ unique_ptr get_secret_message_content( auto document = secret_to_telegram_document(*external_document); return get_document_message_content(td, std::move(document), owner_dialog_id, FormattedText{std::move(message_text), std::move(entities)}, false, - &load_data_multipromise); + is_premium, &load_data_multipromise); } default: break; @@ -4181,7 +4236,8 @@ unique_ptr get_secret_message_content( message_document->attributes_.clear(); auto document = td->documents_manager_->on_get_document( {std::move(file), std::move(message_document), std::move(attributes)}, owner_dialog_id); - return get_document_message_content(std::move(document), {std::move(message_text), std::move(entities)}, false); + return get_document_message_content(std::move(document), {std::move(message_text), std::move(entities)}, false, + false); } default: LOG(ERROR) << "Unsupported: " << to_string(media); @@ -4315,7 +4371,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, *ttl = message_document->ttl_seconds_; } return get_document_message_content(td, move_tl_object_as(document_ptr), owner_dialog_id, - std::move(message), is_content_read, nullptr); + std::move(message), is_content_read, !message_document->nopremium_, nullptr); } case telegram_api::messageMediaGame::ID: { auto message_game = move_tl_object_as(media); @@ -4527,6 +4583,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const } case MessageContentType::Sticker: { auto result = make_unique(*static_cast(content)); + result->is_premium = G()->shared_config().get_option_boolean("is_premium"); if (td->stickers_manager_->has_input_media(result->file_id, to_secret)) { return std::move(result); } @@ -4754,13 +4811,17 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(action); if (!reply_to_message_id.is_valid()) { - LOG(ERROR) << "Receive succesful payment message with " << reply_to_message_id << " in " << owner_dialog_id; + if (reply_to_message_id != MessageId()) { + LOG(ERROR) << "Receive succesful payment message with " << reply_to_message_id << " in " << owner_dialog_id; + } + reply_in_dialog_id = DialogId(); reply_to_message_id = MessageId(); } - auto payment_sent = move_tl_object_as(action); - return td::make_unique(reply_in_dialog_id, reply_to_message_id, - std::move(payment_sent->currency_), payment_sent->total_amount_); + return td::make_unique( + reply_in_dialog_id, reply_to_message_id, std::move(payment_sent->currency_), payment_sent->total_amount_, + std::move(payment_sent->invoice_slug_), payment_sent->recurring_used_, payment_sent->recurring_init_); } case telegram_api::messageActionPaymentSentMe::ID: { if (!td->auth_manager_->is_bot()) { @@ -4769,8 +4830,8 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(action); auto result = td::make_unique( - DialogId(), MessageId(), std::move(payment_sent->currency_), payment_sent->total_amount_); - result->invoice_payload = payment_sent->payload_.as_slice().str(); + DialogId(), MessageId(), std::move(payment_sent->currency_), payment_sent->total_amount_, + payment_sent->payload_.as_slice().str(), payment_sent->recurring_used_, payment_sent->recurring_init_); result->shipping_option_id = std::move(payment_sent->shipping_option_id_); result->order_info = get_order_info(std::move(payment_sent->info_)); result->telegram_payment_charge_id = std::move(payment_sent->charge_->id_); @@ -4952,7 +5013,10 @@ tl_object_ptr get_message_content_object(const MessageCo } case MessageContentType::Sticker: { const auto *m = static_cast(content); - return make_tl_object(td->stickers_manager_->get_sticker_object(m->file_id)); + auto sticker = td->stickers_manager_->get_sticker_object(m->file_id); + CHECK(sticker != nullptr); + auto is_premium = m->is_premium && sticker->premium_animation_ != nullptr; + return make_tl_object(std::move(sticker), is_premium); } case MessageContentType::Text: { const auto *m = static_cast(content); @@ -5066,12 +5130,14 @@ tl_object_ptr get_message_content_object(const MessageCo const auto *m = static_cast(content); if (td->auth_manager_->is_bot()) { return make_tl_object( - m->currency, m->total_amount, m->invoice_payload, m->shipping_option_id, - get_order_info_object(m->order_info), m->telegram_payment_charge_id, m->provider_payment_charge_id); + m->currency, m->total_amount, m->is_recurring, m->is_first_recurring, m->invoice_payload, + m->shipping_option_id, get_order_info_object(m->order_info), m->telegram_payment_charge_id, + m->provider_payment_charge_id); } else { auto invoice_dialog_id = m->invoice_dialog_id.is_valid() ? m->invoice_dialog_id : dialog_id; return make_tl_object(invoice_dialog_id.get(), m->invoice_message_id.get(), - m->currency, m->total_amount); + m->currency, m->total_amount, m->is_recurring, + m->is_first_recurring, m->invoice_payload); } } case MessageContentType::ContactRegistered: diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index 0626a37ee..9a65153ea 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -30,7 +30,6 @@ #include "td/utils/buffer.h" #include "td/utils/common.h" -#include "td/utils/Slice.h" #include "td/utils/Status.h" #include @@ -103,13 +102,13 @@ unique_ptr create_screenshot_taken_message_content(); unique_ptr create_chat_set_ttl_message_content(int32 ttl); Result get_input_message_content( - DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td); + DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td, bool is_premium); bool can_have_input_media(const Td *td, const MessageContent *content, bool is_server); SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, tl_object_ptr input_file, - BufferSlice thumbnail); + BufferSlice thumbnail, int32 layer); tl_object_ptr get_input_media(const MessageContent *content, Td *td, tl_object_ptr input_file, @@ -185,7 +184,7 @@ unique_ptr get_secret_message_content( Td *td, string message_text, unique_ptr file, tl_object_ptr &&media, vector> &&secret_entities, DialogId owner_dialog_id, - MultiPromiseActor &load_data_multipromise); + MultiPromiseActor &load_data_multipromise, bool is_premium); unique_ptr get_message_content(Td *td, FormattedText message_text, tl_object_ptr &&media, diff --git a/td/telegram/MessageEntity.cpp b/td/telegram/MessageEntity.cpp index e0561b8e7..b569f6112 100644 --- a/td/telegram/MessageEntity.cpp +++ b/td/telegram/MessageEntity.cpp @@ -29,9 +29,14 @@ namespace td { int MessageEntity::get_type_priority(Type type) { - static const int types[] = {50, 50, 50, 50, 50, 90, 91, 20, 11, 10, 49, 49, 50, 50, 92, 93, 0, 50, 50, 94}; - static_assert(sizeof(types) / sizeof(types[0]) == static_cast(MessageEntity::Type::Size), ""); - return types[static_cast(type)]; + static const int priorities[] = { + 50 /*Mention*/, 50 /*Hashtag*/, 50 /*BotCommand*/, 50 /*Url*/, + 50 /*EmailAddress*/, 90 /*Bold*/, 91 /*Italic*/, 20 /*Code*/, + 11 /*Pre*/, 10 /*PreCode*/, 49 /*TextUrl*/, 49 /*MentionName*/, + 50 /*Cashtag*/, 50 /*PhoneNumber*/, 92 /*Underline*/, 93 /*Strikethrough*/, + 0 /*BlockQuote*/, 50 /*BankCardNumber*/, 50 /*MediaTimestamp*/, 94 /*Spoiler*/}; + static_assert(sizeof(priorities) / sizeof(priorities[0]) == static_cast(MessageEntity::Type::Size), ""); + return priorities[static_cast(type)]; } StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Type &message_entity_type) { @@ -1631,7 +1636,7 @@ static void fix_entity_offsets(Slice text, vector &entities) { } } -vector find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps) { +vector find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps, bool skip_urls) { vector entities; auto add_entities = [&entities, &text](MessageEntity::Type type, vector (*find_entities_f)(Slice)) mutable { @@ -1650,16 +1655,16 @@ vector find_entities(Slice text, bool skip_bot_commands, bool ski add_entities(MessageEntity::Type::Cashtag, find_cashtags); // TODO find_phone_numbers add_entities(MessageEntity::Type::BankCardNumber, find_bank_card_numbers); - add_entities(MessageEntity::Type::Url, find_tg_urls); - - auto urls = find_urls(text); - for (auto &url : urls) { - auto type = url.second ? MessageEntity::Type::EmailAddress : MessageEntity::Type::Url; - auto offset = narrow_cast(url.first.begin() - text.begin()); - auto length = narrow_cast(url.first.size()); - entities.emplace_back(type, offset, length); + if (!skip_urls) { + add_entities(MessageEntity::Type::Url, find_tg_urls); + auto urls = find_urls(text); + for (auto &url : urls) { + auto type = url.second ? MessageEntity::Type::EmailAddress : MessageEntity::Type::Url; + auto offset = narrow_cast(url.first.begin() - text.begin()); + auto length = narrow_cast(url.first.size()); + entities.emplace_back(type, offset, length); + } } - if (!skip_media_timestamps) { auto media_timestamps = find_media_timestamps(text); for (auto &entity : media_timestamps) { diff --git a/td/telegram/MessageEntity.h b/td/telegram/MessageEntity.h index 4a0ec6e29..88600c26a 100644 --- a/td/telegram/MessageEntity.h +++ b/td/telegram/MessageEntity.h @@ -143,7 +143,8 @@ vector> get_text_entities_object(const vector< td_api::object_ptr get_formatted_text_object(const FormattedText &text, bool skip_bot_commands, int32 max_media_timestamp); -vector find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps); +vector find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps, + bool skip_urls = false); vector find_mentions(Slice str); vector find_bot_commands(Slice str); diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 714086a9c..674406c25 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -94,10 +94,10 @@ namespace td { class GetDialogFiltersQuery final : public Td::ResultHandler { - Promise>> promise_; + Promise>> promise_; public: - explicit GetDialogFiltersQuery(Promise>> &&promise) + explicit GetDialogFiltersQuery(Promise>> &&promise) : promise_(std::move(promise)) { } @@ -126,7 +126,7 @@ class UpdateDialogFilterQuery final : public Td::ResultHandler { explicit UpdateDialogFilterQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(DialogFilterId dialog_filter_id, tl_object_ptr filter) { + void send(DialogFilterId dialog_filter_id, tl_object_ptr filter) { int32 flags = 0; if (filter != nullptr) { flags |= telegram_api::messages_updateDialogFilter::FILTER_MASK; @@ -158,9 +158,12 @@ class UpdateDialogFiltersOrderQuery final : public Td::ResultHandler { explicit UpdateDialogFiltersOrderQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(const vector &dialog_filter_ids) { - send_query(G()->net_query_creator().create(telegram_api::messages_updateDialogFiltersOrder( - transform(dialog_filter_ids, [](auto dialog_filter_id) { return dialog_filter_id.get(); })))); + void send(const vector &dialog_filter_ids, int32 main_dialog_list_position) { + auto filter_ids = transform(dialog_filter_ids, [](auto dialog_filter_id) { return dialog_filter_id.get(); }); + CHECK(0 <= main_dialog_list_position); + CHECK(main_dialog_list_position <= static_cast(filter_ids.size())); + filter_ids.insert(filter_ids.begin() + main_dialog_list_position, 0); + send_query(G()->net_query_creator().create(telegram_api::messages_updateDialogFiltersOrder(std::move(filter_ids)))); } void on_result(BufferSlice packet) final { @@ -4645,7 +4648,7 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_edit_date = edit_date > 0; bool has_random_id = random_id != 0; bool is_forwarded = forward_info != nullptr; - bool is_reply = reply_to_message_id.is_valid(); + bool is_reply = reply_to_message_id.is_valid() || reply_to_message_id.is_valid_scheduled(); bool is_reply_to_random_id = reply_to_random_id != 0; bool is_via_bot = via_bot_user_id.is_valid(); bool has_view_count = view_count > 0; @@ -8278,27 +8281,34 @@ void MessagesManager::hide_dialog_message_reactions(Dialog *d) { } } -void MessagesManager::set_active_reactions(vector active_reactions) { +void MessagesManager::set_active_reactions(vector active_reactions) { if (active_reactions == active_reactions_) { return; } auto old_active_reactions = std::move(active_reactions_); active_reactions_ = std::move(active_reactions); + active_reaction_pos_.clear(); + bool is_changed = old_active_reactions.size() != active_reactions_.size(); for (size_t i = 0; i < active_reactions_.size(); i++) { - active_reaction_pos_[active_reactions_[i]] = i; + active_reaction_pos_[active_reactions_[i].reaction_] = i; + if (!is_changed && active_reactions_[i].reaction_ != old_active_reactions[i].reaction_) { + is_changed = true; + } } for (auto &dialog : dialogs_) { Dialog *d = dialog.second.get(); switch (d->dialog_id.get_type()) { case DialogType::User: - send_update_chat_available_reactions(d); + if (is_changed) { + send_update_chat_available_reactions(d); + } break; case DialogType::Chat: case DialogType::Channel: { - auto old_reactions = get_active_reactions(d->available_reactions, old_active_reactions); - auto new_reactions = get_active_reactions(d->available_reactions, active_reactions_); + auto old_reactions = ::td::get_active_reactions(d->available_reactions, old_active_reactions); + auto new_reactions = ::td::get_active_reactions(d->available_reactions, active_reactions_); if (old_reactions != new_reactions) { if (old_reactions.empty() != new_reactions.empty()) { if (!old_reactions.empty()) { @@ -8321,29 +8331,14 @@ void MessagesManager::set_active_reactions(vector active_reactions) { } vector MessagesManager::get_active_reactions(const vector &available_reactions) const { - return get_active_reactions(available_reactions, active_reactions_); -} - -vector MessagesManager::get_active_reactions(const vector &available_reactions, - const vector &active_reactions) { - if (available_reactions.empty() || available_reactions == active_reactions) { - // fast path - return available_reactions; - } - vector result; - for (const auto &active_reaction : active_reactions) { - if (td::contains(available_reactions, active_reaction)) { - result.push_back(active_reaction); - } - } - return result; + return ::td::get_active_reactions(available_reactions, active_reactions_); } vector MessagesManager::get_dialog_active_reactions(const Dialog *d) const { CHECK(d != nullptr); switch (d->dialog_id.get_type()) { case DialogType::User: - return active_reactions_; + return transform(active_reactions_, [](auto &reaction) { return reaction.reaction_; }); case DialogType::Chat: case DialogType::Channel: return get_active_reactions(d->available_reactions); @@ -9044,10 +9039,11 @@ void MessagesManager::do_send_secret_media(DialogId dialog_id, Message *m, FileI LOG(INFO) << "Do send secret media file " << file_id << " with thumbnail " << thumbnail_file_id << ", have_input_file = " << have_input_file; + auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); on_secret_message_media_uploaded( dialog_id, m, - get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail)), file_id, - thumbnail_file_id); + get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail), layer), + file_id, thumbnail_file_id); } void MessagesManager::on_upload_media_error(FileId file_id, Status status) { @@ -10884,7 +10880,11 @@ void MessagesManager::erase_delete_messages_log_event(uint64 log_event_id) { } } -void MessagesManager::delete_sent_message_on_server(DialogId dialog_id, MessageId message_id) { +void MessagesManager::delete_sent_message_on_server(DialogId dialog_id, MessageId message_id, + MessageId old_message_id) { + // this would be a no-op, because replies have already been removed in cancel_send_message_query + // update_reply_to_message_id(dialog_id, old_message_id, message_id, false, "delete_sent_message_on_server"); + // being sent message was deleted by the user or is in an inaccessible channel // don't need to send an update to the user, because the message has already been deleted if (!have_input_peer(dialog_id, AccessRights::Read)) { @@ -13132,6 +13132,8 @@ void MessagesManager::loop() { class MessagesManager::DialogFiltersLogEvent { public: + int32 server_main_dialog_list_position = 0; + int32 main_dialog_list_position = 0; int32 updated_date = 0; const vector> *server_dialog_filters_in; const vector> *dialog_filters_in; @@ -13140,16 +13142,58 @@ class MessagesManager::DialogFiltersLogEvent { template void store(StorerT &storer) const { + bool has_server_dialog_filters = !server_dialog_filters_in->empty(); + bool has_dialog_filters = !dialog_filters_in->empty(); + bool has_server_main_dialog_list_position = server_main_dialog_list_position != 0; + bool has_main_dialog_list_position = main_dialog_list_position != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_server_dialog_filters); + STORE_FLAG(has_dialog_filters); + STORE_FLAG(has_server_main_dialog_list_position); + STORE_FLAG(has_main_dialog_list_position); + END_STORE_FLAGS(); td::store(updated_date, storer); - td::store(*server_dialog_filters_in, storer); - td::store(*dialog_filters_in, storer); + if (has_server_dialog_filters) { + td::store(*server_dialog_filters_in, storer); + } + if (has_dialog_filters) { + td::store(*dialog_filters_in, storer); + } + if (has_server_main_dialog_list_position) { + td::store(server_main_dialog_list_position, storer); + } + if (has_main_dialog_list_position) { + td::store(main_dialog_list_position, storer); + } } template void parse(ParserT &parser) { + bool has_server_dialog_filters = true; + bool has_dialog_filters = true; + bool has_server_main_dialog_list_position = false; + bool has_main_dialog_list_position = false; + if (parser.version() >= static_cast(Version::AddMainDialogListPosition)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_server_dialog_filters); + PARSE_FLAG(has_dialog_filters); + PARSE_FLAG(has_server_main_dialog_list_position); + PARSE_FLAG(has_main_dialog_list_position); + END_PARSE_FLAGS(); + } td::parse(updated_date, parser); - td::parse(server_dialog_filters_out, parser); - td::parse(dialog_filters_out, parser); + if (has_server_dialog_filters) { + td::parse(server_dialog_filters_out, parser); + } + if (has_dialog_filters) { + td::parse(dialog_filters_out, parser); + } + if (has_server_main_dialog_list_position) { + td::parse(server_main_dialog_list_position, parser); + } + if (has_main_dialog_list_position) { + td::parse(main_dialog_list_position, parser); + } } }; @@ -13207,6 +13251,16 @@ void MessagesManager::init() { if (!dialog_filters.empty()) { DialogFiltersLogEvent log_event; if (log_event_parse(log_event, dialog_filters).is_ok()) { + server_main_dialog_list_position_ = log_event.server_main_dialog_list_position; + main_dialog_list_position_ = log_event.main_dialog_list_position; + if (!G()->shared_config().get_option_boolean("is_premium") && + (server_main_dialog_list_position_ != 0 || main_dialog_list_position_ != 0)) { + LOG(INFO) << "Ignore main chat list position " << server_main_dialog_list_position_ << '/' + << main_dialog_list_position_; + server_main_dialog_list_position_ = 0; + main_dialog_list_position_ = 0; + } + dialog_filters_updated_date_ = G()->ignore_background_updates() ? 0 : log_event.updated_date; std::unordered_set server_dialog_filter_ids; for (auto &dialog_filter : log_event.server_dialog_filters_out) { @@ -13952,7 +14006,8 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId message_info.flags = flags; message_info.content = get_secret_message_content( td_, std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_), - message_info.dialog_id, pending_secret_message->load_data_multipromise); + message_info.dialog_id, pending_secret_message->load_data_multipromise, + td_->contacts_manager_->is_user_premium(user_id)); add_secret_message(std::move(pending_secret_message), std::move(lock_promise)); } @@ -14207,7 +14262,7 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( DialogId reply_in_dialog_id; MessageId reply_to_message_id; - if (message->reply_to_ != nullptr) { + if (message->reply_to_ != nullptr && !message->reply_to_->reply_to_scheduled_) { reply_to_message_id = MessageId(ServerMessageId(message->reply_to_->reply_to_msg_id_)); auto reply_to_peer_id = std::move(message->reply_to_->reply_to_peer_id_); if (reply_to_peer_id != nullptr) { @@ -14314,10 +14369,10 @@ std::pair> MessagesManager::creat is_outgoing = supposed_to_be_outgoing; /* - // it is useless to call getChannelsDifference, because the channel pts will be increased already + // it is useless to call getChannelDifference, because the channel pts will be increased already if (dialog_type == DialogType::Channel && !running_get_difference_ && !running_get_channel_difference(dialog_id) && get_channel_difference_to_log_event_id_.count(dialog_id) == 0) { - // it is safer to completely ignore the message and re-get it through getChannelsDifference + // it is safer to completely ignore the message and re-get it through getChannelDifference Dialog *d = get_dialog(dialog_id); if (d != nullptr) { channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001); @@ -14327,29 +14382,52 @@ std::pair> MessagesManager::creat */ } + int32 date = message_info.date; + if (date <= 0) { + LOG(ERROR) << "Wrong date = " << date << " received in " << message_id << " in " << dialog_id; + date = 1; + } + MessageId reply_to_message_id = message_info.reply_to_message_id; // for secret messages DialogId reply_in_dialog_id; MessageId top_thread_message_id; if (message_info.reply_header != nullptr) { - reply_to_message_id = MessageId(ServerMessageId(message_info.reply_header->reply_to_msg_id_)); - auto reply_to_peer_id = std::move(message_info.reply_header->reply_to_peer_id_); - if (reply_to_peer_id != nullptr) { - reply_in_dialog_id = DialogId(reply_to_peer_id); - if (!reply_in_dialog_id.is_valid()) { - LOG(ERROR) << "Receive reply in invalid " << to_string(reply_to_peer_id); + if (message_info.reply_header->reply_to_scheduled_) { + reply_to_message_id = MessageId(ScheduledServerMessageId(message_info.reply_header->reply_to_msg_id_), date); + if (message_id.is_scheduled()) { + auto reply_to_peer_id = std::move(message_info.reply_header->reply_to_peer_id_); + if (reply_to_peer_id != nullptr) { + reply_in_dialog_id = DialogId(reply_to_peer_id); + LOG(ERROR) << "Receive reply to " << FullMessageId{reply_in_dialog_id, reply_to_message_id} << " in " + << FullMessageId{dialog_id, message_id}; + reply_to_message_id = MessageId(); + reply_in_dialog_id = DialogId(); + } + } else { + LOG(ERROR) << "Receive reply to " << reply_to_message_id << " in " << FullMessageId{dialog_id, message_id}; reply_to_message_id = MessageId(); - reply_in_dialog_id = DialogId(); } - if (reply_in_dialog_id == dialog_id) { - reply_in_dialog_id = DialogId(); // just in case + } else { + reply_to_message_id = MessageId(ServerMessageId(message_info.reply_header->reply_to_msg_id_)); + auto reply_to_peer_id = std::move(message_info.reply_header->reply_to_peer_id_); + if (reply_to_peer_id != nullptr) { + reply_in_dialog_id = DialogId(reply_to_peer_id); + if (!reply_in_dialog_id.is_valid()) { + LOG(ERROR) << "Receive reply in invalid " << to_string(reply_to_peer_id); + reply_to_message_id = MessageId(); + reply_in_dialog_id = DialogId(); + } + if (reply_in_dialog_id == dialog_id) { + reply_in_dialog_id = DialogId(); // just in case + } } - } - if (reply_to_message_id.is_valid() && !td_->auth_manager_->is_bot() && !message_id.is_scheduled() && - !reply_in_dialog_id.is_valid()) { - if ((message_info.reply_header->flags_ & telegram_api::messageReplyHeader::REPLY_TO_TOP_ID_MASK) != 0) { - top_thread_message_id = MessageId(ServerMessageId(message_info.reply_header->reply_to_top_id_)); - } else if (!is_broadcast_channel(dialog_id)) { - top_thread_message_id = reply_to_message_id; + if (reply_to_message_id.is_valid() && !td_->auth_manager_->is_bot() && !message_id.is_scheduled() && + !reply_in_dialog_id.is_valid()) { + if ((message_info.reply_header->flags_ & telegram_api::messageReplyHeader::REPLY_TO_TOP_ID_MASK) != 0) { + top_thread_message_id = MessageId(ServerMessageId(message_info.reply_header->reply_to_top_id_)); + } else if (!is_broadcast_channel(dialog_id)) { + top_thread_message_id = reply_to_message_id; + } } } } @@ -14361,12 +14439,6 @@ std::pair> MessagesManager::creat via_bot_user_id = UserId(); } - int32 date = message_info.date; - if (date <= 0) { - LOG(ERROR) << "Wrong date = " << date << " received in " << message_id << " in " << dialog_id; - date = 1; - } - int32 edit_date = message_info.edit_date; if (edit_date < 0) { LOG(ERROR) << "Wrong edit_date = " << edit_date << " received in " << message_id << " in " << dialog_id; @@ -14602,13 +14674,13 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f } // must be called before delete_message - update_reply_to_message_id(dialog_id, old_message_id, message_id); + update_reply_to_message_id(dialog_id, old_message_id, message_id, true, "on_get_message"); being_readded_message_id_ = {dialog_id, old_message_id}; unique_ptr old_message = delete_message(d, old_message_id, false, &need_update_dialog_pos, "add sent message"); if (old_message == nullptr) { - delete_sent_message_on_server(dialog_id, message_id); + delete_sent_message_on_server(dialog_id, message_id, old_message_id); being_readded_message_id_ = FullMessageId(); return FullMessageId(); } @@ -15815,7 +15887,7 @@ void MessagesManager::add_random_id_to_message_id_correspondence(Dialog *d, int6 CHECK(d != nullptr); CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent()); auto it = d->random_id_to_message_id.find(random_id); - if (it == d->random_id_to_message_id.end() || it->second < message_id) { + if (it == d->random_id_to_message_id.end() || it->second.get() < message_id.get()) { LOG(INFO) << "Add correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id; d->random_id_to_message_id[random_id] = message_id; } @@ -16366,6 +16438,9 @@ unique_ptr MessagesManager::do_delete_scheduled_messag unregister_message_content(td_, result->content.get(), {d->dialog_id, message_id}, "do_delete_scheduled_message"); unregister_message_reply(d->dialog_id, m); + if (message_id.is_yet_unsent()) { + delete_random_id_to_message_id_correspondence(d, result->random_id, result->message_id); + } return result; } @@ -17160,13 +17235,13 @@ void MessagesManager::reload_dialog_filters() { are_dialog_filters_being_reloaded_ = true; need_dialog_filters_reload_ = false; auto promise = PromiseCreator::lambda( - [actor_id = actor_id(this)](Result>> r_filters) { + [actor_id = actor_id(this)](Result>> r_filters) { send_closure(actor_id, &MessagesManager::on_get_dialog_filters, std::move(r_filters), false); }); td_->create_handler(std::move(promise))->send(); } -void MessagesManager::on_get_dialog_filters(Result>> r_filters, +void MessagesManager::on_get_dialog_filters(Result>> r_filters, bool dummy) { are_dialog_filters_being_reloaded_ = false; if (G()->close_flag()) { @@ -17176,8 +17251,10 @@ void MessagesManager::on_get_dialog_filters(Resultis_expected_error(r_filters.error())) { + LOG(WARNING) << "Receive error " << r_filters.error() << " for GetDialogFiltersQuery"; + } fail_promises(promises, r_filters.move_as_error()); - LOG(WARNING) << "Receive error " << r_filters.error() << " for GetDialogFiltersQuery"; need_dialog_filters_reload_ = false; schedule_dialog_filters_reload(Random::fast(60, 5 * 60)); return; @@ -17187,7 +17264,17 @@ void MessagesManager::on_get_dialog_filters(Result> new_server_dialog_filters; LOG(INFO) << "Receive " << filters.size() << " chat filters from server"; std::unordered_set new_dialog_filter_ids; + int32 server_main_dialog_list_position = -1; + int32 position = 0; for (auto &filter : filters) { + if (filter->get_id() == telegram_api::dialogFilterDefault::ID) { + if (server_main_dialog_list_position == -1) { + server_main_dialog_list_position = position; + } else { + LOG(ERROR) << "Receive duplicate dialogFilterDefault"; + } + continue; + } auto dialog_filter = DialogFilter::get_dialog_filter(std::move(filter), true); if (dialog_filter == nullptr) { continue; @@ -17199,6 +17286,15 @@ void MessagesManager::on_get_dialog_filters(Resultshared_config().get_option_boolean("is_premium")) { + LOG(INFO) << "Ignore server main chat list position " << server_main_dialog_list_position; + server_main_dialog_list_position = 0; } bool is_changed = false; @@ -17291,6 +17387,37 @@ void MessagesManager::on_get_dialog_filters(Resultis_empty(true)) { + current_server_position++; + } + if (current_server_position == server_main_dialog_list_position) { + main_dialog_list_position = current_position; + } + } + if (main_dialog_list_position == -1) { + LOG(INFO) << "Failed to find server position " << server_main_dialog_list_position << " in chat filters"; + main_dialog_list_position = static_cast(dialog_filters_.size()); + } + } + + if (main_dialog_list_position != main_dialog_list_position_) { + LOG(INFO) << "Change main chat list position from " << main_dialog_list_position_ << " to " + << main_dialog_list_position; + main_dialog_list_position_ = main_dialog_list_position; + is_changed = true; + } + } if (is_changed || !is_update_chat_filters_sent_) { send_update_chat_filters(); } @@ -17328,6 +17455,10 @@ bool MessagesManager::need_synchronize_dialog_filters() const { // need reorder dialog filters on server return true; } + if (get_server_main_dialog_list_position() != server_main_dialog_list_position_) { + // need reorder main chat list on server + return true; + } return false; } @@ -17368,8 +17499,10 @@ void MessagesManager::synchronize_dialog_filters() { dialog_filter_ids.push_back(dialog_filter->dialog_filter_id); } - if (dialog_filter_ids != get_dialog_filter_ids(server_dialog_filters_)) { - return reorder_dialog_filters_on_server(std::move(dialog_filter_ids)); + auto server_main_dialog_list_position = get_server_main_dialog_list_position(); + if (dialog_filter_ids != get_dialog_filter_ids(server_dialog_filters_) || + server_main_dialog_list_position != server_main_dialog_list_position_) { + return reorder_dialog_filters_on_server(std::move(dialog_filter_ids), server_main_dialog_list_position); } UNREACHABLE(); @@ -17840,10 +17973,10 @@ MessagesManager::Message *MessagesManager::get_message_force(FullMessageId full_ FullMessageId MessagesManager::get_replied_message_id(DialogId dialog_id, const Message *m) { auto full_message_id = get_message_content_replied_message_id(dialog_id, m->content.get()); if (full_message_id.get_message_id().is_valid()) { - CHECK(!m->reply_to_message_id.is_valid()); + CHECK(m->reply_to_message_id == MessageId()); return full_message_id; } - if (!m->reply_to_message_id.is_valid()) { + if (m->reply_to_message_id == MessageId()) { return {}; } return {m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, m->reply_to_message_id}; @@ -19022,7 +19155,9 @@ Result> MessagesManager::create_dialog_filter(DialogFil void MessagesManager::create_dialog_filter(td_api::object_ptr filter, Promise> &&promise) { CHECK(!td_->auth_manager_->is_bot()); - if (dialog_filters_.size() >= MAX_DIALOG_FILTERS) { + auto max_dialog_filters = clamp(G()->shared_config().get_option_integer("chat_filter_count_max"), + static_cast(0), static_cast(100)); + if (dialog_filters_.size() >= narrow_cast(max_dialog_filters)) { return promise.set_error(Status::Error(400, "The maximum number of chat folders exceeded")); } if (!is_update_chat_filters_sent_) { @@ -19174,7 +19309,8 @@ void MessagesManager::on_delete_dialog_filter(DialogFilterId dialog_filter_id, S synchronize_dialog_filters(); } -void MessagesManager::reorder_dialog_filters(vector dialog_filter_ids, Promise &&promise) { +void MessagesManager::reorder_dialog_filters(vector dialog_filter_ids, int32 main_dialog_list_position, + Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); for (auto dialog_filter_id : dialog_filter_ids) { @@ -19188,8 +19324,17 @@ void MessagesManager::reorder_dialog_filters(vector dialog_filte if (new_dialog_filter_ids_set.size() != dialog_filter_ids.size()) { return promise.set_error(Status::Error(400, "Duplicate chat filters in the new list")); } + if (main_dialog_list_position < 0 || main_dialog_list_position > static_cast(dialog_filters_.size())) { + return promise.set_error(Status::Error(400, "Invalid main chat list position specified")); + } + if (!G()->shared_config().get_option_boolean("is_premium")) { + main_dialog_list_position = 0; + } + + if (set_dialog_filters_order(dialog_filters_, dialog_filter_ids) || + main_dialog_list_position != main_dialog_list_position_) { + main_dialog_list_position_ = main_dialog_list_position; - if (set_dialog_filters_order(dialog_filters_, dialog_filter_ids)) { save_dialog_filters(); send_update_chat_filters(); @@ -19198,22 +19343,28 @@ void MessagesManager::reorder_dialog_filters(vector dialog_filte promise.set_value(Unit()); } -void MessagesManager::reorder_dialog_filters_on_server(vector dialog_filter_ids) { +void MessagesManager::reorder_dialog_filters_on_server(vector dialog_filter_ids, + int32 main_dialog_list_position) { CHECK(!td_->auth_manager_->is_bot()); are_dialog_filters_being_synchronized_ = true; - auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_ids](Result result) mutable { - send_closure(actor_id, &MessagesManager::on_reorder_dialog_filters, std::move(dialog_filter_ids), - result.is_error() ? result.move_as_error() : Status::OK()); - }); - td_->create_handler(std::move(promise))->send(dialog_filter_ids); + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this), dialog_filter_ids, main_dialog_list_position](Result result) mutable { + send_closure(actor_id, &MessagesManager::on_reorder_dialog_filters, std::move(dialog_filter_ids), + main_dialog_list_position, result.is_error() ? result.move_as_error() : Status::OK()); + }); + td_->create_handler(std::move(promise)) + ->send(dialog_filter_ids, main_dialog_list_position); } -void MessagesManager::on_reorder_dialog_filters(vector dialog_filter_ids, Status result) { +void MessagesManager::on_reorder_dialog_filters(vector dialog_filter_ids, + int32 main_dialog_list_position, Status result) { CHECK(!td_->auth_manager_->is_bot()); if (result.is_error()) { // TODO rollback dialog_filters_ changes if error isn't 429 } else { - if (set_dialog_filters_order(server_dialog_filters_, std::move(dialog_filter_ids))) { + if (set_dialog_filters_order(server_dialog_filters_, std::move(dialog_filter_ids)) || + server_main_dialog_list_position_ != main_dialog_list_position) { + server_main_dialog_list_position_ = main_dialog_list_position; save_dialog_filters(); } } @@ -19749,7 +19900,7 @@ void MessagesManager::clear_all_draft_messages(bool exclude_secret_chats, Promis int32 MessagesManager::get_pinned_dialogs_limit(DialogListId dialog_list_id) { if (dialog_list_id.is_filter()) { - return DialogFilter::MAX_INCLUDED_FILTER_DIALOGS; + return DialogFilter::get_max_filter_dialogs(); } Slice key{"pinned_chat_count_max"}; @@ -19760,6 +19911,9 @@ int32 MessagesManager::get_pinned_dialogs_limit(DialogListId dialog_list_id) { } int32 limit = clamp(narrow_cast(G()->shared_config().get_option_integer(key)), 0, 1000); if (limit <= 0) { + if (G()->shared_config().get_option_boolean("is_premium")) { + default_limit *= 2; + } return default_limit; } return limit; @@ -24160,7 +24314,7 @@ void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id set_promises(promises); } -Result> MessagesManager::get_message_available_reactions(FullMessageId full_message_id) { +Result> MessagesManager::get_message_available_reactions(FullMessageId full_message_id) { auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "get_message_available_reactions"); if (d == nullptr) { @@ -24174,31 +24328,33 @@ Result> MessagesManager::get_message_available_reactions(FullMess return get_message_available_reactions(d, m); } -vector MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m) { +vector MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m) { CHECK(d != nullptr); CHECK(m != nullptr); auto active_reactions = get_message_active_reactions(d, m); if (!m->message_id.is_valid() || !m->message_id.is_server() || active_reactions.empty()) { - return vector(); + return {}; } - vector result; + bool is_premium = G()->shared_config().get_option_boolean("is_premium"); + vector result; int64 reactions_uniq_max = G()->shared_config().get_option_integer("reactions_uniq_max", 11); bool can_add_new_reactions = m->reactions == nullptr || static_cast(m->reactions->reactions_.size()) < reactions_uniq_max; // can add only active available reactions or remove previously set reaction for (const auto &active_reaction : active_reactions_) { // can add the reaction if it has already been used for the message or is available in the chat - if ((m->reactions != nullptr && m->reactions->get_reaction(active_reaction) != nullptr) || - (can_add_new_reactions && td::contains(active_reactions, active_reaction))) { - result.push_back(active_reaction); + bool is_set = (m->reactions != nullptr && m->reactions->get_reaction(active_reaction.reaction_) != nullptr); + if (is_set || (can_add_new_reactions && td::contains(active_reactions, active_reaction.reaction_))) { + result.emplace_back(active_reaction.reaction_, !is_premium && active_reaction.is_premium_ && !is_set); } } if (m->reactions != nullptr) { for (const auto &reaction : m->reactions->reactions_) { - if (reaction.is_chosen() && !td::contains(result, reaction.get_reaction())) { - CHECK(!td::contains(active_reactions_, reaction.get_reaction())); - result.push_back(reaction.get_reaction()); + if (reaction.is_chosen() && + get_reaction_type(result, reaction.get_reaction()) == AvailableReactionType::Unavailable) { + CHECK(get_reaction_type(active_reactions_, reaction.get_reaction()) == AvailableReactionType::Unavailable); + result.emplace_back(reaction.get_reaction(), false); } } } @@ -24218,8 +24374,14 @@ void MessagesManager::set_message_reaction(FullMessageId full_message_id, string return promise.set_error(Status::Error(400, "Message not found")); } - if (!reaction.empty() && !td::contains(get_message_available_reactions(d, m), reaction)) { - return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); + if (!reaction.empty()) { + auto reaction_type = get_reaction_type(get_message_available_reactions(d, m), reaction); + if (reaction_type == AvailableReactionType::Unavailable) { + return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); + } + if (reaction_type == AvailableReactionType::NeedsPremium) { + return promise.set_error(Status::Error(400, "The reaction is available only for Telegram Premium users")); + } } bool can_get_added_reactions = !is_broadcast_channel(dialog_id) && dialog_id.get_type() != DialogType::User && @@ -24619,6 +24781,12 @@ bool MessagesManager::get_dialog_silent_send_message(DialogId dialog_id) const { return d->notification_settings.silent_send_message; } +DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const { + auto *d = get_dialog(dialog_id); + CHECK(d != nullptr); + return d->default_send_message_as_dialog_id; +} + int64 MessagesManager::generate_new_random_id(const Dialog *d) { int64 random_id; do { @@ -24739,7 +24907,7 @@ unique_ptr MessagesManager::create_message_to_send( (dialog_type == DialogType::SecretChat || reply_to_message_id.is_yet_unsent())) { // the message was forcely preloaded in get_reply_to_message_id auto *reply_to_message = get_message(d, reply_to_message_id); - if (reply_to_message == nullptr || (reply_to_message->message_id.is_yet_unsent() && is_scheduled)) { + if (reply_to_message == nullptr) { m->reply_to_message_id = MessageId(); } else { m->reply_to_random_id = reply_to_message->random_id; @@ -24868,8 +25036,10 @@ MessageId MessagesManager::get_reply_to_message_id(Dialog *d, MessageId top_thre void MessagesManager::fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id, DialogId reply_in_dialog_id, MessageId &reply_to_message_id) { if (!reply_to_message_id.is_valid()) { - if (reply_to_message_id.is_scheduled()) { - if (!message_id.is_scheduled() || message_id == reply_to_message_id) { + if (reply_to_message_id.is_valid_scheduled()) { + CHECK(message_id.is_scheduled()); + CHECK(reply_in_dialog_id == DialogId()); + if (message_id == reply_to_message_id) { LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id; reply_to_message_id = MessageId(); } @@ -24937,23 +25107,23 @@ void MessagesManager::cancel_send_message_query(DialogId dialog_id, Message *m) m->send_message_log_event_id = 0; } - if (m->reply_to_message_id.is_valid()) { - if (!m->reply_to_message_id.is_yet_unsent()) { - auto it = replied_by_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id}); - CHECK(it != replied_by_yet_unsent_messages_.end()); - it->second--; - CHECK(it->second >= 0); - if (it->second == 0) { - replied_by_yet_unsent_messages_.erase(it); - } - } else { - auto it = replied_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id}); - CHECK(it != replied_yet_unsent_messages_.end()); - size_t erased_count = it->second.erase(m->message_id); - CHECK(erased_count > 0); - if (it->second.empty()) { - replied_yet_unsent_messages_.erase(it); - } + if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) { + auto it = replied_by_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id}); + CHECK(it != replied_by_yet_unsent_messages_.end()); + it->second--; + CHECK(it->second >= 0); + if (it->second == 0) { + replied_by_yet_unsent_messages_.erase(it); + } + } + if ((m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_valid_scheduled()) && + m->reply_to_message_id.is_yet_unsent()) { + auto it = replied_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id}); + CHECK(it != replied_yet_unsent_messages_.end()); + size_t erased_count = it->second.erase(m->message_id); + CHECK(erased_count > 0); + if (it->second.empty()) { + replied_yet_unsent_messages_.erase(it); } } { @@ -25329,7 +25499,8 @@ Result MessagesManager::process_input_message_content( UserId(), copied_message->send_emoji); } - TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_)); + bool is_premium = G()->shared_config().get_option_boolean("is_premium"); + TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_, is_premium)); if (content.ttl < 0 || content.ttl > MAX_PRIVATE_MESSAGE_TTL) { return Status::Error(400, "Invalid message content TTL specified"); @@ -25592,7 +25763,8 @@ void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vect LOG(DEBUG) << "Need to send file " << file_id << " with thumbnail " << thumbnail_file_id; if (is_secret) { CHECK(!is_edit); - auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice()); + auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice(), layer); if (secret_input_media.empty()) { LOG(INFO) << "Ask to upload encrypted file " << file_id; CHECK(file_view.is_encrypted_secret()); @@ -25721,10 +25893,17 @@ void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, const FileId thumbnail_file_id) { CHECK(m != nullptr); CHECK(m->message_id.is_valid()); - CHECK(!secret_input_media.empty()); if (G()->close_flag()) { return; } + if (secret_input_media.empty()) { + // the media can't be sent to the chat + LOG(INFO) << "Can't send a media message to " << dialog_id; + + fail_send_message({dialog_id, m->message_id}, Status::Error(400, "The file can't be sent to the secret chat")); + return; + } + /* if (m->media_album_id != 0) { switch (secret_input_media->input_file_->get_id()) { @@ -26075,7 +26254,8 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(!message_id.is_scheduled()); - send_secret_message(dialog_id, m, get_secret_input_media(content, td_, nullptr, BufferSlice())); + auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + send_secret_message(dialog_id, m, get_secret_input_media(content, td_, nullptr, BufferSlice(), layer)); } else { const FormattedText *message_text = get_message_content_text(content); CHECK(message_text != nullptr); @@ -28282,7 +28462,7 @@ Result> MessagesManager::forward_messages( } } MessageId reply_to_message_id; - if (forwarded_message->reply_to_message_id.is_valid() && message_send_options.schedule_date == 0) { + if (forwarded_message->reply_to_message_id.is_valid()) { auto it = forwarded_message_id_to_new_message_id.find(forwarded_message->reply_to_message_id); if (it != forwarded_message_id_to_new_message_id.end()) { reply_to_message_id = it->second; @@ -28912,7 +29092,7 @@ bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_messag being_sent_messages_.erase(it); if (!have_message_force({dialog_id, old_message_id}, "on_update_message_id")) { - delete_sent_message_on_server(dialog_id, new_message_id); + delete_sent_message_on_server(dialog_id, new_message_id, old_message_id); return true; } @@ -28942,7 +29122,8 @@ bool MessagesManager::on_update_scheduled_message_id(int64 random_id, ScheduledS being_sent_messages_.erase(it); if (!have_message_force({dialog_id, old_message_id}, "on_update_scheduled_message_id")) { - delete_sent_message_on_server(dialog_id, MessageId(new_message_id, std::numeric_limits::max())); + delete_sent_message_on_server(dialog_id, MessageId(new_message_id, std::numeric_limits::max()), + old_message_id); return true; } @@ -29271,6 +29452,36 @@ MessagesManager::MessageNotificationGroup MessagesManager::get_message_notificat return result; } +bool MessagesManager::get_dialog_show_preview(const Dialog *d) const { + CHECK(!td_->auth_manager_->is_bot()); + CHECK(d != nullptr); + if (d->notification_settings.use_default_show_preview) { + auto scope = get_dialog_notification_setting_scope(d->dialog_id); + return td_->notification_settings_manager_->get_scope_show_preview(scope); + } else { + return d->notification_settings.show_preview; + } +} + +bool MessagesManager::is_message_preview_enabled(const Dialog *d, const Message *m, bool from_mentions) { + if (!get_dialog_show_preview(d)) { + return false; + } + if (!from_mentions) { + return true; + } + auto sender_dialog_id = get_message_sender(m); + if (!sender_dialog_id.is_valid()) { + return true; + } + d = get_dialog_force(sender_dialog_id, "is_message_preview_enabled"); + if (d == nullptr) { + auto scope = get_dialog_notification_setting_scope(sender_dialog_id); + return td_->notification_settings_manager_->get_scope_show_preview(scope); + } + return get_dialog_show_preview(d); +} + bool MessagesManager::is_from_mention_notification_group(const Message *m) { return m->contains_mention && !m->is_mention_notification_disabled; } @@ -29310,7 +29521,7 @@ void MessagesManager::try_add_pinned_message_notification(Dialog *d, vectornotification_id, m->date, m->disable_notification, - create_new_message_notification(message_id)); + create_new_message_notification(message_id, is_message_preview_enabled(d, m, true))); while (pos > 0 && res[pos - 1].type->get_message_id() < message_id) { std::swap(res[pos - 1], res[pos]); pos--; @@ -29424,8 +29635,9 @@ vector MessagesManager::get_message_notifications_from_database_fo if (is_correct) { // skip mention messages returned among unread messages - res.emplace_back(m->notification_id, m->date, m->disable_notification, - create_new_message_notification(m->message_id)); + res.emplace_back( + m->notification_id, m->date, m->disable_notification, + create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions))); } else { remove_message_notification_id(d, m, true, false); on_message_changed(d, m, false, "get_message_notifications_from_database_force"); @@ -29684,7 +29896,7 @@ void MessagesManager::on_get_message_notifications_from_database(DialogId dialog // skip mention messages returned among unread messages CHECK(m->date > 0); res.emplace_back(m->notification_id, m->date, m->disable_notification, - create_new_message_notification(m->message_id)); + create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions))); } else { remove_message_notification_id(d, m, true, false); on_message_changed(d, m, false, "on_get_message_notifications_from_database"); @@ -30135,7 +30347,8 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f send_closure_later(G()->notification_manager(), &NotificationManager::add_notification, notification_group_id, from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages, d->dialog_id, m->date, settings_dialog_id, m->disable_notification, is_silent ? 0 : ringtone_id, min_delay_ms, - m->notification_id, create_new_message_notification(m->message_id), + m->notification_id, + create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)), "add_new_message_notification"); return true; } @@ -30424,6 +30637,8 @@ void MessagesManager::save_dialog_filters() { } DialogFiltersLogEvent log_event; + log_event.server_main_dialog_list_position = server_main_dialog_list_position_; + log_event.main_dialog_list_position = main_dialog_list_position_; log_event.updated_date = dialog_filters_updated_date_; log_event.server_dialog_filters_in = &server_dialog_filters_; log_event.dialog_filters_in = &dialog_filters_; @@ -30845,8 +31060,10 @@ void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog } } -void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, - MessageId new_message_id) { +void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id, + bool have_new_message, const char *source) { + LOG(INFO) << "Update replies of " << FullMessageId{dialog_id, old_message_id} << " to " << new_message_id << " from " + << source; auto it = replied_yet_unsent_messages_.find({dialog_id, old_message_id}); if (it == replied_yet_unsent_messages_.end()) { return; @@ -30866,9 +31083,11 @@ void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId o // TODO rewrite send message log event register_message_reply(dialog_id, replied_m); } - if (new_message_id.is_valid()) { + if (have_new_message) { CHECK(!new_message_id.is_yet_unsent()); replied_by_yet_unsent_messages_[FullMessageId{dialog_id, new_message_id}] = static_cast(it->second.size()); + } else { + replied_by_yet_unsent_messages_.erase(FullMessageId{dialog_id, new_message_id}); } replied_yet_unsent_messages_.erase(it); } @@ -30913,7 +31132,7 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI being_sent_messages_.erase(it); // must be called before delete_message - update_reply_to_message_id(dialog_id, old_message_id, new_message_id); + update_reply_to_message_id(dialog_id, old_message_id, new_message_id, true, "on_send_message_success"); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); @@ -30922,7 +31141,7 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI being_readded_message_id_ = {dialog_id, old_message_id}; unique_ptr sent_message = delete_message(d, old_message_id, false, &need_update_dialog_pos, source); if (sent_message == nullptr) { - delete_sent_message_on_server(dialog_id, new_message_id); + delete_sent_message_on_server(dialog_id, new_message_id, old_message_id); being_readded_message_id_ = FullMessageId(); return {}; } @@ -31282,6 +31501,11 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) { if (error.message() == "MESSAGE_DELETE_FORBIDDEN") { error_code = 400; error_message = "Message can't be deleted"; + } else if (error.message() == "CHAT_GUEST_SEND_FORBIDDEN") { + error_code = 400; + if (dialog_id.get_type() == DialogType::Channel) { + td_->contacts_manager_->reload_channel(dialog_id.get_channel_id(), Promise()); + } } else if (error.message() != "CHANNEL_PUBLIC_GROUP_NA" && error.message() != "USER_IS_BLOCKED" && error.message() != "USER_BOT_INVALID" && error.message() != "USER_DELETED") { error_code = 400; @@ -31367,7 +31591,7 @@ void MessagesManager::fail_send_message(FullMessageId full_message_id, int error CHECK(old_message_id.is_valid() || old_message_id.is_valid_scheduled()); CHECK(old_message_id.is_yet_unsent()); - update_reply_to_message_id(dialog_id, old_message_id, MessageId()); + update_reply_to_message_id(dialog_id, old_message_id, MessageId(), false, "fail_send_message"); bool need_update_dialog_pos = false; being_readded_message_id_ = full_message_id; @@ -33022,7 +33246,7 @@ vector MessagesManager::get_dialog_lists_to_add_dialog(DialogId di // the dialog isn't added yet to the dialog list // check that it can be actually added if (dialog_filter->included_dialog_ids.size() + dialog_filter->pinned_dialog_ids.size() < - DialogFilter::MAX_INCLUDED_FILTER_DIALOGS) { + narrow_cast(DialogFilter::get_max_filter_dialogs())) { // fast path result.push_back(DialogListId(dialog_filter_id)); continue; @@ -34249,8 +34473,11 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq if (need_send_update && m->notification_id.is_valid() && is_message_notification_active(d, m)) { auto &group_info = get_notification_group_info(d, m); if (group_info.group_id.is_valid()) { - send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, - group_info.group_id, m->notification_id, create_new_message_notification(m->message_id)); + send_closure_later( + G()->notification_manager(), &NotificationManager::edit_notification, group_info.group_id, + m->notification_id, + create_new_message_notification( + m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m)))); } } if (need_send_update && m->is_pinned && d->pinned_message_notification_message_id.is_valid() && @@ -34259,9 +34486,10 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq if (pinned_message != nullptr && pinned_message->notification_id.is_valid() && is_message_notification_active(d, pinned_message) && get_message_content_pinned_message_id(pinned_message->content.get()) == message_id) { - send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, - d->mention_notification_group.group_id, pinned_message->notification_id, - create_new_message_notification(pinned_message->message_id)); + send_closure_later( + G()->notification_manager(), &NotificationManager::edit_notification, + d->mention_notification_group.group_id, pinned_message->notification_id, + create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true))); } } update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask); @@ -34597,9 +34825,11 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } const Message *m = message.get(); - if (m->message_id.is_yet_unsent() && m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_scheduled()) { + if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) { if (!m->reply_to_message_id.is_yet_unsent()) { - replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++; + if (!m->reply_to_message_id.is_scheduled()) { + replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++; + } } else { replied_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}].insert(m->message_id); } @@ -34895,9 +35125,14 @@ MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialo LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source; const Message *m = message.get(); - if (m->message_id.is_yet_unsent() && m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent() && - !m->reply_to_message_id.is_scheduled()) { - replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++; + if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) { + if (!m->reply_to_message_id.is_yet_unsent()) { + if (!m->reply_to_message_id.is_scheduled()) { + replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++; + } + } else { + replied_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}].insert(m->message_id); + } } if (!m->from_database && !m->message_id.is_yet_unsent()) { @@ -34910,6 +35145,10 @@ MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialo register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_scheduled_message_to_dialog"); + if (m->message_id.is_yet_unsent()) { + add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); + } + // must be called after register_message_content, which loads web page update_message_max_reply_media_timestamp(d, message.get(), false); update_message_max_own_media_timestamp(d, message.get()); @@ -35533,9 +35772,28 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog); need_send_update = true; } else if (is_new_available) { - LOG(ERROR) << message_id << " in " << dialog_id << " has changed message it is replied message from " - << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id - << ", message content type is " << old_content_type << '/' << new_content_type; + if (message_id.is_yet_unsent() && old_message->reply_to_message_id == MessageId() && + is_deleted_message(d, new_message->reply_to_message_id) && + get_message(d, new_message->reply_to_message_id) == nullptr && !is_message_in_dialog) { + LOG(INFO) << "Update replied message from " << old_message->reply_to_message_id << " to deleted " + << new_message->reply_to_message_id; + old_message->reply_to_message_id = new_message->reply_to_message_id; + update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog); + need_send_update = true; + } else if (old_message->reply_to_message_id.is_valid_scheduled() && + old_message->reply_to_message_id.is_scheduled_server() && + new_message->reply_to_message_id.is_valid_scheduled() && + new_message->reply_to_message_id.is_scheduled_server() && + old_message->reply_to_message_id.get_scheduled_server_message_id() == + new_message->reply_to_message_id.get_scheduled_server_message_id()) { + // schedule date has changed + old_message->reply_to_message_id = new_message->reply_to_message_id; + need_send_update = true; + } else { + LOG(ERROR) << message_id << " in " << dialog_id << " has changed message it is replied message from " + << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id + << ", message content type is " << old_content_type << '/' << new_content_type; + } } } if (old_message->reply_in_dialog_id != new_message->reply_in_dialog_id) { @@ -37374,6 +37632,25 @@ const DialogFilter *MessagesManager::get_dialog_filter(DialogFilterId dialog_fil return nullptr; } +int32 MessagesManager::get_server_main_dialog_list_position() const { + int32 current_position = 0; + int32 current_server_position = 0; + if (current_position == main_dialog_list_position_) { + return current_server_position; + } + for (const auto &dialog_filter : dialog_filters_) { + current_position++; + if (!dialog_filter->is_empty(true)) { + current_server_position++; + } + if (current_position == main_dialog_list_position_) { + return current_server_position; + } + } + LOG(WARNING) << "Failed to find server position for " << main_dialog_list_position_ << " in chat filters"; + return current_server_position; +} + vector MessagesManager::get_dialog_filter_ids(const vector> &dialog_filters) { return transform(dialog_filters, [](const auto &dialog_filter) { return dialog_filter->dialog_filter_id; }); } @@ -38679,12 +38956,12 @@ void MessagesManager::update_has_outgoing_messages(DialogId dialog_id, const Mes } void MessagesManager::restore_message_reply_to_message_id(Dialog *d, Message *m) { - if (!m->reply_to_message_id.is_valid() || !m->reply_to_message_id.is_yet_unsent()) { + if (m->reply_to_message_id == MessageId() || !m->reply_to_message_id.is_yet_unsent()) { return; } auto message_id = get_message_id_by_random_id(d, m->reply_to_random_id, "restore_message_reply_to_message_id"); - if (!message_id.is_valid()) { + if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { LOG(INFO) << "Failed to find replied " << m->reply_to_message_id << " with random_id = " << m->reply_to_random_id; m->reply_to_message_id = m->top_thread_message_id; m->reply_to_random_id = 0; @@ -39858,6 +40135,7 @@ td_api::object_ptr MessagesManager::get_update_chat_f for (const auto &filter : dialog_filters_) { update->chat_filters_.push_back(filter->get_chat_filter_info_object()); } + update->main_chat_list_position_ = main_dialog_list_position_; return update; } diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 229ab5b45..88c8490be 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -8,6 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AffectedHistory.h" +#include "td/telegram/AvailableReaction.h" #include "td/telegram/ChannelId.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogDate.h" @@ -431,6 +432,8 @@ class MessagesManager final : public Actor { bool get_dialog_silent_send_message(DialogId dialog_id) const; + DialogId get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const; + Result> send_message( DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id, tl_object_ptr &&options, tl_object_ptr &&reply_markup, @@ -535,7 +538,7 @@ class MessagesManager final : public Actor { void set_dialog_description(DialogId dialog_id, const string &description, Promise &&promise); - void set_active_reactions(vector active_reactions); + void set_active_reactions(vector active_reactions); void set_dialog_available_reactions(DialogId dialog_id, vector available_reactions, Promise &&promise); @@ -660,7 +663,8 @@ class MessagesManager final : public Actor { void delete_dialog_filter(DialogFilterId dialog_filter_id, Promise &&promise); - void reorder_dialog_filters(vector dialog_filter_ids, Promise &&promise); + void reorder_dialog_filters(vector dialog_filter_ids, int32 main_dialog_list_position, + Promise &&promise); Status delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) TD_WARN_UNUSED_RESULT; @@ -800,7 +804,7 @@ class MessagesManager final : public Actor { vector get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result, Promise &&promise); - Result> get_message_available_reactions(FullMessageId full_message_id); + Result> get_message_available_reactions(FullMessageId full_message_id); void set_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, Promise &&promise); @@ -1756,7 +1760,6 @@ class MessagesManager final : public Actor { static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description static constexpr size_t MAX_DIALOG_FILTER_TITLE_LENGTH = 12; // server side limit for dialog filter title static constexpr int32 MAX_PRIVATE_MESSAGE_TTL = 60; // server side limit - static constexpr int32 MAX_DIALOG_FILTERS = 10; // server side limit static constexpr int32 DIALOG_FILTERS_CACHE_TIME = 86400; static constexpr int64 SPONSORED_DIALOG_ORDER = static_cast(2147483647) << 32; @@ -2086,7 +2089,7 @@ class MessagesManager final : public Actor { void erase_delete_messages_log_event(uint64 log_event_id); - void delete_sent_message_on_server(DialogId dialog_id, MessageId message_id); + void delete_sent_message_on_server(DialogId dialog_id, MessageId message_id, MessageId old_message_id); void delete_messages_on_server(DialogId dialog_id, vector message_ids, bool revoke, uint64 log_event_id, Promise &&promise); @@ -2328,7 +2331,8 @@ class MessagesManager final : public Actor { void delete_message_from_database(Dialog *d, MessageId message_id, const Message *m, bool is_permanently_deleted); - void update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id); + void update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id, + bool have_new_message, const char *source); void delete_message_files(DialogId dialog_id, const Message *m) const; @@ -2381,6 +2385,10 @@ class MessagesManager final : public Actor { void send_update_new_message(const Dialog *d, const Message *m); + bool get_dialog_show_preview(const Dialog *d) const; + + bool is_message_preview_enabled(const Dialog *d, const Message *m, bool from_mentions); + static bool is_from_mention_notification_group(const Message *m); static bool is_message_notification_active(const Dialog *d, const Message *m); @@ -2645,7 +2653,7 @@ class MessagesManager final : public Actor { bool update_dialog_silent_send_message(Dialog *d, bool silent_send_message); - vector get_message_available_reactions(const Dialog *d, const Message *m); + vector get_message_available_reactions(const Dialog *d, const Message *m); void on_set_message_reaction(FullMessageId full_message_id, Result result, Promise promise); @@ -2657,9 +2665,6 @@ class MessagesManager final : public Actor { vector get_active_reactions(const vector &available_reactions) const; - static vector get_active_reactions(const vector &available_reactions, - const vector &active_reactions); - vector get_dialog_active_reactions(const Dialog *d) const; vector get_message_active_reactions(const Dialog *d, const Message *m) const; @@ -2739,7 +2744,7 @@ class MessagesManager final : public Actor { void reload_dialog_filters(); - void on_get_dialog_filters(Result>> r_filters, bool dummy); + void on_get_dialog_filters(Result>> r_filters, bool dummy); bool need_synchronize_dialog_filters() const; @@ -2773,9 +2778,10 @@ class MessagesManager final : public Actor { void on_delete_dialog_filter(DialogFilterId dialog_filter_id, Status result); - void reorder_dialog_filters_on_server(vector dialog_filter_ids); + void reorder_dialog_filters_on_server(vector dialog_filter_ids, int32 main_dialog_list_position); - void on_reorder_dialog_filters(vector dialog_filter_ids, Status result); + void on_reorder_dialog_filters(vector dialog_filter_ids, int32 main_dialog_list_position, + Status result); void save_dialog_filters(); @@ -2793,6 +2799,8 @@ class MessagesManager final : public Actor { DialogFilter *get_dialog_filter(DialogFilterId dialog_filter_id); const DialogFilter *get_dialog_filter(DialogFilterId dialog_filter_id) const; + int32 get_server_main_dialog_list_position() const; + static vector get_dialog_filter_ids(const vector> &dialog_filters); static vector get_dialog_filter_folder_ids(const DialogFilter *filter); @@ -3539,6 +3547,8 @@ class MessagesManager final : public Actor { vector> dialog_filters_; vector recommended_dialog_filters_; vector> dialog_filter_reload_queries_; + int32 server_main_dialog_list_position_ = 0; // position of the main dialog list stored on the server + int32 main_dialog_list_position_ = 0; // local position of the main dialog list stored on the server FlatHashMap active_get_channel_differencies_; FlatHashMap get_channel_difference_to_log_event_id_; @@ -3680,7 +3690,7 @@ class MessagesManager final : public Actor { }; FlatHashMap pending_reactions_; - vector active_reactions_; + vector active_reactions_; FlatHashMap active_reaction_pos_; uint32 scheduled_messages_sync_generation_ = 1; diff --git a/td/telegram/NotificationManager.cpp b/td/telegram/NotificationManager.cpp index 92c07c4aa..b99cc4024 100644 --- a/td/telegram/NotificationManager.cpp +++ b/td/telegram/NotificationManager.cpp @@ -2869,6 +2869,9 @@ string NotificationManager::convert_loc_key(const string &loc_key) { if (loc_key == "MESSAGE_ROUND") { return "MESSAGE_VIDEO_NOTE"; } + if (loc_key == "MESSAGE_RECURRING_PAY") { + return "MESSAGE_RECURRING_PAYMENT"; + } break; case 'S': if (loc_key == "MESSAGE_SCREENSHOT") { @@ -3053,7 +3056,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo if (loc_key == "SESSION_REVOKE") { if (was_encrypted) { - send_closure(td_->auth_manager_actor_, &AuthManager::on_authorization_lost, "SESSION_REVOKE"); + G()->log_out("SESSION_REVOKE"); } else { LOG(ERROR) << "Receive unencrypted SESSION_REVOKE push notification"; } @@ -3306,8 +3309,8 @@ Status NotificationManager::process_push_notification_payload(string payload, bo flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, sender_user_id.get(), sender_access_hash, user_name, string(), string(), string(), - std::move(sender_photo), nullptr, 0, Auto(), string(), string()); + false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), sender_access_hash, user_name, + string(), string(), string(), std::move(sender_photo), nullptr, 0, Auto(), string(), string()); td_->contacts_manager_->on_get_user(std::move(user), "process_push_notification_payload"); } @@ -3664,8 +3667,8 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, sender_user_id.get(), 0, user_name, string(), string(), string(), nullptr, nullptr, 0, - Auto(), string(), string()); + false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), 0, user_name, string(), string(), + string(), nullptr, nullptr, 0, Auto(), string(), string()); td_->contacts_manager_->on_get_user(std::move(user), "add_message_push_notification"); } diff --git a/td/telegram/NotificationSettingsManager.cpp b/td/telegram/NotificationSettingsManager.cpp index 07320b361..eb431cfee 100644 --- a/td/telegram/NotificationSettingsManager.cpp +++ b/td/telegram/NotificationSettingsManager.cpp @@ -582,6 +582,10 @@ const unique_ptr &NotificationSettingsManager::get_scope_noti return get_scope_notification_settings(scope)->sound; } +bool NotificationSettingsManager::get_scope_show_preview(NotificationSettingsScope scope) const { + return get_scope_notification_settings(scope)->show_preview; +} + bool NotificationSettingsManager::get_scope_disable_pinned_message_notifications( NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->disable_pinned_message_notifications; diff --git a/td/telegram/NotificationSettingsManager.h b/td/telegram/NotificationSettingsManager.h index 26437ea1c..c88be9458 100644 --- a/td/telegram/NotificationSettingsManager.h +++ b/td/telegram/NotificationSettingsManager.h @@ -44,6 +44,8 @@ class NotificationSettingsManager final : public Actor { const unique_ptr &get_scope_notification_sound(NotificationSettingsScope scope) const; + bool get_scope_show_preview(NotificationSettingsScope scope) const; + bool get_scope_disable_pinned_message_notifications(NotificationSettingsScope scope) const; bool get_scope_disable_mention_notifications(NotificationSettingsScope scope) const; diff --git a/td/telegram/NotificationSound.cpp b/td/telegram/NotificationSound.cpp index 1ddf05250..c4ae2ad16 100644 --- a/td/telegram/NotificationSound.cpp +++ b/td/telegram/NotificationSound.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/NotificationSound.h" +#include "td/utils/logging.h" #include "td/utils/tl_helpers.h" namespace td { @@ -239,7 +240,7 @@ unique_ptr get_notification_sound(telegram_api::NotificationS case telegram_api::notificationSoundRingtone::ID: { const auto *sound = static_cast(notification_sound); if (sound->id_ == 0 || sound->id_ == -1) { - LOG(ERROR) << "Receive ringtone with id = " << sound->id_; + LOG(ERROR) << "Receive ringtone with ID = " << sound->id_; return make_unique(); } return td::make_unique(sound->id_); diff --git a/td/telegram/NotificationSound.h b/td/telegram/NotificationSound.h index 0a91662e2..b7414681e 100644 --- a/td/telegram/NotificationSound.h +++ b/td/telegram/NotificationSound.h @@ -8,7 +8,6 @@ #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/NotificationSoundType.h" -#include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/utils/common.h" diff --git a/td/telegram/NotificationType.cpp b/td/telegram/NotificationType.cpp index c4d0b310f..557ec4bee 100644 --- a/td/telegram/NotificationType.cpp +++ b/td/telegram/NotificationType.cpp @@ -49,7 +49,7 @@ class NotificationTypeMessage final : public NotificationType { if (message_object == nullptr) { return nullptr; } - return td_api::make_object(std::move(message_object)); + return td_api::make_object(std::move(message_object), show_preview_); } StringBuilder &to_string_builder(StringBuilder &string_builder) const final { @@ -57,9 +57,11 @@ class NotificationTypeMessage final : public NotificationType { } MessageId message_id_; + bool show_preview_; public: - explicit NotificationTypeMessage(MessageId message_id) : message_id_(message_id) { + NotificationTypeMessage(MessageId message_id, bool show_preview) + : message_id_(message_id), show_preview_(show_preview) { } }; @@ -288,6 +290,11 @@ class NotificationTypePushMessage final : public NotificationType { return td_api::make_object(arg, false, is_pinned); } break; + case 'R': + if (key == "MESSAGE_RECURRING_PAYMENT") { + return td_api::make_object(arg); + } + break; case 'S': if (key == "MESSAGE_SECRET_PHOTO") { return td_api::make_object(nullptr, arg, true, false); @@ -375,8 +382,8 @@ class NotificationTypePushMessage final : public NotificationType { } }; -unique_ptr create_new_message_notification(MessageId message_id) { - return make_unique(message_id); +unique_ptr create_new_message_notification(MessageId message_id, bool show_preview) { + return make_unique(message_id, show_preview); } unique_ptr create_new_secret_chat_notification() { diff --git a/td/telegram/NotificationType.h b/td/telegram/NotificationType.h index 46e78de86..d3689213f 100644 --- a/td/telegram/NotificationType.h +++ b/td/telegram/NotificationType.h @@ -53,7 +53,7 @@ inline StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr return string_builder << *notification_type; } -unique_ptr create_new_message_notification(MessageId message_id); +unique_ptr create_new_message_notification(MessageId message_id, bool show_preview); unique_ptr create_new_secret_chat_notification(); diff --git a/td/telegram/OptionManager.cpp b/td/telegram/OptionManager.cpp index eee451159..ba903b603 100644 --- a/td/telegram/OptionManager.cpp +++ b/td/telegram/OptionManager.cpp @@ -31,6 +31,7 @@ #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/port/Clocks.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" @@ -93,6 +94,9 @@ OptionManager::OptionManager(Td *td, ActorShared<> parent) : td_(td), parent_(st if (!G()->shared_config().have_option("message_caption_length_max")) { G()->shared_config().set_option_integer("message_caption_length_max", 1024); } + if (!G()->shared_config().have_option("bio_length_max")) { + G()->shared_config().set_option_integer("bio_length_max", 70); + } if (!G()->shared_config().have_option("suggested_video_note_length")) { G()->shared_config().set_option_integer("suggested_video_note_length", 384); } @@ -111,6 +115,12 @@ OptionManager::OptionManager(Td *td, ActorShared<> parent) : td_(td), parent_(st if (!G()->shared_config().have_option("notification_sound_count_max")) { G()->shared_config().set_option_integer("notification_sound_count_max", G()->is_test_dc() ? 5 : 100); } + if (!G()->shared_config().have_option("chat_filter_count_max")) { + G()->shared_config().set_option_integer("chat_filter_count_max", G()->is_test_dc() ? 3 : 10); + } + if (!G()->shared_config().have_option("chat_filter_chosen_chat_count_max")) { + G()->shared_config().set_option_integer("chat_filter_chosen_chat_count_max", G()->is_test_dc() ? 5 : 100); + } G()->shared_config().set_option_integer("utc_time_offset", Clocks::tz_offset()); } @@ -150,17 +160,25 @@ void OptionManager::clear_options() { bool OptionManager::is_internal_option(Slice name) { switch (name[0]) { case 'a': - return name == "animated_emoji_zoom" || name == "animation_search_emojis" || + return name == "about_length_limit_default" || name == "about_length_limit_premium" || + name == "animated_emoji_zoom" || name == "animation_search_emojis" || name == "animation_search_provider" || name == "auth"; case 'b': return name == "base_language_pack_version"; case 'c': return name == "call_receive_timeout_ms" || name == "call_ring_timeout_ms" || + name == "caption_length_limit_default" || name == "caption_length_limit_premium" || + name == "channels_limit_default" || name == "channels_limit_premium" || + name == "channels_public_limit_default" || name == "channels_public_limit_premium" || name == "channels_read_media_period" || name == "chat_read_mark_expire_period" || name == "chat_read_mark_size_threshold"; case 'd': - return name == "dc_txt_domain_name" || name == "default_reaction_needs_sync" || name == "dice_emojis" || - name == "dice_success_values"; + return name == "dc_txt_domain_name" || name == "default_reaction_needs_sync" || + name == "dialog_filters_chats_limit_default" || name == "dialog_filters_chats_limit_premium" || + name == "dialog_filters_limit_default" || name == "dialog_filters_limit_premium" || + name == "dialogs_folder_pinned_limit_default" || name == "dialogs_folder_pinned_limit_premium" || + name == "dialogs_pinned_limit_default" || name == "dialogs_pinned_limit_premium" || + name == "dice_emojis" || name == "dice_success_values"; case 'e': return name == "edit_time_limit" || name == "emoji_sounds"; case 'i': @@ -173,11 +191,16 @@ bool OptionManager::is_internal_option(Slice name) { return name == "notification_cloud_delay_ms" || name == "notification_default_delay_ms"; case 'o': return name == "online_cloud_timeout_ms" || name == "online_update_period_ms" || name == "otherwise_relogin_days"; + case 'p': + return name == "premium_bot_username" || name == "premium_features" || name == "premium_invoice_slug"; case 'r': return name == "rating_e_decay" || name == "reactions_uniq_max" || name == "recent_stickers_limit" || name == "revoke_pm_inbox" || name == "revoke_time_limit" || name == "revoke_pm_time_limit"; case 's': - return name == "saved_animations_limit" || name == "session_count"; + return name == "saved_animations_limit" || name == "saved_gifs_limit_default" || + name == "saved_gifs_limit_premium" || name == "session_count" || name == "stickers_faved_limit_default" || + name == "stickers_faved_limit_premium" || name == "stickers_normal_by_emoji_per_premium_num" || + name == "stickers_premium_by_emoji_num"; case 'v': return name == "video_note_size_max"; case 'w': diff --git a/td/telegram/Payments.cpp b/td/telegram/Payments.cpp index cff860453..15ea203f2 100644 --- a/td/telegram/Payments.cpp +++ b/td/telegram/Payments.cpp @@ -12,6 +12,7 @@ #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" +#include "td/telegram/MessageEntity.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/PasswordManager.h" @@ -34,6 +35,56 @@ namespace td { +namespace { + +static tl_object_ptr get_product_description_object(const string &description) { + FormattedText result; + result.text = description; + result.entities = find_entities(result.text, true, true); + return get_formatted_text_object(result, true, 0); +} + +struct InputInvoiceInfo { + DialogId dialog_id_; + telegram_api::object_ptr input_invoice_; +}; + +Result get_input_invoice_info(Td *td, td_api::object_ptr &&input_invoice) { + if (input_invoice == nullptr) { + return Status::Error(400, "Input invoice must be non-empty"); + } + + InputInvoiceInfo result; + switch (input_invoice->get_id()) { + case td_api::inputInvoiceMessage::ID: { + auto invoice = td_api::move_object_as(input_invoice); + DialogId dialog_id(invoice->chat_id_); + MessageId message_id(invoice->message_id_); + TRY_RESULT(server_message_id, td->messages_manager_->get_invoice_message_id({dialog_id, message_id})); + + auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr) { + return Status::Error(400, "Can't access the chat"); + } + + result.dialog_id_ = dialog_id; + result.input_invoice_ = + make_tl_object(std::move(input_peer), server_message_id.get()); + break; + } + case td_api::inputInvoiceName::ID: { + auto invoice = td_api::move_object_as(input_invoice); + result.input_invoice_ = make_tl_object(invoice->name_); + break; + } + default: + UNREACHABLE(); + } + return std::move(result); +} + +} // namespace + class SetBotShippingAnswerQuery final : public Td::ResultHandler { Promise promise_; @@ -137,18 +188,45 @@ static tl_object_ptr convert_invoice(tl_object_ptr( - std::move(invoice->currency_), std::move(labeled_prices), invoice->max_tip_amount_, - vector(invoice->suggested_tip_amounts_), is_test, need_name, need_phone_number, need_email_address, - need_shipping_address, send_phone_number_to_provider, send_email_address_to_provider, is_flexible); + return make_tl_object(std::move(invoice->currency_), std::move(labeled_prices), + invoice->max_tip_amount_, vector(invoice->suggested_tip_amounts_), + std::move(invoice->recurring_terms_url_), is_test, need_name, + need_phone_number, need_email_address, need_shipping_address, + send_phone_number_to_provider, send_email_address_to_provider, is_flexible); } -static tl_object_ptr convert_payment_provider( +static tl_object_ptr convert_payment_provider( const string &native_provider_name, tl_object_ptr native_parameters) { if (native_parameters == nullptr) { return nullptr; } + if (native_provider_name == "smartglocal") { + string data = native_parameters->data_; + auto r_value = json_decode(data); + if (r_value.is_error()) { + LOG(ERROR) << "Can't parse JSON object \"" << native_parameters->data_ << "\": " << r_value.error(); + return nullptr; + } + + auto value = r_value.move_as_ok(); + if (value.type() != JsonValue::Type::Object) { + LOG(ERROR) << "Wrong JSON data \"" << native_parameters->data_ << '"'; + return nullptr; + } + + auto r_public_token = get_json_object_string_field(value.get_object(), "public_token", false); + + if (r_public_token.is_error()) { + LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; + return nullptr; + } + if (value.get_object().size() != 1) { + LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; + } + + return make_tl_object(r_public_token.move_as_ok()); + } if (native_provider_name == "stripe") { string data = native_parameters->data_; auto r_value = json_decode(data); @@ -178,9 +256,9 @@ static tl_object_ptr convert_payment_provider( LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; } - return make_tl_object(r_publishable_key.move_as_ok(), r_need_country.move_as_ok(), - r_need_postal_code.move_as_ok(), - r_need_cardholder_name.move_as_ok()); + return make_tl_object(r_publishable_key.move_as_ok(), r_need_country.move_as_ok(), + r_need_postal_code.move_as_ok(), + r_need_cardholder_name.move_as_ok()); } return nullptr; @@ -270,20 +348,15 @@ class GetPaymentFormQuery final : public Td::ResultHandler { explicit GetPaymentFormQuery(Promise> &&promise) : promise_(std::move(promise)) { } - void send(DialogId dialog_id, ServerMessageId server_message_id, - tl_object_ptr &&theme_parameters) { - dialog_id_ = dialog_id; - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + void send(InputInvoiceInfo &&input_invoice_info, tl_object_ptr &&theme_parameters) { + dialog_id_ = input_invoice_info.dialog_id_; int32 flags = 0; if (theme_parameters != nullptr) { flags |= telegram_api::payments_getPaymentForm::THEME_PARAMS_MASK; } send_query(G()->net_query_creator().create(telegram_api::payments_getPaymentForm( - flags, std::move(input_peer), server_message_id.get(), std::move(theme_parameters)))); + flags, std::move(input_invoice_info.input_invoice_), std::move(theme_parameters)))); } void on_result(BufferSlice packet) final { @@ -309,13 +382,20 @@ class GetPaymentFormQuery final : public Td::ResultHandler { } bool can_save_credentials = payment_form->can_save_credentials_; bool need_password = payment_form->password_missing_; + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_form->photo_), dialog_id_); + auto payment_provider = + convert_payment_provider(payment_form->native_provider_, std::move(payment_form->native_params_)); + if (payment_provider == nullptr) { + payment_provider = td_api::make_object(std::move(payment_form->url_)); + } promise_.set_value(make_tl_object( - payment_form->form_id_, convert_invoice(std::move(payment_form->invoice_)), std::move(payment_form->url_), + payment_form->form_id_, convert_invoice(std::move(payment_form->invoice_)), td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentForm seller"), td_->contacts_manager_->get_user_id_object(payments_provider_user_id, "paymentForm provider"), - convert_payment_provider(payment_form->native_provider_, std::move(payment_form->native_params_)), - convert_order_info(std::move(payment_form->saved_info_)), - convert_saved_credentials(std::move(payment_form->saved_credentials_)), can_save_credentials, need_password)); + std::move(payment_provider), convert_order_info(std::move(payment_form->saved_info_)), + convert_saved_credentials(std::move(payment_form->saved_credentials_)), can_save_credentials, need_password, + payment_form->title_, get_product_description_object(payment_form->description_), + get_photo_object(td_->file_manager_.get(), photo))); } void on_error(Status status) final { @@ -333,13 +413,9 @@ class ValidateRequestedInfoQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(DialogId dialog_id, ServerMessageId server_message_id, - tl_object_ptr requested_info, bool allow_save) { - dialog_id_ = dialog_id; - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + void send(InputInvoiceInfo &&input_invoice_info, tl_object_ptr requested_info, + bool allow_save) { + dialog_id_ = input_invoice_info.dialog_id_; int32 flags = 0; if (allow_save) { @@ -350,7 +426,7 @@ class ValidateRequestedInfoQuery final : public Td::ResultHandler { requested_info->flags_ = 0; } send_query(G()->net_query_creator().create(telegram_api::payments_validateRequestedInfo( - flags, false /*ignored*/, std::move(input_peer), server_message_id.get(), std::move(requested_info)))); + flags, false /*ignored*/, std::move(input_invoice_info.input_invoice_), std::move(requested_info)))); } void on_result(BufferSlice packet) final { @@ -382,16 +458,12 @@ class SendPaymentFormQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(DialogId dialog_id, ServerMessageId server_message_id, int64 payment_form_id, const string &order_info_id, + void send(InputInvoiceInfo &&input_invoice_info, int64 payment_form_id, const string &order_info_id, const string &shipping_option_id, tl_object_ptr input_credentials, int64 tip_amount) { CHECK(input_credentials != nullptr); - dialog_id_ = dialog_id; - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + dialog_id_ = input_invoice_info.dialog_id_; int32 flags = 0; if (!order_info_id.empty()) { @@ -404,7 +476,7 @@ class SendPaymentFormQuery final : public Td::ResultHandler { flags |= telegram_api::payments_sendPaymentForm::TIP_AMOUNT_MASK; } send_query(G()->net_query_creator().create(telegram_api::payments_sendPaymentForm( - flags, payment_form_id, std::move(input_peer), server_message_id.get(), order_info_id, shipping_option_id, + flags, payment_form_id, std::move(input_invoice_info.input_invoice_), order_info_id, shipping_option_id, std::move(input_credentials), tip_amount))); } @@ -486,8 +558,9 @@ class GetPaymentReceiptQuery final : public Td::ResultHandler { auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_receipt->photo_), dialog_id_); promise_.set_value(make_tl_object( - payment_receipt->title_, payment_receipt->description_, get_photo_object(td_->file_manager_.get(), photo), - payment_receipt->date_, td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"), + payment_receipt->title_, get_product_description_object(payment_receipt->description_), + get_photo_object(td_->file_manager_.get(), photo), payment_receipt->date_, + td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"), td_->contacts_manager_->get_user_id_object(payments_provider_user_id, "paymentReceipt provider"), convert_invoice(std::move(payment_receipt->invoice_)), convert_order_info(std::move(payment_receipt->info_)), convert_shipping_option(std::move(payment_receipt->shipping_)), std::move(payment_receipt->credentials_title_), @@ -561,6 +634,32 @@ class ClearSavedInfoQuery final : public Td::ResultHandler { } }; +class ExportInvoiceQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ExportInvoiceQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(tl_object_ptr &&input_media_invoice) { + send_query(G()->net_query_creator().create(telegram_api::payments_exportInvoice(std::move(input_media_invoice)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto link = result_ptr.move_as_ok(); + promise_.set_value(std::move(link->url_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class GetBankCardInfoQuery final : public Td::ResultHandler { Promise> promise_; @@ -611,7 +710,8 @@ bool operator==(const Invoice &lhs, const Invoice &rhs) { lhs.send_phone_number_to_provider == rhs.send_phone_number_to_provider && lhs.send_email_address_to_provider == rhs.send_email_address_to_provider && lhs.is_flexible == rhs.is_flexible && lhs.currency == rhs.currency && lhs.price_parts == rhs.price_parts && - lhs.max_tip_amount == rhs.max_tip_amount && lhs.suggested_tip_amounts == rhs.suggested_tip_amounts; + lhs.max_tip_amount == rhs.max_tip_amount && lhs.suggested_tip_amounts == rhs.suggested_tip_amounts && + lhs.recurring_payment_terms_of_service_url == rhs.recurring_payment_terms_of_service_url; } bool operator!=(const Invoice &lhs, const Invoice &rhs) { @@ -625,8 +725,12 @@ StringBuilder &operator<<(StringBuilder &string_builder, const Invoice &invoice) << (invoice.need_email_address ? ", needs email address" : "") << (invoice.need_shipping_address ? ", needs shipping address" : "") << (invoice.send_phone_number_to_provider ? ", sends phone number to provider" : "") - << (invoice.send_email_address_to_provider ? ", sends email address to provider" : "") << " in " - << invoice.currency << " with price parts " << format::as_array(invoice.price_parts) + << (invoice.send_email_address_to_provider ? ", sends email address to provider" : "") + << (invoice.recurring_payment_terms_of_service_url.empty() + ? string() + : ", recurring payments terms of service at " + + invoice.recurring_payment_terms_of_service_url) + << " in " << invoice.currency << " with price parts " << format::as_array(invoice.price_parts) << " and suggested tip amounts " << invoice.suggested_tip_amounts << " up to " << invoice.max_tip_amount << "]"; } @@ -784,6 +888,8 @@ Result process_input_message_invoice( result.invoice.max_tip_amount = input_invoice->invoice_->max_tip_amount_; result.invoice.suggested_tip_amounts = std::move(input_invoice->invoice_->suggested_tip_amounts_); + result.invoice.recurring_payment_terms_of_service_url = + std::move(input_invoice->invoice_->recurring_payment_terms_of_service_url_); result.invoice.is_test = input_invoice->invoice_->is_test_; result.invoice.need_name = input_invoice->invoice_->need_name_; result.invoice.need_phone_number = input_invoice->invoice_->need_phone_number_; @@ -810,10 +916,10 @@ Result process_input_message_invoice( tl_object_ptr get_message_invoice_object(const InputInvoice &input_invoice, Td *td) { return make_tl_object( - input_invoice.title, input_invoice.description, get_photo_object(td->file_manager_.get(), input_invoice.photo), - input_invoice.invoice.currency, input_invoice.total_amount, input_invoice.start_parameter, - input_invoice.invoice.is_test, input_invoice.invoice.need_shipping_address, - input_invoice.receipt_message_id.get()); + input_invoice.title, get_product_description_object(input_invoice.description), + get_photo_object(td->file_manager_.get(), input_invoice.photo), input_invoice.invoice.currency, + input_invoice.total_amount, input_invoice.start_parameter, input_invoice.invoice.is_test, + input_invoice.invoice.need_shipping_address, input_invoice.receipt_message_id.get()); } static tl_object_ptr get_input_invoice(const Invoice &invoice) { @@ -845,14 +951,18 @@ static tl_object_ptr get_input_invoice(const Invoice &inv if (invoice.max_tip_amount != 0) { flags |= telegram_api::invoice::MAX_TIP_AMOUNT_MASK; } + if (!invoice.recurring_payment_terms_of_service_url.empty()) { + flags |= telegram_api::invoice::RECURRING_TERMS_URL_MASK; + } auto prices = transform(invoice.price_parts, [](const LabeledPricePart &price) { return telegram_api::make_object(price.label, price.amount); }); return make_tl_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, invoice.currency, std::move(prices), - invoice.max_tip_amount, vector(invoice.suggested_tip_amounts)); + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, invoice.currency, std::move(prices), + invoice.max_tip_amount, vector(invoice.suggested_tip_amounts), + invoice.recurring_payment_terms_of_service_url); } static tl_object_ptr get_input_web_document(const FileManager *file_manager, @@ -1147,9 +1257,10 @@ void answer_pre_checkout_query(Td *td, int64 pre_checkout_query_id, const string td->create_handler(std::move(promise))->send(pre_checkout_query_id, error_message); } -void get_payment_form(Td *td, FullMessageId full_message_id, const td_api::object_ptr &theme, +void get_payment_form(Td *td, td_api::object_ptr &&input_invoice, + const td_api::object_ptr &theme, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, server_message_id, td->messages_manager_->get_invoice_message_id(full_message_id)); + TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice))); tl_object_ptr theme_parameters; if (theme != nullptr) { @@ -1157,12 +1268,13 @@ void get_payment_form(Td *td, FullMessageId full_message_id, const td_api::objec theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false); } td->create_handler(std::move(promise)) - ->send(full_message_id.get_dialog_id(), server_message_id, std::move(theme_parameters)); + ->send(std::move(input_invoice_info), std::move(theme_parameters)); } -void validate_order_info(Td *td, FullMessageId full_message_id, tl_object_ptr order_info, - bool allow_save, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, server_message_id, td->messages_manager_->get_invoice_message_id(full_message_id)); +void validate_order_info(Td *td, td_api::object_ptr &&input_invoice, + td_api::object_ptr &&order_info, bool allow_save, + Promise> &&promise) { + TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice))); if (order_info != nullptr) { if (!clean_input_string(order_info->name_)) { @@ -1197,13 +1309,14 @@ void validate_order_info(Td *td, FullMessageId full_message_id, tl_object_ptrcreate_handler(std::move(promise)) - ->send(full_message_id.get_dialog_id(), server_message_id, convert_order_info(std::move(order_info)), allow_save); + ->send(std::move(input_invoice_info), convert_order_info(std::move(order_info)), allow_save); } -void send_payment_form(Td *td, FullMessageId full_message_id, int64 payment_form_id, const string &order_info_id, - const string &shipping_option_id, const tl_object_ptr &credentials, - int64 tip_amount, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, server_message_id, td->messages_manager_->get_invoice_message_id(full_message_id)); +void send_payment_form(Td *td, td_api::object_ptr &&input_invoice, int64 payment_form_id, + const string &order_info_id, const string &shipping_option_id, + const td_api::object_ptr &credentials, int64 tip_amount, + Promise> &&promise) { + TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice))); if (credentials == nullptr) { return promise.set_error(Status::Error(400, "Input payment credentials must be non-empty")); @@ -1254,7 +1367,7 @@ void send_payment_form(Td *td, FullMessageId full_message_id, int64 payment_form } td->create_handler(std::move(promise)) - ->send(full_message_id.get_dialog_id(), server_message_id, payment_form_id, order_info_id, shipping_option_id, + ->send(std::move(input_invoice_info), payment_form_id, order_info_id, shipping_option_id, std::move(input_credentials), tip_amount); } @@ -1278,6 +1391,14 @@ void delete_saved_credentials(Td *td, Promise &&promise) { td->create_handler(std::move(promise))->send(true, false); } +void export_invoice(Td *td, td_api::object_ptr &&invoice, Promise &&promise) { + if (invoice == nullptr) { + return promise.set_error(Status::Error(400, "Invoice must be non-empty")); + } + TRY_RESULT_PROMISE(promise, input_invoice, process_input_message_invoice(std::move(invoice), td)); + td->create_handler(std::move(promise))->send(get_input_media_invoice(input_invoice, td)); +} + void get_bank_card_info(Td *td, const string &bank_card_number, Promise> &&promise) { td->create_handler(std::move(promise))->send(bank_card_number); diff --git a/td/telegram/Payments.h b/td/telegram/Payments.h index 1deb56d81..13deb0e31 100644 --- a/td/telegram/Payments.h +++ b/td/telegram/Payments.h @@ -39,6 +39,7 @@ struct Invoice { vector price_parts; int64 max_tip_amount = 0; vector suggested_tip_amounts; + string recurring_payment_terms_of_service_url; bool is_test = false; bool need_name = false; bool need_phone_number = false; @@ -180,15 +181,18 @@ void answer_shipping_query(Td *td, int64 shipping_query_id, void answer_pre_checkout_query(Td *td, int64 pre_checkout_query_id, const string &error_message, Promise &&promise); -void get_payment_form(Td *td, FullMessageId full_message_id, const td_api::object_ptr &theme, +void get_payment_form(Td *td, td_api::object_ptr &&input_invoice, + const td_api::object_ptr &theme, Promise> &&promise); -void validate_order_info(Td *td, FullMessageId full_message_id, tl_object_ptr order_info, - bool allow_save, Promise> &&promise); +void validate_order_info(Td *td, td_api::object_ptr &&input_invoice, + td_api::object_ptr &&order_info, bool allow_save, + Promise> &&promise); -void send_payment_form(Td *td, FullMessageId full_message_id, int64 payment_form_id, const string &order_info_id, - const string &shipping_option_id, const tl_object_ptr &credentials, - int64 tip_amount, Promise> &&promise); +void send_payment_form(Td *td, td_api::object_ptr &&input_invoice, int64 payment_form_id, + const string &order_info_id, const string &shipping_option_id, + const td_api::object_ptr &credentials, int64 tip_amount, + Promise> &&promise); void get_payment_receipt(Td *td, FullMessageId full_message_id, Promise> &&promise); @@ -199,6 +203,8 @@ void delete_saved_order_info(Td *td, Promise &&promise); void delete_saved_credentials(Td *td, Promise &&promise); +void export_invoice(Td *td, td_api::object_ptr &&invoice, Promise &&promise); + void get_bank_card_info(Td *td, const string &bank_card_number, Promise> &&promise); diff --git a/td/telegram/Payments.hpp b/td/telegram/Payments.hpp index 5eef1de6a..46113c4fd 100644 --- a/td/telegram/Payments.hpp +++ b/td/telegram/Payments.hpp @@ -30,6 +30,7 @@ void parse(LabeledPricePart &labeled_price_part, ParserT &parser) { template void store(const Invoice &invoice, StorerT &storer) { bool has_tip = invoice.max_tip_amount != 0; + bool is_recurring = !invoice.recurring_payment_terms_of_service_url.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(invoice.is_test); STORE_FLAG(invoice.need_name); @@ -40,6 +41,7 @@ void store(const Invoice &invoice, StorerT &storer) { STORE_FLAG(invoice.send_phone_number_to_provider); STORE_FLAG(invoice.send_email_address_to_provider); STORE_FLAG(has_tip); + STORE_FLAG(is_recurring); END_STORE_FLAGS(); store(invoice.currency, storer); store(invoice.price_parts, storer); @@ -47,11 +49,15 @@ void store(const Invoice &invoice, StorerT &storer) { store(invoice.max_tip_amount, storer); store(invoice.suggested_tip_amounts, storer); } + if (is_recurring) { + store(invoice.recurring_payment_terms_of_service_url, storer); + } } template void parse(Invoice &invoice, ParserT &parser) { bool has_tip; + bool is_recurring; BEGIN_PARSE_FLAGS(); PARSE_FLAG(invoice.is_test); PARSE_FLAG(invoice.need_name); @@ -62,6 +68,7 @@ void parse(Invoice &invoice, ParserT &parser) { PARSE_FLAG(invoice.send_phone_number_to_provider); PARSE_FLAG(invoice.send_email_address_to_provider); PARSE_FLAG(has_tip); + PARSE_FLAG(is_recurring); END_PARSE_FLAGS(); parse(invoice.currency, parser); parse(invoice.price_parts, parser); @@ -69,6 +76,9 @@ void parse(Invoice &invoice, ParserT &parser) { parse(invoice.max_tip_amount, parser); parse(invoice.suggested_tip_amounts, parser); } + if (is_recurring) { + parse(invoice.recurring_payment_terms_of_service_url, parser); + } } template diff --git a/td/telegram/Photo.cpp b/td/telegram/Photo.cpp index c20bc5705..c7d0822d0 100644 --- a/td/telegram/Photo.cpp +++ b/td/telegram/Photo.cpp @@ -20,7 +20,6 @@ #include "td/utils/common.h" #include "td/utils/format.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" #include "td/utils/SliceBuilder.h" #include @@ -307,7 +306,8 @@ Photo get_photo(FileManager *file_manager, tl_object_ptr && owner_dialog_id, std::move(size_ptr), PhotoFormat::Jpeg); if (photo_size.get_offset() == 0) { PhotoSize &size = photo_size.get<0>(); - if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'u' || size.type == 'v') { + if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'p' || size.type == 'u' || + size.type == 'v') { LOG(ERROR) << "Skip unallowed photo size " << size; continue; } @@ -358,10 +358,23 @@ tl_object_ptr get_chat_photo_object(FileManager *file_manager return nullptr; } - const AnimationSize *animation = photo.animations.empty() ? nullptr : &photo.animations.back(); + const AnimationSize *small_animation = nullptr; + const AnimationSize *big_animation = nullptr; + for (auto &animation : photo.animations) { + if (animation.type == 'p') { + small_animation = &animation; + } else if (animation.type == 'u') { + big_animation = &animation; + } + } + if (big_animation == nullptr && small_animation != nullptr) { + LOG(ERROR) << "Have small animation without big animation in " << photo; + small_animation = nullptr; + } return td_api::make_object( photo.id.get(), photo.date, get_minithumbnail_object(photo.minithumbnail), - get_photo_sizes_object(file_manager, photo.photos), get_animated_chat_photo_object(file_manager, animation)); + get_photo_sizes_object(file_manager, photo.photos), get_animated_chat_photo_object(file_manager, big_animation), + get_animated_chat_photo_object(file_manager, small_animation)); } void photo_delete_thumbnail(Photo &photo) { @@ -490,11 +503,15 @@ SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const P if (thumbnail_file_id.is_valid() && thumbnail.empty()) { return {}; } + auto size = file_view.size(); + if (size < 0 || size >= 1000000000) { + size = 0; + } return SecretInputMedia{ std::move(input_file), make_tl_object( - std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, narrow_cast(file_view.size()), + std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, static_cast(size), BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), caption)}; } diff --git a/td/telegram/PhotoSize.cpp b/td/telegram/PhotoSize.cpp index 32aeb8f6e..62b2befe1 100644 --- a/td/telegram/PhotoSize.cpp +++ b/td/telegram/PhotoSize.cpp @@ -259,6 +259,10 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo if (source.get_type("get_photo_size") == PhotoSizeSource::Type::Thumbnail) { source.thumbnail().thumbnail_type = res.type; } + if (res.size < 0 || res.size > 1000000000) { + LOG(ERROR) << "Receive photo of size " << res.size; + res.size = 0; + } res.file_id = register_photo_size(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id, res.size, dc_id, format); @@ -275,8 +279,8 @@ AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource sour tl_object_ptr &&size) { CHECK(size != nullptr); AnimationSize res; - if (size->type_ != "v" && size->type_ != "u") { - LOG(ERROR) << "Wrong videoSize \"" << size->type_ << "\" in " << to_string(size); + if (size->type_ != "p" && size->type_ != "u" && size->type_ != "v") { + LOG(ERROR) << "Unsupported videoSize \"" << size->type_ << "\" in " << to_string(size); } res.type = static_cast(size->type_[0]); if (res.type >= 128) { @@ -292,6 +296,10 @@ AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource sour if (source.get_type("get_animation_size") == PhotoSizeSource::Type::Thumbnail) { source.thumbnail().thumbnail_type = res.type; } + if (res.size < 0 || res.size > 1000000000) { + LOG(ERROR) << "Receive animation of size " << res.size; + res.size = 0; + } res.file_id = register_photo_size(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id, res.size, dc_id, PhotoFormat::Mpeg4); @@ -318,9 +326,9 @@ PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_t } auto http_url = r_http_url.move_as_ok(); auto url = http_url.get_url(); - file_id = file_manager->register_remote(FullRemoteFileLocation(file_type, url, web_document->access_hash_), - FileLocationSource::FromServer, owner_dialog_id, 0, web_document->size_, - get_url_query_file_name(http_url.query_)); + file_id = file_manager->register_remote( + FullRemoteFileLocation(file_type, url, web_document->access_hash_), FileLocationSource::FromServer, + owner_dialog_id, 0, static_cast(web_document->size_), get_url_query_file_name(http_url.query_)); size = web_document->size_; mime_type = std::move(web_document->mime_type_); attributes = std::move(web_document->attributes_); @@ -379,6 +387,11 @@ PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_t s.dimensions = dimensions; s.size = size; s.file_id = file_id; + + if (s.size < 0 || s.size > 1000000000) { + LOG(ERROR) << "Receive web photo of size " << s.size; + s.size = 0; + } return s; } diff --git a/td/telegram/PhotoSize.h b/td/telegram/PhotoSize.h index 2c3eb90f6..bf64f474d 100644 --- a/td/telegram/PhotoSize.h +++ b/td/telegram/PhotoSize.h @@ -27,8 +27,8 @@ class FileManager; struct PhotoSize { int32 type = 0; - Dimensions dimensions; int32 size = 0; + Dimensions dimensions; FileId file_id; vector progressive_sizes; }; diff --git a/td/telegram/PollManager.cpp b/td/telegram/PollManager.cpp index 91ede435d..57d85dd15 100644 --- a/td/telegram/PollManager.cpp +++ b/td/telegram/PollManager.cpp @@ -675,6 +675,10 @@ void PollManager::unregister_poll(PollId poll_id, FullMessageId full_message_id, auto &message_ids = other_poll_messages_[poll_id]; auto is_deleted = message_ids.erase(full_message_id) > 0; LOG_CHECK(is_deleted) << source << ' ' << poll_id << ' ' << full_message_id; + if (is_local_poll_id(poll_id)) { + CHECK(message_ids.empty()); + forget_local_poll(poll_id); + } if (message_ids.empty()) { other_poll_messages_.erase(poll_id); @@ -686,6 +690,10 @@ void PollManager::unregister_poll(PollId poll_id, FullMessageId full_message_id, auto &message_ids = server_poll_messages_[poll_id]; auto is_deleted = message_ids.erase(full_message_id) > 0; LOG_CHECK(is_deleted) << source << ' ' << poll_id << ' ' << full_message_id; + if (is_local_poll_id(poll_id)) { + CHECK(message_ids.empty()); + forget_local_poll(poll_id); + } if (message_ids.empty()) { server_poll_messages_.erase(poll_id); update_poll_timeout_.cancel_timeout(poll_id.get()); @@ -1287,7 +1295,16 @@ void PollManager::on_unload_poll_timeout(PollId poll_id) { if (G()->close_flag()) { return; } - CHECK(!is_local_poll_id(poll_id)); + if (is_local_poll_id(poll_id)) { + LOG(INFO) << "Forget " << poll_id; + + auto is_deleted = polls_.erase(poll_id) > 0; + CHECK(is_deleted); + + CHECK(poll_voters_.count(poll_id) == 0); + CHECK(loaded_from_database_polls_.count(poll_id) == 0); + return; + } if (!can_unload_poll(poll_id)) { return; @@ -1309,6 +1326,11 @@ void PollManager::on_unload_poll_timeout(PollId poll_id) { unload_poll_timeout_.cancel_timeout(poll_id.get()); } +void PollManager::forget_local_poll(PollId poll_id) { + CHECK(is_local_poll_id(poll_id)); + unload_poll_timeout_.set_timeout_in(poll_id.get(), UNLOAD_POLL_DELAY); +} + void PollManager::on_get_poll_results(PollId poll_id, uint64 generation, Result> result) { auto poll = get_poll(poll_id); @@ -1692,7 +1714,7 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptrexplanation != explanation && (!is_min || poll_server_is_closed)) { if (explanation.text.empty() && !poll->explanation.text.empty()) { - LOG(ERROR) << "Can't change known " << poll_id << " explanation to empty from " << source ; + LOG(ERROR) << "Can't change known " << poll_id << " explanation to empty from " << source; } else { poll->explanation = std::move(explanation); is_changed = true; diff --git a/td/telegram/PollManager.h b/td/telegram/PollManager.h index 8057ca396..16e899d2b 100644 --- a/td/telegram/PollManager.h +++ b/td/telegram/PollManager.h @@ -215,6 +215,8 @@ class PollManager final : public Actor { void do_stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr &&reply_markup, uint64 log_event_id, Promise &&promise); + void forget_local_poll(PollId poll_id); + MultiTimeout update_poll_timeout_{"UpdatePollTimeout"}; MultiTimeout close_poll_timeout_{"ClosePollTimeout"}; MultiTimeout unload_poll_timeout_{"UnloadPollTimeout"}; diff --git a/td/telegram/Premium.cpp b/td/telegram/Premium.cpp new file mode 100644 index 000000000..1a625aef6 --- /dev/null +++ b/td/telegram/Premium.cpp @@ -0,0 +1,380 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/Premium.h" + +#include "td/telegram/AnimationsManager.h" +#include "td/telegram/Application.h" +#include "td/telegram/ConfigShared.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/Document.h" +#include "td/telegram/DocumentsManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/Status.h" + +namespace td { + +static td_api::object_ptr get_premium_feature_object(Slice premium_feature) { + if (premium_feature == "double_limits") { + return td_api::make_object(); + } + if (premium_feature == "more_upload") { + return td_api::make_object(); + } + if (premium_feature == "faster_download") { + return td_api::make_object(); + } + if (premium_feature == "voice_to_text") { + return td_api::make_object(); + } + if (premium_feature == "no_ads") { + return td_api::make_object(); + } + if (premium_feature == "unique_reactions") { + return td_api::make_object(); + } + if (premium_feature == "premium_stickers") { + return td_api::make_object(); + } + if (premium_feature == "advanced_chat_management") { + return td_api::make_object(); + } + if (premium_feature == "profile_badge") { + return td_api::make_object(); + } + if (premium_feature == "animated_userpics") { + return td_api::make_object(); + } + if (premium_feature == "app_icons") { + return td_api::make_object(); + } + return nullptr; +} + +class GetPremiumPromoQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetPremiumPromoQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::help_getPremiumPromo())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto promo = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetPremiumPromoQuery: " << to_string(promo); + + td_->contacts_manager_->on_get_users(std::move(promo->users_), "GetPremiumPromoQuery"); + + auto state = get_message_text(td_->contacts_manager_.get(), std::move(promo->status_text_), + std::move(promo->status_entities_), true, true, 0, false, "GetPremiumPromoQuery"); + + if (promo->video_sections_.size() != promo->videos_.size()) { + return on_error(Status::Error(500, "Receive wrong number of videos")); + } + + if (promo->monthly_amount_ < 0 || promo->monthly_amount_ > 9999'9999'9999) { + return on_error(Status::Error(500, "Receive invalid monthly amount")); + } + + if (promo->currency_.size() != 3) { + return on_error(Status::Error(500, "Receive invalid currency")); + } + + vector> animations; + for (size_t i = 0; i < promo->video_sections_.size(); i++) { + auto feature = get_premium_feature_object(promo->video_sections_[i]); + if (feature == nullptr) { + continue; + } + + auto video = std::move(promo->videos_[i]); + if (video->get_id() != telegram_api::document::ID) { + LOG(ERROR) << "Receive " << to_string(video) << " for " << promo->video_sections_[i]; + continue; + } + + auto parsed_document = td_->documents_manager_->on_get_document(move_tl_object_as(video), + DialogId(), nullptr, Document::Type::Animation); + + if (parsed_document.type != Document::Type::Animation) { + LOG(ERROR) << "Receive " << parsed_document.type << " for " << promo->video_sections_[i]; + continue; + } + + auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id); + animations.push_back(td_api::make_object(std::move(feature), + std::move(animation_object))); + } + + promise_.set_value(td_api::make_object(get_formatted_text_object(state, true, 0), + std::move(promo->currency_), promo->monthly_amount_, + std::move(animations))); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +const vector &get_premium_limit_keys() { + static const vector limit_keys{"channels", + "saved_gifs", + "stickers_faved", + "dialog_filters", + "dialog_filters_chats", + "dialogs_pinned", + "dialogs_folder_pinned", + "channels_public", + "caption_length", + "about_length"}; + return limit_keys; +} + +static Slice get_limit_type_key(const td_api::PremiumLimitType *limit_type) { + CHECK(limit_type != nullptr); + switch (limit_type->get_id()) { + case td_api::premiumLimitTypeSupergroupCount::ID: + return Slice("channels"); + case td_api::premiumLimitTypeSavedAnimationCount::ID: + return Slice("saved_gifs"); + case td_api::premiumLimitTypeFavoriteStickerCount::ID: + return Slice("stickers_faved"); + case td_api::premiumLimitTypeChatFilterCount::ID: + return Slice("dialog_filters"); + case td_api::premiumLimitTypeChatFilterChosenChatCount::ID: + return Slice("dialog_filters_chats"); + case td_api::premiumLimitTypePinnedChatCount::ID: + return Slice("dialogs_pinned"); + case td_api::premiumLimitTypePinnedArchivedChatCount::ID: + return Slice("dialogs_folder_pinned"); + case td_api::premiumLimitTypeCreatedPublicChatCount::ID: + return Slice("channels_public"); + case td_api::premiumLimitTypeCaptionLength::ID: + return Slice("caption_length"); + case td_api::premiumLimitTypeBioLength::ID: + return Slice("about_length"); + default: + UNREACHABLE(); + return Slice(); + } +} + +static string get_premium_source(const td_api::PremiumLimitType *limit_type) { + if (limit_type == nullptr) { + return string(); + } + + return PSTRING() << "double_limits__" << get_limit_type_key(limit_type); +} + +static string get_premium_source(const td_api::PremiumFeature *feature) { + if (feature == nullptr) { + return string(); + } + + switch (feature->get_id()) { + case td_api::premiumFeatureIncreasedLimits::ID: + return "double_limits"; + case td_api::premiumFeatureIncreasedUploadFileSize::ID: + return "more_upload"; + case td_api::premiumFeatureImprovedDownloadSpeed::ID: + return "faster_download"; + case td_api::premiumFeatureVoiceRecognition::ID: + return "voice_to_text"; + case td_api::premiumFeatureDisabledAds::ID: + return "no_ads"; + case td_api::premiumFeatureUniqueReactions::ID: + return "unique_reactions"; + case td_api::premiumFeatureUniqueStickers::ID: + return "premium_stickers"; + case td_api::premiumFeatureAdvancedChatManagement::ID: + return "advanced_chat_management"; + case td_api::premiumFeatureProfileBadge::ID: + return "profile_badge"; + case td_api::premiumFeatureAnimatedProfilePhoto::ID: + return "animated_userpics"; + case td_api::premiumFeatureAppIcons::ID: + return "app_icons"; + default: + UNREACHABLE(); + } + return string(); +} + +static string get_premium_source(const td_api::object_ptr &source) { + if (source == nullptr) { + return string(); + } + switch (source->get_id()) { + case td_api::premiumSourceLimitExceeded::ID: { + auto *limit_type = static_cast(source.get())->limit_type_.get(); + return get_premium_source(limit_type); + } + case td_api::premiumSourceFeature::ID: { + auto *feature = static_cast(source.get())->feature_.get(); + return get_premium_source(feature); + } + case td_api::premiumSourceLink::ID: { + auto &referrer = static_cast(source.get())->referrer_; + if (referrer.empty()) { + return "deeplink"; + } + return PSTRING() << "deeplink_" << referrer; + } + case td_api::premiumSourceSettings::ID: + return "settings"; + default: + UNREACHABLE(); + return string(); + } +} + +static td_api::object_ptr get_premium_limit_object(Slice key) { + int32 default_limit = + static_cast(G()->shared_config().get_option_integer(PSLICE() << key << "_limit_default")); + int32 premium_limit = + static_cast(G()->shared_config().get_option_integer(PSLICE() << key << "_limit_premium")); + if (default_limit <= 0 || premium_limit <= default_limit) { + return nullptr; + } + auto type = [&]() -> td_api::object_ptr { + if (key == "channels") { + return td_api::make_object(); + } + if (key == "saved_gifs") { + return td_api::make_object(); + } + if (key == "stickers_faved") { + return td_api::make_object(); + } + if (key == "dialog_filters") { + return td_api::make_object(); + } + if (key == "dialog_filters_chats") { + return td_api::make_object(); + } + if (key == "dialogs_pinned") { + return td_api::make_object(); + } + if (key == "dialogs_folder_pinned") { + return td_api::make_object(); + } + if (key == "channels_public") { + return td_api::make_object(); + } + if (key == "caption_length") { + return td_api::make_object(); + } + if (key == "about_length") { + return td_api::make_object(); + } + UNREACHABLE(); + return nullptr; + }(); + return td_api::make_object(std::move(type), default_limit, premium_limit); +} + +void get_premium_limit(const td_api::object_ptr &limit_type, + Promise> &&promise) { + if (limit_type == nullptr) { + return promise.set_error(Status::Error(400, "Limit type must be non-empty")); + } + + promise.set_value(get_premium_limit_object(get_limit_type_key(limit_type.get()))); +} + +void get_premium_features(Td *td, const td_api::object_ptr &source, + Promise> &&promise) { + auto premium_features = + full_split(G()->shared_config().get_option_string( + "premium_features", + "double_limits,more_upload,faster_download,voice_to_text,no_ads,unique_reactions,premium_stickers," + "advanced_chat_management,profile_badge,animated_userpics,app_icons"), + ','); + vector> features; + for (const auto &premium_feature : premium_features) { + auto feature = get_premium_feature_object(premium_feature); + if (feature != nullptr) { + features.push_back(std::move(feature)); + } + } + + auto limits = transform(get_premium_limit_keys(), get_premium_limit_object); + td::remove_if(limits, [](auto &limit) { return limit == nullptr; }); + + auto source_str = get_premium_source(source); + if (!source_str.empty()) { + vector> data; + vector> promo_order; + for (const auto &premium_feature : premium_features) { + promo_order.push_back(make_tl_object(premium_feature)); + } + data.push_back(make_tl_object( + "premium_promo_order", make_tl_object(std::move(promo_order)))); + data.push_back( + make_tl_object("source", make_tl_object(source_str))); + save_app_log(td, "premium.promo_screen_show", DialogId(), make_tl_object(std::move(data)), + Promise()); + } + + td_api::object_ptr payment_link; + auto premium_bot_username = G()->shared_config().get_option_string("premium_bot_username"); + if (!premium_bot_username.empty()) { + payment_link = td_api::make_object(premium_bot_username, source_str); + } else { + auto premium_invoice_slug = G()->shared_config().get_option_string("premium_invoice_slug"); + if (!premium_invoice_slug.empty()) { + payment_link = td_api::make_object(premium_invoice_slug); + } + } + + promise.set_value( + td_api::make_object(std::move(features), std::move(limits), std::move(payment_link))); +} + +void view_premium_feature(Td *td, const td_api::object_ptr &feature, Promise &&promise) { + auto source = get_premium_source(feature.get()); + if (source.empty()) { + return promise.set_error(Status::Error(400, "Feature must be non-empty")); + } + + vector> data; + data.push_back( + make_tl_object("item", make_tl_object(source))); + save_app_log(td, "premium.promo_screen_tap", DialogId(), make_tl_object(std::move(data)), + std::move(promise)); +} + +void click_premium_subscription_button(Td *td, Promise &&promise) { + vector> data; + save_app_log(td, "premium.promo_screen_accept", DialogId(), make_tl_object(std::move(data)), + std::move(promise)); +} + +void get_premium_state(Td *td, Promise> &&promise) { + td->create_handler(std::move(promise))->send(); +} + +} // namespace td diff --git a/td/telegram/Premium.h b/td/telegram/Premium.h new file mode 100644 index 000000000..7519cb7d9 --- /dev/null +++ b/td/telegram/Premium.h @@ -0,0 +1,34 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" + +#include "td/actor/PromiseFuture.h" + +#include "td/utils/common.h" +#include "td/utils/Slice.h" + +namespace td { + +class Td; + +const vector &get_premium_limit_keys(); + +void get_premium_limit(const td_api::object_ptr &limit_type, + Promise> &&promise); + +void get_premium_features(Td *td, const td_api::object_ptr &source, + Promise> &&promise); + +void view_premium_feature(Td *td, const td_api::object_ptr &feature, Promise &&promise); + +void click_premium_subscription_button(Td *td, Promise &&promise); + +void get_premium_state(Td *td, Promise> &&promise); + +} // namespace td diff --git a/td/telegram/ReplyMarkup.cpp b/td/telegram/ReplyMarkup.cpp index 26815179e..785e18c0b 100644 --- a/td/telegram/ReplyMarkup.cpp +++ b/td/telegram/ReplyMarkup.cpp @@ -537,7 +537,7 @@ static Result get_inline_keyboard_button(tl_object_ptrurl_, true); + auto r_url = LinkManager::check_link(button_type->url_, true, !G()->is_test_dc()); if (r_url.is_error()) { return Status::Error(400, PSLICE() << "Inline keyboard button login " << r_url.error().message()); } diff --git a/td/telegram/SecretChatActor.cpp b/td/telegram/SecretChatActor.cpp index 3347aaaf3..f5e65d9f3 100644 --- a/td/telegram/SecretChatActor.cpp +++ b/td/telegram/SecretChatActor.cpp @@ -633,13 +633,15 @@ void SecretChatActor::check_status(Status status) { if (status.code() == 1) { LOG(WARNING) << "Non-fatal error: " << status; } else { - on_fatal_error(std::move(status)); + on_fatal_error(std::move(status), false); } } } -void SecretChatActor::on_fatal_error(Status status) { - LOG(ERROR) << "Fatal error: " << status; +void SecretChatActor::on_fatal_error(Status status, bool is_expected) { + if (!is_expected) { + LOG(ERROR) << "Fatal error: " << status; + } cancel_chat(false, false, Promise<>()); } @@ -1650,19 +1652,9 @@ void SecretChatActor::on_outbound_send_message_error(uint64 state_id, Status err state = outbound_message_states_.get(state_id); need_sync = true; } - } else { - bool should_fail = false; - if (error.code() == 429) { - should_fail = false; - } else if (error.code() == 400 && error.message() == "ENCRYPTION_DECLINED") { - should_fail = true; - } else { - LOG(ERROR) << "Got unknown error for encrypted service message: " << error; - should_fail = true; - } - if (should_fail) { - return on_fatal_error(std::move(error)); - } + } else if (error.code() != 429) { + return on_fatal_error(std::move(error), + (error.code() == 400 && error.message() == "ENCRYPTION_DECLINED") || error.code() == 403); } auto query = create_net_query(*state->message); state->net_query_id = query->id(); diff --git a/td/telegram/SecretChatActor.h b/td/telegram/SecretChatActor.h index 68605041d..775e3d1c7 100644 --- a/td/telegram/SecretChatActor.h +++ b/td/telegram/SecretChatActor.h @@ -637,7 +637,7 @@ class SecretChatActor final : public NetQueryCallback { void outbound_loop(OutboundMessageState *state, uint64 state_id); // DiscardEncryption - void on_fatal_error(Status status); + void on_fatal_error(Status status, bool is_expected); void do_close_chat_impl(bool delete_history, bool is_already_discarded, uint64 log_event_id, Promise &&promise); void on_closed(uint64 log_event_id, Promise &&promise); diff --git a/td/telegram/SecretChatLayer.h b/td/telegram/SecretChatLayer.h index 92e548ff8..8ce137c93 100644 --- a/td/telegram/SecretChatLayer.h +++ b/td/telegram/SecretChatLayer.h @@ -13,7 +13,8 @@ enum class SecretChatLayer : int32 { Mtproto2 = 73, NewEntities = 101, DeleteMessagesOnClose = 123, - Current = DeleteMessagesOnClose + SupportBigFiles = 143, + Current = SupportBigFiles }; } // namespace td diff --git a/td/telegram/SecretInputMedia.cpp b/td/telegram/SecretInputMedia.cpp index 9ad5290f7..562b0404b 100644 --- a/td/telegram/SecretInputMedia.cpp +++ b/td/telegram/SecretInputMedia.cpp @@ -7,19 +7,34 @@ #include "td/telegram/SecretInputMedia.h" #include "td/telegram/files/FileManager.h" +#include "td/telegram/SecretChatLayer.h" + +#include "td/utils/misc.h" namespace td { SecretInputMedia::SecretInputMedia(tl_object_ptr input_file, BufferSlice &&thumbnail, Dimensions thumbnail_dimensions, const string &mime_type, const FileView &file_view, vector> &&attributes, - const string &caption) + const string &caption, int32 layer) : input_file_(std::move(input_file)) { auto &encryption_key = file_view.encryption_key(); - decrypted_media_ = secret_api::make_object( - std::move(thumbnail), thumbnail_dimensions.width, thumbnail_dimensions.height, mime_type, - narrow_cast(file_view.size()), BufferSlice(encryption_key.key_slice()), - BufferSlice(encryption_key.iv_slice()), std::move(attributes), caption); + auto size = file_view.size(); + if (layer >= static_cast(SecretChatLayer::SupportBigFiles)) { + decrypted_media_ = secret_api::make_object( + std::move(thumbnail), thumbnail_dimensions.width, thumbnail_dimensions.height, mime_type, size, + BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), std::move(attributes), + caption); + return; + } + if (size <= (2000 << 20)) { + decrypted_media_ = secret_api::make_object( + std::move(thumbnail), thumbnail_dimensions.width, thumbnail_dimensions.height, mime_type, + narrow_cast(size), BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), + std::move(attributes), caption); + return; + } + input_file_ = nullptr; } } // namespace td diff --git a/td/telegram/SecretInputMedia.h b/td/telegram/SecretInputMedia.h index e9c620758..5621e04c2 100644 --- a/td/telegram/SecretInputMedia.h +++ b/td/telegram/SecretInputMedia.h @@ -30,7 +30,8 @@ struct SecretInputMedia { SecretInputMedia(tl_object_ptr input_file, BufferSlice &&thumbnail, Dimensions thumbnail_dimensions, const string &mime_type, const FileView &file_view, - vector> &&attributes, const string &caption); + vector> &&attributes, const string &caption, + int32 layer); bool empty() const { return decrypted_media_ == nullptr; diff --git a/td/telegram/SequenceDispatcher.cpp b/td/telegram/SequenceDispatcher.cpp index d980fb02e..7fa0c27a6 100644 --- a/td/telegram/SequenceDispatcher.cpp +++ b/td/telegram/SequenceDispatcher.cpp @@ -264,7 +264,7 @@ void MultiSequenceDispatcherOld::send(NetQueryPtr query) { auto &data = it_ok.first->second; if (it_ok.second) { LOG(DEBUG) << "Create SequenceDispatcher" << sequence_id; - data.dispatcher_ = create_actor("sequence dispatcher", actor_shared(this, sequence_id)); + data.dispatcher_ = create_actor("SequenceDispatcher", actor_shared(this, sequence_id)); } data.cnt_++; query->debug(PSTRING() << "send to SequenceDispatcher " << tag("sequence_id", sequence_id)); diff --git a/td/telegram/SponsoredMessageManager.cpp b/td/telegram/SponsoredMessageManager.cpp index 90103b2d9..0c42e385c 100644 --- a/td/telegram/SponsoredMessageManager.cpp +++ b/td/telegram/SponsoredMessageManager.cpp @@ -90,6 +90,7 @@ class ViewSponsoredMessageQuery final : public Td::ResultHandler { struct SponsoredMessageManager::SponsoredMessage { int64 local_id = 0; + bool is_recommended = false; DialogId sponsor_dialog_id; ServerMessageId server_message_id; string start_param; @@ -97,9 +98,10 @@ struct SponsoredMessageManager::SponsoredMessage { unique_ptr content; SponsoredMessage() = default; - SponsoredMessage(int64 local_id, DialogId sponsor_dialog_id, ServerMessageId server_message_id, string start_param, - string invite_hash, unique_ptr content) + SponsoredMessage(int64 local_id, bool is_recommended, DialogId sponsor_dialog_id, ServerMessageId server_message_id, + string start_param, string invite_hash, unique_ptr content) : local_id(local_id) + , is_recommended(is_recommended) , sponsor_dialog_id(sponsor_dialog_id) , server_message_id(server_message_id) , start_param(std::move(start_param)) @@ -187,8 +189,9 @@ td_api::object_ptr SponsoredMessageManager::get_sponso break; } return td_api::make_object( - sponsored_message.local_id, sponsored_message.sponsor_dialog_id.get(), std::move(chat_invite_link_info), - std::move(link), get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1)); + sponsored_message.local_id, sponsored_message.is_recommended, sponsored_message.sponsor_dialog_id.get(), + std::move(chat_invite_link_info), std::move(link), + get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1)); } td_api::object_ptr SponsoredMessageManager::get_sponsored_message_object( @@ -310,9 +313,10 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( auto local_id = current_sponsored_message_id_.get(); CHECK(!current_sponsored_message_id_.is_valid()); CHECK(!current_sponsored_message_id_.is_scheduled()); - CHECK(messages->message_random_ids.count(local_id) == 0); - messages->message_random_ids[local_id] = sponsored_message->random_id_.as_slice().str(); - messages->messages.emplace_back(local_id, sponsor_dialog_id, server_message_id, + auto is_inserted = + messages->message_random_ids.emplace(local_id, sponsored_message->random_id_.as_slice().str()).second; + CHECK(is_inserted); + messages->messages.emplace_back(local_id, sponsored_message->recommended_, sponsor_dialog_id, server_message_id, std::move(sponsored_message->start_param_), std::move(invite_hash), std::move(content)); } diff --git a/td/telegram/StateManager.cpp b/td/telegram/StateManager.cpp index 1ebdd1938..2dc618895 100644 --- a/td/telegram/StateManager.cpp +++ b/td/telegram/StateManager.cpp @@ -9,7 +9,6 @@ #include "td/actor/PromiseFuture.h" #include "td/actor/SleepActor.h" -#include "td/utils/algorithm.h" #include "td/utils/logging.h" #include "td/utils/Time.h" diff --git a/td/telegram/StickerFormat.cpp b/td/telegram/StickerFormat.cpp index a962c30d2..ddf05a5fa 100644 --- a/td/telegram/StickerFormat.cpp +++ b/td/telegram/StickerFormat.cpp @@ -88,6 +88,21 @@ Slice get_sticker_format_extension(StickerFormat sticker_format) { } } +PhotoFormat get_sticker_format_photo_format(StickerFormat sticker_format) { + switch (sticker_format) { + case StickerFormat::Unknown: + case StickerFormat::Webp: + return PhotoFormat::Webp; + case StickerFormat::Tgs: + return PhotoFormat::Tgs; + case StickerFormat::Webm: + return PhotoFormat::Webm; + default: + UNREACHABLE(); + return PhotoFormat::Webp; + } +} + bool is_sticker_format_animated(StickerFormat sticker_format) { switch (sticker_format) { case StickerFormat::Unknown: diff --git a/td/telegram/StickerFormat.h b/td/telegram/StickerFormat.h index 151d92ebe..6e1d537ee 100644 --- a/td/telegram/StickerFormat.h +++ b/td/telegram/StickerFormat.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/PhotoFormat.h" #include "td/telegram/td_api.h" #include "td/utils/common.h" @@ -28,6 +29,8 @@ string get_sticker_format_mime_type(StickerFormat sticker_format); Slice get_sticker_format_extension(StickerFormat sticker_format); +PhotoFormat get_sticker_format_photo_format(StickerFormat sticker_format); + bool is_sticker_format_animated(StickerFormat sticker_format); bool is_sticker_format_vector(StickerFormat sticker_format); diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index 6769583a8..da55738ac 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -8,6 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/AvailableReaction.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/ContactsManager.h" @@ -35,6 +36,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/TdParameters.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/Version.h" #include "td/db/SqliteKeyValue.h" #include "td/db/SqliteKeyValueAsync.h" @@ -745,10 +747,14 @@ class ReloadSpecialStickerSetQuery final : public Td::ResultHandler { auto set_ptr = result_ptr.move_as_ok(); if (set_ptr->get_id() == telegram_api::messages_stickerSet::ID) { - // sticker_set_id_ needs to be replaced always + // sticker_set_id_ must be replaced always, because it could have been changed + // we must not pass sticker_set_id_ in order to allow its change sticker_set_id_ = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), std::move(set_ptr), true, "ReloadSpecialStickerSetQuery"); - } else if (sticker_set_id_.is_valid()) { + } else { + CHECK(set_ptr->get_id() == telegram_api::messages_stickerSetNotModified::ID); + // we received telegram_api::messages_stickerSetNotModified, and must pass sticker_set_id_ to handle it + // sticker_set_id_ can't be changed by this call td_->stickers_manager_->on_get_messages_sticker_set(sticker_set_id_, std::move(set_ptr), false, "ReloadSpecialStickerSetQuery"); } @@ -1237,21 +1243,31 @@ class StickersManager::StickerListLogEvent { class StickersManager::StickerSetListLogEvent { public: - vector sticker_set_ids; + vector sticker_set_ids_; + bool is_premium_ = false; StickerSetListLogEvent() = default; - explicit StickerSetListLogEvent(vector sticker_set_ids) : sticker_set_ids(std::move(sticker_set_ids)) { + StickerSetListLogEvent(vector sticker_set_ids, bool is_premium) + : sticker_set_ids_(std::move(sticker_set_ids)), is_premium_(is_premium) { } template void store(StorerT &storer) const { - td::store(sticker_set_ids, storer); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_premium_); + END_STORE_FLAGS(); + td::store(sticker_set_ids_, storer); } template void parse(ParserT &parser) { - td::parse(sticker_set_ids, parser); + if (parser.version() >= static_cast(Version::AddStickerSetListFlags)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_premium_); + END_PARSE_FLAGS(); + } + td::parse(sticker_set_ids_, parser); } }; @@ -1872,11 +1888,14 @@ tl_object_ptr StickersManager::get_sticker_object(FileId file_i width = static_cast(width * zoom + 0.5); height = static_cast(height * zoom + 0.5); } - return make_tl_object( + auto premium_animation_object = sticker->premium_animation_file_id.is_valid() + ? td_->file_manager_->get_file_object(sticker->premium_animation_file_id) + : nullptr; + return td_api::make_object( sticker->set_id.get(), width, height, sticker->alt, get_sticker_type_object(sticker->format, sticker->is_mask, std::move(mask_position)), get_sticker_minithumbnail(sticker->minithumbnail, sticker->set_id, document_id, zoom), - std::move(thumbnail_object), td_->file_manager_->get_file_object(file_id)); + std::move(thumbnail_object), std::move(premium_animation_object), td_->file_manager_->get_file_object(file_id)); } tl_object_ptr StickersManager::get_stickers_object(const vector &sticker_ids) const { @@ -2016,7 +2035,7 @@ tl_object_ptr StickersManager::get_sticker_sets_object(int3 vector> result; result.reserve(sticker_set_ids.size()); for (auto sticker_set_id : sticker_set_ids) { - auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, covers_limit); + auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, covers_limit, false); if (sticker_set_info->size_ != 0) { result.push_back(std::move(sticker_set_info)); } @@ -2033,30 +2052,57 @@ tl_object_ptr StickersManager::get_sticker_sets_object(int3 } tl_object_ptr StickersManager::get_sticker_set_info_object(StickerSetId sticker_set_id, - size_t covers_limit) const { + size_t covers_limit, + bool prefer_premium) const { const StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); CHECK(sticker_set->is_inited); sticker_set->was_update_sent = true; - std::vector> stickers; - for (auto sticker_id : sticker_set->sticker_ids) { - stickers.push_back(get_sticker_object(sticker_id)); - if (stickers.size() >= covers_limit) { - break; + vector> stickers; + if (prefer_premium) { + vector regular_sticker_ids; + vector premium_sticker_ids; + std::tie(regular_sticker_ids, premium_sticker_ids) = split_stickers_by_premium(sticker_set->sticker_ids); + auto is_premium = G()->shared_config().get_option_boolean("is_premium"); + size_t max_premium_stickers = is_premium ? covers_limit : 1; + if (premium_sticker_ids.size() > max_premium_stickers) { + premium_sticker_ids.resize(max_premium_stickers); + } + CHECK(premium_sticker_ids.size() <= covers_limit); + if (regular_sticker_ids.size() > covers_limit - premium_sticker_ids.size()) { + regular_sticker_ids.resize(covers_limit - premium_sticker_ids.size()); + } + if (!is_premium) { + std::swap(premium_sticker_ids, regular_sticker_ids); + } + + append(premium_sticker_ids, regular_sticker_ids); + for (auto sticker_id : premium_sticker_ids) { + stickers.push_back(get_sticker_object(sticker_id)); + if (stickers.size() >= covers_limit) { + break; + } + } + } else { + for (auto sticker_id : sticker_set->sticker_ids) { + stickers.push_back(get_sticker_object(sticker_id)); + if (stickers.size() >= covers_limit) { + break; + } } } auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set->sticker_format); auto thumbnail = get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail, thumbnail_format); + auto actual_count = narrow_cast(sticker_set->sticker_ids.size()); return make_tl_object( sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail), get_sticker_minithumbnail(sticker_set->minithumbnail, sticker_set->id, -3, get_sticker_set_minithumbnail_zoom(sticker_set)), sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official, get_sticker_type_object(sticker_set->sticker_format, sticker_set->is_masks, nullptr), sticker_set->is_viewed, - sticker_set->was_loaded ? narrow_cast(sticker_set->sticker_ids.size()) : sticker_set->sticker_count, - std::move(stickers)); + sticker_set->was_loaded ? actual_count : max(actual_count, sticker_set->sticker_count), std::move(stickers)); } const StickersManager::StickerSet *StickersManager::get_animated_emoji_sticker_set() { @@ -2195,6 +2241,7 @@ FileId StickersManager::on_get_sticker(unique_ptr new_sticker, bool rep << s->m_thumbnail << " to " << new_sticker->m_thumbnail; s->m_thumbnail = std::move(new_sticker->m_thumbnail); } + s->premium_animation_file_id = new_sticker->premium_animation_file_id; if (s->format != new_sticker->format && new_sticker->format != StickerFormat::Unknown) { s->format = new_sticker->format; } @@ -2290,6 +2337,7 @@ std::pair StickersManager::on_get_sticker_document(tl_object_ptr< PhotoSize thumbnail; string minithumbnail; auto thumbnail_format = has_webp_thumbnail(document->thumbs_) ? PhotoFormat::Webp : PhotoFormat::Jpeg; + FileId premium_animation_file_id; for (auto &thumb : document->thumbs_) { auto photo_size = get_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0), document_id, document->access_hash_, document->file_reference_.as_slice().str(), @@ -2305,9 +2353,19 @@ std::pair StickersManager::on_get_sticker_document(tl_object_ptr< } } } + for (auto &thumb : document->video_thumbs_) { + if (thumb->type_ == "f") { + if (!premium_animation_file_id.is_valid()) { + premium_animation_file_id = + register_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 'f'), + document_id, document->access_hash_, document->file_reference_.as_slice().str(), + DialogId(), thumb->size_, dc_id, get_sticker_format_photo_format(format)); + } + } + } - create_sticker(sticker_id, std::move(minithumbnail), std::move(thumbnail), dimensions, std::move(sticker), format, - nullptr); + create_sticker(sticker_id, premium_animation_file_id, std::move(minithumbnail), std::move(thumbnail), dimensions, + std::move(sticker), format, nullptr); return {document_id, sticker_id}; } @@ -2448,6 +2506,9 @@ vector StickersManager::get_sticker_file_ids(FileId file_id) const { if (sticker->m_thumbnail.file_id.is_valid()) { result.push_back(sticker->m_thumbnail.file_id); } + if (sticker->premium_animation_file_id.is_valid()) { + result.push_back(sticker->premium_animation_file_id); + } return result; } @@ -2458,7 +2519,7 @@ FileId StickersManager::dup_sticker(FileId new_id, FileId old_id) { CHECK(new_sticker == nullptr); new_sticker = make_unique(*old_sticker); new_sticker->file_id = new_id; - // there is no reason to dup m_thumbnail + // there is no reason to dup m_thumbnail and premium_animation_file_id new_sticker->s_thumbnail.file_id = td_->file_manager_->dup_file_id(new_sticker->s_thumbnail.file_id); return new_id; } @@ -2624,7 +2685,8 @@ void StickersManager::add_sticker_thumbnail(Sticker *s, PhotoSize thumbnail) { LOG(ERROR) << "Receive sticker thumbnail of unsupported type " << thumbnail.type; } -void StickersManager::create_sticker(FileId file_id, string minithumbnail, PhotoSize thumbnail, Dimensions dimensions, +void StickersManager::create_sticker(FileId file_id, FileId premium_animation_file_id, string minithumbnail, + PhotoSize thumbnail, Dimensions dimensions, tl_object_ptr sticker, StickerFormat format, MultiPromiseActor *load_data_multipromise_ptr) { if (format == StickerFormat::Unknown && sticker == nullptr) { @@ -2654,6 +2716,7 @@ void StickersManager::create_sticker(FileId file_id, string minithumbnail, Photo s->minithumbnail = std::move(minithumbnail); } add_sticker_thumbnail(s.get(), std::move(thumbnail)); + s->premium_animation_file_id = premium_animation_file_id; if (sticker != nullptr) { s->set_id = on_get_input_sticker_set(file_id, std::move(sticker->stickerset_), load_data_multipromise_ptr); s->alt = std::move(sticker->alt_); @@ -2671,7 +2734,7 @@ void StickersManager::create_sticker(FileId file_id, string minithumbnail, Photo } } s->format = format; - on_get_sticker(std::move(s), sticker != nullptr); + on_get_sticker(std::move(s), sticker != nullptr && load_data_multipromise_ptr == nullptr); } bool StickersManager::has_input_media(FileId sticker_file_id, bool is_secret) const { @@ -2710,7 +2773,7 @@ bool StickersManager::has_input_media(FileId sticker_file_id, bool is_secret) co SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id, tl_object_ptr input_file, - BufferSlice thumbnail) const { + BufferSlice thumbnail, int32 layer) const { const Sticker *sticker = get_sticker(sticker_file_id); CHECK(sticker != nullptr); auto file_view = td_->file_manager_->get_file_view(sticker_file_id); @@ -2759,7 +2822,8 @@ SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id, get_sticker_format_mime_type(sticker->format), file_view, std::move(attributes), - string()}; + string(), + layer}; } else { CHECK(!file_view.is_encrypted()); auto &remote_location = file_view.remote_location(); @@ -2768,6 +2832,10 @@ SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id, LOG(ERROR) << "Have a web sticker in " << sticker->set_id; return {}; } + if (file_view.size() > 1000000000) { + LOG(ERROR) << "Have a sticker of size " << file_view.size() << " in " << sticker->set_id; + return {}; + } return SecretInputMedia{ nullptr, make_tl_object( remote_location.get_id(), remote_location.get_access_hash(), 0 /*date*/, @@ -3028,6 +3096,7 @@ StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_s CHECK(s->is_inited); CHECK(s->was_loaded); + s->is_loaded = true; s->expires_at = G()->unix_time() + (td_->auth_manager_->is_bot() ? Random::fast(10 * 60, 15 * 60) : Random::fast(30 * 60, 50 * 60)); } @@ -3037,7 +3106,7 @@ StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_s auto set_id = on_get_sticker_set(std::move(set->set_), is_changed, source); if (!set_id.is_valid()) { - return set_id; + return StickerSetId(); } if (sticker_set_id.is_valid() && sticker_set_id != set_id) { LOG(ERROR) << "Expected " << sticker_set_id << ", but receive " << set_id << " from " << source; @@ -3210,10 +3279,11 @@ void StickersManager::on_get_special_sticker_set(const SpecialStickerSetType &ty td_api::object_ptr StickersManager::get_update_reactions_object() const { auto reactions = transform(reactions_.reactions_, [this](const Reaction &reaction) { return td_api::make_object( - reaction.reaction_, reaction.title_, reaction.is_active_, get_sticker_object(reaction.static_icon_), - get_sticker_object(reaction.appear_animation_), get_sticker_object(reaction.select_animation_), - get_sticker_object(reaction.activate_animation_), get_sticker_object(reaction.effect_animation_), - get_sticker_object(reaction.around_animation_), get_sticker_object(reaction.center_animation_)); + reaction.reaction_, reaction.title_, reaction.is_active_, reaction.is_premium_, + get_sticker_object(reaction.static_icon_), get_sticker_object(reaction.appear_animation_), + get_sticker_object(reaction.select_animation_), get_sticker_object(reaction.activate_animation_), + get_sticker_object(reaction.effect_animation_), get_sticker_object(reaction.around_animation_), + get_sticker_object(reaction.center_animation_)); }); return td_api::make_object(std::move(reactions)); } @@ -3251,10 +3321,10 @@ void StickersManager::load_reactions() { } void StickersManager::update_active_reactions() { - vector active_reactions; + vector active_reactions; for (auto &reaction : reactions_.reactions_) { if (reaction.is_active_) { - active_reactions.push_back(reaction.reaction_); + active_reactions.emplace_back(reaction.reaction_, reaction.is_premium_); } } td_->messages_manager_->set_active_reactions(std::move(active_reactions)); @@ -3282,6 +3352,7 @@ void StickersManager::on_get_available_reactions( for (auto &available_reaction : available_reactions->reactions_) { Reaction reaction; reaction.is_active_ = !available_reaction->inactive_; + reaction.is_premium_ = available_reaction->premium_; reaction.reaction_ = std::move(available_reaction->reaction_); reaction.title_ = std::move(available_reaction->title_); reaction.static_icon_ = @@ -3397,6 +3468,24 @@ void StickersManager::on_get_installed_sticker_sets_failed(bool is_masks, Status fail_promises(load_installed_sticker_sets_queries_[is_masks], std::move(error)); } +std::pair, vector> StickersManager::split_stickers_by_premium( + const vector &sticker_ids) const { + vector regular_sticker_ids; + vector premium_sticker_ids; + for (const auto &sticker_id : sticker_ids) { + if (sticker_id.is_valid()) { + const Sticker *s = get_sticker(sticker_id); + CHECK(s != nullptr); + if (s->premium_animation_file_id.is_valid()) { + premium_sticker_ids.push_back(sticker_id); + } else { + regular_sticker_ids.push_back(sticker_id); + } + } + } + return {std::move(regular_sticker_ids), std::move(premium_sticker_ids)}; +} + vector StickersManager::get_stickers(string emoji, int32 limit, bool force, Promise &&promise) { if (limit <= 0) { promise.set_error(Status::Error(400, "Parameter limit must be positive")); @@ -3578,15 +3667,55 @@ vector StickersManager::get_stickers(string emoji, int32 limit, bool for } } if (sorted.size() != limit_size_t) { - for (const auto &sticker_id : result) { - if (sticker_id.is_valid()) { - LOG(INFO) << "Add sticker " << sticker_id << " from installed sticker set"; + vector regular_sticker_ids; + vector premium_sticker_ids; + std::tie(regular_sticker_ids, premium_sticker_ids) = split_stickers_by_premium(result); + if (G()->shared_config().get_option_boolean("is_premium")) { + auto normal_count = G()->shared_config().get_option_integer("stickers_normal_by_emoji_per_premium_num", 2); + if (normal_count < 0) { + normal_count = 2; + } + if (normal_count > 10) { + normal_count = 10; + } + // premium users have normal_count normal stickers per each premium + size_t normal_pos = 0; + size_t premium_pos = 0; + normal_count++; + for (size_t pos = 1; normal_pos < regular_sticker_ids.size() || premium_pos < premium_sticker_ids.size(); + pos++) { + if (pos % normal_count == 0 && premium_pos < premium_sticker_ids.size()) { + auto sticker_id = premium_sticker_ids[premium_pos++]; + LOG(INFO) << "Add premium sticker " << sticker_id << " from installed sticker set"; + sorted.push_back(sticker_id); + } else if (normal_pos < regular_sticker_ids.size()) { + auto sticker_id = regular_sticker_ids[normal_pos++]; + LOG(INFO) << "Add normal sticker " << sticker_id << " from installed sticker set"; + sorted.push_back(sticker_id); + } + if (sorted.size() == limit_size_t) { + break; + } + } + } else { + for (const auto &sticker_id : regular_sticker_ids) { + LOG(INFO) << "Add normal sticker " << sticker_id << " from installed sticker set"; sorted.push_back(sticker_id); if (sorted.size() == limit_size_t) { break; } - } else { - LOG(INFO) << "Skip already added sticker"; + } + if (sorted.size() < limit_size_t) { + auto premium_count = G()->shared_config().get_option_integer("stickers_premium_by_emoji_num", 0); + if (premium_count > 0) { + for (const auto &sticker_id : premium_sticker_ids) { + LOG(INFO) << "Add premium sticker " << sticker_id << " from installed sticker set"; + sorted.push_back(sticker_id); + if (sorted.size() == limit_size_t || --premium_count == 0) { + break; + } + } + } } } } @@ -3999,9 +4128,10 @@ void StickersManager::on_load_installed_sticker_sets_from_database(bool is_masks LOG(ERROR) << "Can't load installed sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_installed_sticker_sets(is_masks, true); } + CHECK(!log_event.is_premium_); vector sets_to_load; - for (auto sticker_set_id : log_event.sticker_set_ids) { + for (auto sticker_set_id : log_event.sticker_set_ids_) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->is_inited) { @@ -4013,7 +4143,7 @@ void StickersManager::on_load_installed_sticker_sets_from_database(bool is_masks load_sticker_sets_without_stickers( std::move(sets_to_load), PromiseCreator::lambda( - [is_masks, sticker_set_ids = std::move(log_event.sticker_set_ids)](Result<> result) mutable { + [is_masks, sticker_set_ids = std::move(log_event.sticker_set_ids_)](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::on_load_installed_sticker_sets_finished, is_masks, std::move(sticker_set_ids), true); @@ -4587,6 +4717,37 @@ void StickersManager::get_animated_emoji(string emoji, bool is_recursive, get_animated_emoji_sound_file_id(emoji))); } +void StickersManager::get_all_animated_emojis(bool is_recursive, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji()); + auto sticker_set = get_sticker_set(special_sticker_set.id_); + if (sticker_set == nullptr || !sticker_set->was_loaded) { + if (is_recursive) { + return promise.set_value(td_api::make_object()); + } + + pending_get_animated_emoji_queries_.push_back(PromiseCreator::lambda( + [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &StickersManager::get_all_animated_emojis, true, std::move(promise)); + } + })); + load_special_sticker_set(special_sticker_set); + return; + } + + auto emojis = transform(sticker_set->sticker_ids, [&](FileId sticker_id) { + auto s = get_sticker(sticker_id); + CHECK(s != nullptr); + return s->alt; + }); + promise.set_value(td_api::make_object(std::move(emojis))); +} + void StickersManager::get_animated_emoji_click_sticker(const string &message_text, FullMessageId full_message_id, Promise> &&promise) { if (disable_animated_emojis_ || td_->auth_manager_->is_bot()) { @@ -5087,8 +5248,8 @@ void StickersManager::on_get_archived_sticker_sets( send_update_installed_sticker_sets(); } -std::pair> StickersManager::get_featured_sticker_sets(int32 offset, int32 limit, - Promise &&promise) { +td_api::object_ptr StickersManager::get_featured_sticker_sets(int32 offset, int32 limit, + Promise &&promise) { if (offset < 0) { promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); return {}; @@ -5109,20 +5270,20 @@ std::pair> StickersManager::get_featured_sticker_set reload_featured_sticker_sets(false); auto set_count = static_cast(featured_sticker_set_ids_.size()); - auto total_count = set_count + (old_featured_sticker_set_count_ == -1 ? 1 : old_featured_sticker_set_count_); if (offset < set_count) { if (limit > set_count - offset) { limit = set_count - offset; } promise.set_value(Unit()); auto begin = featured_sticker_set_ids_.begin() + offset; - return {total_count, {begin, begin + limit}}; + return get_trending_sticker_sets_object({begin, begin + limit}); } if (offset == set_count && are_old_featured_sticker_sets_invalidated_) { invalidate_old_featured_sticker_sets(); } + auto total_count = set_count + (old_featured_sticker_set_count_ == -1 ? 1 : old_featured_sticker_set_count_); if (offset < total_count || old_featured_sticker_set_count_ == -1) { offset -= set_count; set_count = static_cast(old_featured_sticker_set_ids_.size()); @@ -5132,7 +5293,7 @@ std::pair> StickersManager::get_featured_sticker_set } promise.set_value(Unit()); auto begin = old_featured_sticker_set_ids_.begin() + offset; - return {total_count, {begin, begin + limit}}; + return get_trending_sticker_sets_object({begin, begin + limit}); } if (offset > set_count) { promise.set_error( @@ -5145,7 +5306,7 @@ std::pair> StickersManager::get_featured_sticker_set } promise.set_value(Unit()); - return {total_count, vector()}; + return get_trending_sticker_sets_object({}); } void StickersManager::on_old_featured_sticker_sets_invalidated() { @@ -5230,6 +5391,14 @@ void StickersManager::on_get_featured_sticker_sets( CHECK(constructor_id == telegram_api::messages_featuredStickers::ID); auto featured_stickers = move_tl_object_as(sticker_sets_ptr); + if (featured_stickers->premium_ != are_featured_sticker_sets_premium_) { + on_old_featured_sticker_sets_invalidated(); + if (offset >= 0) { + featured_stickers->premium_ = are_featured_sticker_sets_premium_; + reload_featured_sticker_sets(true); + } + } + if (offset >= 0 && generation == old_featured_sticker_set_generation_) { set_old_featured_sticker_set_count(featured_stickers->count_); // the count will be fixed in on_load_old_featured_sticker_sets_finished @@ -5258,7 +5427,7 @@ void StickersManager::on_get_featured_sticker_sets( set->is_changed = true; } - update_sticker_set(set, "on_get_archived_sticker_sets 2"); + update_sticker_set(set, "on_get_featured_sticker_sets 2"); featured_sticker_set_ids.push_back(set_id); } @@ -5270,7 +5439,7 @@ void StickersManager::on_get_featured_sticker_sets( if (G()->parameters().use_file_db && !G()->close_flag()) { LOG(INFO) << "Save old trending sticker sets to database with offset " << old_featured_sticker_set_ids_.size(); CHECK(old_featured_sticker_set_ids_.size() % OLD_FEATURED_STICKER_SET_SLICE_SIZE == 0); - StickerSetListLogEvent log_event(featured_sticker_set_ids); + StickerSetListLogEvent log_event(featured_sticker_set_ids, false); G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sssoldfeatured" << old_featured_sticker_set_ids_.size(), log_event_store(log_event).as_slice().str(), Auto()); } @@ -5281,7 +5450,7 @@ void StickersManager::on_get_featured_sticker_sets( return; } - on_load_featured_sticker_sets_finished(std::move(featured_sticker_set_ids)); + on_load_featured_sticker_sets_finished(std::move(featured_sticker_set_ids), featured_stickers->premium_); LOG_IF(ERROR, featured_sticker_sets_hash_ != featured_stickers->hash_) << "Trending sticker sets hash mismatch"; @@ -5290,7 +5459,7 @@ void StickersManager::on_get_featured_sticker_sets( } LOG(INFO) << "Save trending sticker sets to database"; - StickerSetListLogEvent log_event(featured_sticker_set_ids_); + StickerSetListLogEvent log_event(featured_sticker_set_ids_, are_featured_sticker_sets_premium_); G()->td_db()->get_sqlite_pmc()->set("sssfeatured", log_event_store(log_event).as_slice().str(), Auto()); } @@ -5353,7 +5522,7 @@ void StickersManager::on_load_featured_sticker_sets_from_database(string value) } vector sets_to_load; - for (auto sticker_set_id : log_event.sticker_set_ids) { + for (auto sticker_set_id : log_event.sticker_set_ids_) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->is_inited) { @@ -5362,23 +5531,25 @@ void StickersManager::on_load_featured_sticker_sets_from_database(string value) } load_sticker_sets_without_stickers( - std::move(sets_to_load), - PromiseCreator::lambda([sticker_set_ids = std::move(log_event.sticker_set_ids)](Result<> result) mutable { + std::move(sets_to_load), PromiseCreator::lambda([sticker_set_ids = std::move(log_event.sticker_set_ids_), + is_premium = log_event.is_premium_](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::on_load_featured_sticker_sets_finished, - std::move(sticker_set_ids)); + std::move(sticker_set_ids), is_premium); } else { send_closure(G()->stickers_manager(), &StickersManager::reload_featured_sticker_sets, true); } })); } -void StickersManager::on_load_featured_sticker_sets_finished(vector &&featured_sticker_set_ids) { +void StickersManager::on_load_featured_sticker_sets_finished(vector &&featured_sticker_set_ids, + bool is_premium) { if (!featured_sticker_set_ids_.empty() && featured_sticker_set_ids != featured_sticker_set_ids_) { // always invalidate old featured sticker sets when current featured sticker sets change on_old_featured_sticker_sets_invalidated(); } featured_sticker_set_ids_ = std::move(featured_sticker_set_ids); + are_featured_sticker_sets_premium_ = is_premium; are_featured_sticker_sets_loaded_ = true; need_update_featured_sticker_sets_ = true; send_update_featured_sticker_sets(); @@ -5429,9 +5600,10 @@ void StickersManager::on_load_old_featured_sticker_sets_from_database(uint32 gen LOG(ERROR) << "Can't load old trending sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value)); return reload_old_featured_sticker_sets(); } + CHECK(!log_event.is_premium_); vector sets_to_load; - for (auto sticker_set_id : log_event.sticker_set_ids) { + for (auto sticker_set_id : log_event.sticker_set_ids_) { StickerSet *sticker_set = get_sticker_set(sticker_set_id); CHECK(sticker_set != nullptr); if (!sticker_set->is_inited) { @@ -5442,7 +5614,7 @@ void StickersManager::on_load_old_featured_sticker_sets_from_database(uint32 gen load_sticker_sets_without_stickers( std::move(sets_to_load), PromiseCreator::lambda( - [generation, sticker_set_ids = std::move(log_event.sticker_set_ids)](Result<> result) mutable { + [generation, sticker_set_ids = std::move(log_event.sticker_set_ids_)](Result<> result) mutable { if (result.is_ok()) { send_closure(G()->stickers_manager(), &StickersManager::on_load_old_featured_sticker_sets_finished, generation, std::move(sticker_set_ids)); @@ -5647,7 +5819,8 @@ Result> StickersManager::prepare_i if (format == StickerFormat::Tgs) { int32 width = for_thumbnail ? 100 : 512; - create_sticker(file_id, string(), PhotoSize(), get_dimensions(width, width, nullptr), nullptr, format, nullptr); + create_sticker(file_id, FileId(), string(), PhotoSize(), get_dimensions(width, width, "prepare_input_file"), + nullptr, format, nullptr); } else if (format == StickerFormat::Webm) { td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.webm", "video/webm", false); } else { @@ -6351,7 +6524,7 @@ void StickersManager::send_update_installed_sticker_sets(bool from_database) { if (G()->parameters().use_file_db && !from_database && !G()->close_flag()) { LOG(INFO) << "Save installed " << (is_masks ? "mask " : "") << "sticker sets to database"; - StickerSetListLogEvent log_event(installed_sticker_set_ids_[is_masks]); + StickerSetListLogEvent log_event(installed_sticker_set_ids_[is_masks], false); G()->td_db()->get_sqlite_pmc()->set(is_masks ? "sss1" : "sss0", log_event_store(log_event).as_slice().str(), Auto()); } @@ -6360,11 +6533,29 @@ void StickersManager::send_update_installed_sticker_sets(bool from_database) { } } -td_api::object_ptr StickersManager::get_update_trending_sticker_sets_object() const { +td_api::object_ptr StickersManager::get_trending_sticker_sets_object( + const vector &sticker_set_ids) const { auto total_count = static_cast(featured_sticker_set_ids_.size()) + (old_featured_sticker_set_count_ == -1 ? 1 : old_featured_sticker_set_count_); + + vector> result; + result.reserve(sticker_set_ids.size()); + for (auto sticker_set_id : sticker_set_ids) { + auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, 5, are_featured_sticker_sets_premium_); + if (sticker_set_info->size_ != 0) { + result.push_back(std::move(sticker_set_info)); + } + } + + auto result_size = narrow_cast(result.size()); + CHECK(total_count >= result_size); + return td_api::make_object(total_count, std::move(result), + are_featured_sticker_sets_premium_); +} + +td_api::object_ptr StickersManager::get_update_trending_sticker_sets_object() const { return td_api::make_object( - get_sticker_sets_object(total_count, featured_sticker_set_ids_, 5)); + get_trending_sticker_sets_object(featured_sticker_set_ids_)); } void StickersManager::send_update_featured_sticker_sets() { diff --git a/td/telegram/StickersManager.h b/td/telegram/StickersManager.h index f0d3f0c07..7825e4089 100644 --- a/td/telegram/StickersManager.h +++ b/td/telegram/StickersManager.h @@ -85,6 +85,8 @@ class StickersManager final : public Actor { void get_animated_emoji(string emoji, bool is_recursive, Promise> &&promise); + void get_all_animated_emojis(bool is_recursive, Promise> &&promise); + void get_animated_emoji_click_sticker(const string &message_text, FullMessageId full_message_id, Promise> &&promise); @@ -96,9 +98,9 @@ class StickersManager final : public Actor { bool is_active_reaction(const string &reaction) const; - void create_sticker(FileId file_id, string minithumbnail, PhotoSize thumbnail, Dimensions dimensions, - tl_object_ptr sticker, StickerFormat sticker_format, - MultiPromiseActor *load_data_multipromise_ptr); + void create_sticker(FileId file_id, FileId premium_animation_file_id, string minithumbnail, PhotoSize thumbnail, + Dimensions dimensions, tl_object_ptr sticker, + StickerFormat sticker_format, MultiPromiseActor *load_data_multipromise_ptr); bool has_input_media(FileId sticker_file_id, bool is_secret) const; @@ -109,7 +111,7 @@ class StickersManager final : public Actor { SecretInputMedia get_secret_input_media(FileId sticker_file_id, tl_object_ptr input_file, - BufferSlice thumbnail) const; + BufferSlice thumbnail, int32 layer) const; vector get_stickers(string emoji, int32 limit, bool force, Promise &&promise); @@ -187,7 +189,8 @@ class StickersManager final : public Actor { vector> &&sticker_sets, int32 total_count); - std::pair> get_featured_sticker_sets(int32 offset, int32 limit, Promise &&promise); + td_api::object_ptr get_featured_sticker_sets(int32 offset, int32 limit, + Promise &&promise); void on_get_featured_sticker_sets(int32 offset, int32 limit, uint32 generation, tl_object_ptr &&sticker_sets_ptr); @@ -353,6 +356,7 @@ class StickersManager final : public Actor { string minithumbnail; PhotoSize s_thumbnail; PhotoSize m_thumbnail; + FileId premium_animation_file_id; FileId file_id; StickerFormat format = StickerFormat::Unknown; bool is_mask = false; @@ -450,6 +454,7 @@ class StickersManager final : public Actor { string reaction_; string title_; bool is_active_ = false; + bool is_premium_ = false; FileId static_icon_; FileId appear_animation_; FileId select_animation_; @@ -495,8 +500,8 @@ class StickersManager final : public Actor { static tl_object_ptr get_mask_point_object(int32 point); - tl_object_ptr get_sticker_set_info_object(StickerSetId sticker_set_id, - size_t covers_limit) const; + tl_object_ptr get_sticker_set_info_object(StickerSetId sticker_set_id, size_t covers_limit, + bool prefer_premium) const; Sticker *get_sticker(FileId file_id); const Sticker *get_sticker(FileId file_id) const; @@ -572,7 +577,7 @@ class StickersManager final : public Actor { void on_load_featured_sticker_sets_from_database(string value); - void on_load_featured_sticker_sets_finished(vector &&featured_sticker_set_ids); + void on_load_featured_sticker_sets_finished(vector &&featured_sticker_set_ids, bool is_premium); void on_load_old_featured_sticker_sets_from_database(uint32 generation, string value); @@ -600,6 +605,9 @@ class StickersManager final : public Actor { // any change of old_featured_sticker_set_ids_ size void fix_old_featured_sticker_set_count(); + td_api::object_ptr get_trending_sticker_sets_object( + const vector &sticker_set_ids) const; + td_api::object_ptr get_update_trending_sticker_sets_object() const; void send_update_featured_sticker_sets(); @@ -634,6 +642,8 @@ class StickersManager final : public Actor { template void parse_sticker_set(StickerSet *sticker_set, ParserT &parser); + std::pair, vector> split_stickers_by_premium(const vector &sticker_ids) const; + Result> prepare_input_file( const tl_object_ptr &input_file, StickerFormat format, bool for_thumbnail); @@ -798,6 +808,7 @@ class StickersManager final : public Actor { bool are_recent_stickers_loaded_[2] = {false, false}; bool are_favorite_stickers_loaded_ = false; + bool are_featured_sticker_sets_premium_ = false; bool are_old_featured_sticker_sets_invalidated_ = false; vector> load_installed_sticker_sets_queries_[2]; diff --git a/td/telegram/StickersManager.hpp b/td/telegram/StickersManager.hpp index e5274cb8b..5e1d0f9de 100644 --- a/td/telegram/StickersManager.hpp +++ b/td/telegram/StickersManager.hpp @@ -33,6 +33,7 @@ void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT bool has_minithumbnail = !sticker->minithumbnail.empty(); bool is_tgs = sticker->format == StickerFormat::Tgs; bool is_webm = sticker->format == StickerFormat::Webm; + bool has_premium_animation = sticker->premium_animation_file_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(sticker->is_mask); STORE_FLAG(has_sticker_set_access_hash); @@ -40,6 +41,7 @@ void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT STORE_FLAG(is_tgs); STORE_FLAG(has_minithumbnail); STORE_FLAG(is_webm); + STORE_FLAG(has_premium_animation); END_STORE_FLAGS(); if (!in_sticker_set) { store(sticker->set_id.get(), storer); @@ -63,6 +65,9 @@ void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT if (has_minithumbnail) { store(sticker->minithumbnail, storer); } + if (has_premium_animation) { + store(sticker->premium_animation_file_id, storer); + } } template @@ -77,6 +82,7 @@ FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) { bool has_minithumbnail; bool is_tgs; bool is_webm; + bool has_premium_animation; BEGIN_PARSE_FLAGS(); PARSE_FLAG(sticker->is_mask); PARSE_FLAG(has_sticker_set_access_hash); @@ -84,6 +90,7 @@ FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) { PARSE_FLAG(is_tgs); PARSE_FLAG(has_minithumbnail); PARSE_FLAG(is_webm); + PARSE_FLAG(has_premium_animation); END_PARSE_FLAGS(); if (is_webm) { sticker->format = StickerFormat::Webm; @@ -133,6 +140,9 @@ FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) { if (has_minithumbnail) { parse(sticker->minithumbnail, parser); } + if (has_premium_animation) { + parse(sticker->premium_animation_file_id, parser); + } if (parser.get_error() != nullptr || !sticker->file_id.is_valid()) { return FileId(); } @@ -396,6 +406,7 @@ void StickersManager::Reaction::store(StorerT &storer) const { STORE_FLAG(is_active_); STORE_FLAG(has_around_animation); STORE_FLAG(has_center_animation); + STORE_FLAG(is_premium_); END_STORE_FLAGS(); td::store(reaction_, storer); td::store(title_, storer); @@ -421,6 +432,7 @@ void StickersManager::Reaction::parse(ParserT &parser) { PARSE_FLAG(is_active_); PARSE_FLAG(has_around_animation); PARSE_FLAG(has_center_animation); + PARSE_FLAG(is_premium_); END_PARSE_FLAGS(); td::parse(reaction_, parser); td::parse(title_, parser); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 546e85b11..19d9a90e8 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -95,6 +95,7 @@ #include "td/telegram/Photo.h" #include "td/telegram/PhotoSizeSource.h" #include "td/telegram/PollManager.h" +#include "td/telegram/Premium.h" #include "td/telegram/PrivacyManager.h" #include "td/telegram/PublicDialogType.h" #include "td/telegram/ReportReason.h" @@ -143,9 +144,7 @@ #include "td/utils/MimeType.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" -#include "td/utils/port/Clocks.h" #include "td/utils/port/IPAddress.h" -#include "td/utils/port/path.h" #include "td/utils/port/SocketFd.h" #include "td/utils/port/uname.h" #include "td/utils/Random.h" @@ -2072,16 +2071,16 @@ class GetArchivedStickerSetsRequest final : public RequestActor<> { }; class GetTrendingStickerSetsRequest final : public RequestActor<> { - std::pair> sticker_set_ids_; + td_api::object_ptr result_; int32 offset_; int32 limit_; void do_run(Promise &&promise) final { - sticker_set_ids_ = td_->stickers_manager_->get_featured_sticker_sets(offset_, limit_, std::move(promise)); + result_ = td_->stickers_manager_->get_featured_sticker_sets(offset_, limit_, std::move(promise)); } void do_send_result() final { - send_result(td_->stickers_manager_->get_sticker_sets_object(sticker_set_ids_.first, sticker_set_ids_.second, 5)); + send_result(std::move(result_)); } public: @@ -3047,6 +3046,10 @@ void Td::request(uint64 id, tl_object_ptr function) { } void Td::run_request(uint64 id, tl_object_ptr function) { + if (set_parameters_request_id_ > 0) { + pending_set_parameters_requests_.emplace_back(id, std::move(function)); + return; + } if (init_request_id_ > 0) { pending_init_requests_.emplace_back(id, std::move(function)); return; @@ -3078,9 +3081,20 @@ void Td::run_request(uint64 id, tl_object_ptr function) { switch (state_) { case State::WaitParameters: { switch (function_id) { - case td_api::setTdlibParameters::ID: - return answer_ok_query( - id, set_parameters(std::move(move_tl_object_as(function)->parameters_))); + case td_api::setTdlibParameters::ID: { + auto status = set_parameters(std::move(move_tl_object_as(function)->parameters_)); + if (status.is_error()) { + return send_closure(actor_id(this), &Td::send_error, id, std::move(status)); + } + + VLOG(td_init) << "Begin to check parameters"; + set_parameters_request_id_ = id; + auto promise = + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_checked_parameters) { + send_closure(actor_id, &Td::on_parameters_checked, std::move(r_checked_parameters)); + }); + return TdDb::check_parameters(get_database_scheduler_id(), parameters_, std::move(promise)); + } default: if (is_preinitialization_request(function_id)) { break; @@ -3577,6 +3591,8 @@ void Td::clear() { LOG(DEBUG) << "TopDialogManager actor was cleared" << timer; updates_manager_actor_.reset(); LOG(DEBUG) << "UpdatesManager actor was cleared" << timer; + voice_notes_manager_actor_.reset(); + LOG(DEBUG) << "VoiceNotesManager actor was cleared" << timer; web_pages_manager_actor_.reset(); LOG(DEBUG) << "WebPagesManager actor was cleared" << timer; } @@ -3665,6 +3681,51 @@ void Td::complete_pending_preauthentication_requests(const T &func) { } } +int32 Td::get_database_scheduler_id() { + auto current_scheduler_id = Scheduler::instance()->sched_id(); + auto scheduler_count = Scheduler::instance()->sched_count(); + return min(current_scheduler_id + 1, scheduler_count - 1); +} + +void Td::on_parameters_checked(Result r_checked_parameters) { + CHECK(set_parameters_request_id_ != 0); + if (r_checked_parameters.is_error()) { + send_closure(actor_id(this), &Td::send_error, set_parameters_request_id_, + Status::Error(400, r_checked_parameters.error().message())); + return finish_set_parameters(); + } + auto checked_parameters = r_checked_parameters.move_as_ok(); + + parameters_.database_directory = std::move(checked_parameters.database_directory); + parameters_.files_directory = std::move(checked_parameters.files_directory); + is_database_encrypted_ = checked_parameters.is_database_encrypted; + + state_ = State::Decrypt; + VLOG(td_init) << "Send authorizationStateWaitEncryptionKey"; + send_closure(actor_id(this), &Td::send_update, + td_api::make_object( + td_api::make_object(is_database_encrypted_))); + VLOG(td_init) << "Finish set parameters"; + send_closure(actor_id(this), &Td::send_result, set_parameters_request_id_, td_api::make_object()); + return finish_set_parameters(); +} + +void Td::finish_set_parameters() { + CHECK(set_parameters_request_id_ != 0); + set_parameters_request_id_ = 0; + + if (pending_set_parameters_requests_.empty()) { + return; + } + + VLOG(td_init) << "Continue to execute " << pending_set_parameters_requests_.size() << " pending requests"; + auto requests = std::move(pending_set_parameters_requests_); + for (auto &request : requests) { + run_request(request.first, std::move(request.second)); + } + CHECK(pending_set_parameters_requests_.size() < requests.size()); +} + void Td::start_init(uint64 id, string &&key) { VLOG(td_init) << "Begin to init database"; init_request_id_ = id; @@ -3672,9 +3733,7 @@ void Td::start_init(uint64 id, string &&key) { auto promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result r_opened_database) { send_closure(actor_id, &Td::init, std::move(r_opened_database)); }); - auto current_scheduler_id = Scheduler::instance()->sched_id(); - auto scheduler_count = Scheduler::instance()->sched_count(); - TdDb::open(min(current_scheduler_id + 1, scheduler_count - 1), parameters_, as_db_key(std::move(key), parameters_.use_custom_db_format), + TdDb::open(get_database_scheduler_id(), parameters_, as_db_key(std::move(key), parameters_.use_custom_db_format), std::move(promise)); } @@ -3735,11 +3794,7 @@ void Td::init(Result r_opened_database) { G()->set_my_id(G()->shared_config().get_option_integer("my_id")); - auto current_scheduler_id = Scheduler::instance()->sched_id(); - auto scheduler_count = Scheduler::instance()->sched_count(); - - storage_manager_ = create_actor("StorageManager", create_reference(), - min(current_scheduler_id + 2, scheduler_count - 1)); + storage_manager_ = create_actor("StorageManager", create_reference(), G()->get_gc_scheduler_id()); G()->set_storage_manager(storage_manager_.get()); VLOG(td_init) << "Send binlog events"; @@ -4000,7 +4055,6 @@ void Td::init_managers() { documents_manager_ = make_unique(this); video_notes_manager_ = make_unique(this); videos_manager_ = make_unique(this); - voice_notes_manager_ = make_unique(this); animations_manager_ = make_unique(this, create_reference()); animations_manager_actor_ = register_actor("AnimationsManager", animations_manager_.get()); @@ -4059,6 +4113,8 @@ void Td::init_managers() { updates_manager_ = make_unique(this, create_reference()); updates_manager_actor_ = register_actor("UpdatesManager", updates_manager_.get()); G()->set_updates_manager(updates_manager_actor_.get()); + voice_notes_manager_ = make_unique(this, create_reference()); + voice_notes_manager_actor_ = register_actor("VoiceNotesManager", voice_notes_manager_.get()); web_pages_manager_ = make_unique(this, create_reference()); web_pages_manager_actor_ = register_actor("WebPagesManager", web_pages_manager_.get()); G()->set_web_pages_manager(web_pages_manager_actor_.get()); @@ -4234,35 +4290,6 @@ Status Td::fix_parameters(TdParameters ¶meters) { VLOG(td_init) << "Invalid api_hash"; return Status::Error(400, "Valid api_hash must be provided. Can be obtained at https://my.telegram.org"); } - - auto prepare_dir = [](string dir) -> Result { - CHECK(!dir.empty()); - if (dir.back() != TD_DIR_SLASH) { - dir += TD_DIR_SLASH; - } - TRY_STATUS(mkpath(dir, 0750)); - TRY_RESULT(real_dir, realpath(dir, true)); - if (dir.back() != TD_DIR_SLASH) { - dir += TD_DIR_SLASH; - } - return real_dir; - }; - - auto r_database_directory = prepare_dir(parameters.database_directory); - if (r_database_directory.is_error()) { - VLOG(td_init) << "Invalid database_directory"; - return Status::Error(400, PSLICE() << "Can't init database in the directory \"" << parameters.database_directory - << "\": " << r_database_directory.error()); - } - parameters.database_directory = r_database_directory.move_as_ok(); - auto r_files_directory = prepare_dir(parameters.files_directory); - if (r_files_directory.is_error()) { - VLOG(td_init) << "Invalid files_directory"; - return Status::Error(400, PSLICE() << "Can't init files directory \"" << parameters.files_directory - << "\": " << r_files_directory.error()); - } - parameters.files_directory = r_files_directory.move_as_ok(); - return Status::OK(); } @@ -4273,8 +4300,8 @@ Status Td::set_parameters(td_api::object_ptr parameters return Status::Error(400, "Parameters aren't specified"); } - if (!clean_input_string(parameters->api_hash_) && !clean_input_string(parameters->system_language_code_) && - !clean_input_string(parameters->device_model_) && !clean_input_string(parameters->system_version_) && + if (!clean_input_string(parameters->api_hash_) || !clean_input_string(parameters->system_language_code_) || + !clean_input_string(parameters->device_model_) || !clean_input_string(parameters->system_version_) || !clean_input_string(parameters->application_version_)) { VLOG(td_init) << "Wrong string encoding"; return Status::Error(400, "Strings must be encoded in UTF-8"); @@ -4293,11 +4320,7 @@ Status Td::set_parameters(td_api::object_ptr parameters parameters_.use_chat_info_db = parameters->use_chat_info_database_; parameters_.use_message_db = parameters->use_message_database_; - VLOG(td_init) << "Fix parameters..."; TRY_STATUS(fix_parameters(parameters_)); - VLOG(td_init) << "Check binlog encryption..."; - TRY_RESULT(encryption_info, TdDb::check_encryption(parameters_)); - is_database_encrypted_ = encryption_info.is_encrypted; VLOG(td_init) << "Create MtprotoHeader::Options"; options_.api_id = parameters->api_id_; @@ -4328,12 +4351,6 @@ Status Td::set_parameters(td_api::object_ptr parameters options_.is_emulator = false; options_.proxy = Proxy(); - state_ = State::Decrypt; - VLOG(td_init) << "Send authorizationStateWaitEncryptionKey"; - send_closure(actor_id(this), &Td::send_update, - td_api::make_object( - td_api::make_object(is_database_encrypted_))); - VLOG(td_init) << "Finish set parameters"; return Status::OK(); } @@ -4822,6 +4839,20 @@ void Td::on_request(uint64 id, td_api::translateText &request) { std::move(promise)); } +void Td::on_request(uint64 id, const td_api::recognizeSpeech &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + voice_notes_manager_->recognize_speech({DialogId(request.chat_id_), MessageId(request.message_id_)}, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::rateSpeechRecognition &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + voice_notes_manager_->rate_speech_recognition({DialogId(request.chat_id_), MessageId(request.message_id_)}, + request.is_good_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getFile &request) { send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(FileId(request.file_id_, 0))); } @@ -5340,8 +5371,10 @@ void Td::on_request(uint64 id, const td_api::getMessageAvailableReactions &reque if (r_reactions.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_reactions.move_as_error()); } else { + auto reactions = + transform(r_reactions.ok(), [](auto &reaction) { return reaction.get_available_reaction_object(); }); send_closure(actor_id(this), &Td::send_result, id, - td_api::make_object(r_reactions.move_as_ok())); + td_api::make_object(std::move(reactions))); } } @@ -6094,7 +6127,8 @@ void Td::on_request(uint64 id, const td_api::reorderChatFilters &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); messages_manager_->reorder_dialog_filters( - transform(request.chat_filter_ids_, [](int32 id) { return DialogFilterId(id); }), std::move(promise)); + transform(request.chat_filter_ids_, [](int32 id) { return DialogFilterId(id); }), + request.main_chat_list_position_, std::move(promise)); } void Td::on_request(uint64 id, td_api::setChatTitle &request) { @@ -6529,7 +6563,7 @@ void Td::on_file_download_finished(FileId file_id) { auto file_size = file_object->size_; auto limit = it->second.limit; if (limit == 0) { - limit = std::numeric_limits::max(); + limit = std::numeric_limits::max(); } if (file_object->local_->is_downloading_completed_ || (download_offset <= it->second.offset && download_offset + downloaded_size >= it->second.offset && @@ -6552,7 +6586,7 @@ void Td::on_request(uint64 id, const td_api::getFileDownloadedPrefixSize &reques return send_closure(actor_id(this), &Td::send_error, id, Status::Error(400, "Unknown file ID")); } send_closure(actor_id(this), &Td::send_result, id, - td_api::make_object(narrow_cast(file_view.downloaded_prefix(request.offset_)))); + td_api::make_object(file_view.downloaded_prefix(request.offset_))); } void Td::on_request(uint64 id, const td_api::cancelDownloadFile &request) { @@ -6906,6 +6940,20 @@ void Td::on_request(uint64 id, const td_api::toggleSupergroupSignMessages &reque std::move(promise)); } +void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinToSendMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->toggle_channel_join_to_send(ChannelId(request.supergroup_id_), request.join_to_send_messages_, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinByRequest &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->toggle_channel_join_request(ChannelId(request.supergroup_id_), request.join_by_request_, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -7136,6 +7184,12 @@ void Td::on_request(uint64 id, td_api::getAnimatedEmoji &request) { stickers_manager_->get_animated_emoji(std::move(request.emoji_), false, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getAllAnimatedEmojis &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + stickers_manager_->get_all_animated_emojis(false, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::getEmojiSuggestionsUrl &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.language_code_); @@ -7517,18 +7571,17 @@ void Td::on_request(uint64 id, td_api::getBankCardInfo &request) { get_bank_card_info(this, request.bank_card_number_, std::move(promise)); } -void Td::on_request(uint64 id, const td_api::getPaymentForm &request) { +void Td::on_request(uint64 id, td_api::getPaymentForm &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - get_payment_form(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, request.theme_, - std::move(promise)); + get_payment_form(this, std::move(request.input_invoice_), request.theme_, std::move(promise)); } void Td::on_request(uint64 id, td_api::validateOrderInfo &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - validate_order_info(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, - std::move(request.order_info_), request.allow_save_, std::move(promise)); + validate_order_info(this, std::move(request.input_invoice_), std::move(request.order_info_), request.allow_save_, + std::move(promise)); } void Td::on_request(uint64 id, td_api::sendPaymentForm &request) { @@ -7536,9 +7589,8 @@ void Td::on_request(uint64 id, td_api::sendPaymentForm &request) { CLEAN_INPUT_STRING(request.order_info_id_); CLEAN_INPUT_STRING(request.shipping_option_id_); CREATE_REQUEST_PROMISE(); - send_payment_form(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, request.payment_form_id_, - request.order_info_id_, request.shipping_option_id_, request.credentials_, request.tip_amount_, - std::move(promise)); + send_payment_form(this, std::move(request.input_invoice_), request.payment_form_id_, request.order_info_id_, + request.shipping_option_id_, request.credentials_, request.tip_amount_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getPaymentReceipt &request) { @@ -7565,6 +7617,19 @@ void Td::on_request(uint64 id, const td_api::deleteSavedCredentials &request) { delete_saved_credentials(this, std::move(promise)); } +void Td::on_request(uint64 id, td_api::createInvoiceLink &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(td_api::make_object(result.move_as_ok())); + } + }); + export_invoice(this, std::move(request.invoice_), std::move(query_promise)); +} + void Td::on_request(uint64 id, td_api::getPassportElement &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.password_); @@ -7831,6 +7896,41 @@ void Td::on_request(uint64 id, td_api::removeRecentHashtag &request) { send_closure(hashtag_hints_, &HashtagHints::remove_hashtag, std::move(request.hashtag_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getPremiumLimit &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_premium_limit(request.limit_type_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getPremiumFeatures &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_premium_features(this, request.source_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getPremiumStickers &request) { + CHECK_IS_USER(); + CREATE_REQUEST(SearchStickersRequest, "⭐️⭐️", 100); +} + +void Td::on_request(uint64 id, const td_api::viewPremiumFeature &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + view_premium_feature(this, request.feature_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::clickPremiumSubscriptionButton &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + click_premium_subscription_button(this, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getPremiumState &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_premium_state(this, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::acceptTermsOfService &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.terms_of_service_id_); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 76bce325f..01cb107bb 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -109,7 +109,7 @@ class Td final : public Actor { Td &operator=(Td &&) = delete; ~Td() final; - static constexpr const char *TDLIB_VERSION = "1.8.3"; + static constexpr const char *TDLIB_VERSION = "1.8.4"; struct Options { std::shared_ptr net_query_stats; @@ -144,7 +144,6 @@ class Td final : public Actor { unique_ptr documents_manager_; unique_ptr video_notes_manager_; unique_ptr videos_manager_; - unique_ptr voice_notes_manager_; unique_ptr animations_manager_; ActorOwn animations_manager_actor_; @@ -192,6 +191,8 @@ class Td final : public Actor { ActorOwn top_dialog_manager_actor_; unique_ptr updates_manager_; ActorOwn updates_manager_actor_; + unique_ptr voice_notes_manager_; + ActorOwn voice_notes_manager_actor_; unique_ptr memory_manager_; ActorOwn memory_manager_actor_; unique_ptr web_pages_manager_; @@ -304,6 +305,7 @@ class Td final : public Actor { enum class State : int32 { WaitParameters, Decrypt, Run, Close } state_ = State::WaitParameters; uint64 init_request_id_ = 0; + uint64 set_parameters_request_id_ = 0; bool is_database_encrypted_ = false; FlatHashMap> result_handlers_; @@ -321,14 +323,15 @@ class Td final : public Actor { TermsOfService pending_terms_of_service_; struct DownloadInfo { - int32 offset = -1; - int32 limit = -1; + int64 offset = -1; + int64 limit = -1; vector request_ids; }; FlatHashMap pending_file_downloads_; vector>> pending_preauthentication_requests_; + vector>> pending_set_parameters_requests_; vector>> pending_init_requests_; template @@ -541,6 +544,10 @@ class Td final : public Actor { void on_request(uint64 id, td_api::translateText &request); + void on_request(uint64 id, const td_api::recognizeSpeech &request); + + void on_request(uint64 id, const td_api::rateSpeechRecognition &request); + void on_request(uint64 id, const td_api::getFile &request); void on_request(uint64 id, td_api::getRemoteFile &request); @@ -1043,6 +1050,10 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::toggleSupergroupSignMessages &request); + void on_request(uint64 id, const td_api::toggleSupergroupJoinToSendMessages &request); + + void on_request(uint64 id, const td_api::toggleSupergroupJoinByRequest &request); + void on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request); void on_request(uint64 id, const td_api::toggleSupergroupIsBroadcastGroup &request); @@ -1115,6 +1126,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::getAnimatedEmoji &request); + void on_request(uint64 id, const td_api::getAllAnimatedEmojis &request); + void on_request(uint64 id, td_api::getEmojiSuggestionsUrl &request); void on_request(uint64 id, const td_api::getFavoriteStickers &request); @@ -1213,7 +1226,7 @@ class Td final : public Actor { void on_request(uint64 id, td_api::getBankCardInfo &request); - void on_request(uint64 id, const td_api::getPaymentForm &request); + void on_request(uint64 id, td_api::getPaymentForm &request); void on_request(uint64 id, td_api::validateOrderInfo &request); @@ -1227,6 +1240,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::deleteSavedCredentials &request); + void on_request(uint64 id, td_api::createInvoiceLink &request); + void on_request(uint64 id, td_api::getPassportElement &request); void on_request(uint64 id, td_api::getAllPassportElements &request); @@ -1291,6 +1306,18 @@ class Td final : public Actor { void on_request(uint64 id, td_api::removeRecentHashtag &request); + void on_request(uint64 id, const td_api::getPremiumLimit &request); + + void on_request(uint64 id, const td_api::getPremiumFeatures &request); + + void on_request(uint64 id, const td_api::getPremiumStickers &request); + + void on_request(uint64 id, const td_api::viewPremiumFeature &request); + + void on_request(uint64 id, const td_api::clickPremiumSubscriptionButton &request); + + void on_request(uint64 id, const td_api::getPremiumState &request); + void on_request(uint64 id, td_api::acceptTermsOfService &request); void on_request(uint64 id, const td_api::getCountries &request); @@ -1412,6 +1439,12 @@ class Td final : public Actor { static DbKey as_db_key(string key, bool custom_db); + static int32 get_database_scheduler_id(); + + void on_parameters_checked(Result r_checked_parameters); + + void finish_set_parameters(); + void start_init(uint64 id, string &&key); void init(Result r_opened_database); diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index d50456867..80824c6f2 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -24,6 +24,7 @@ #include "td/db/SqliteKeyValueAsync.h" #include "td/db/SqliteKeyValueSafe.h" +#include "td/actor/actor.h" #include "td/actor/MultiPromise.h" #include "td/utils/common.h" @@ -50,19 +51,6 @@ std::string get_sqlite_path(const TdParameters ¶meters) { return parameters.database_directory + db_name + ".sqlite"; } -Result check_encryption(string path) { - Binlog binlog; - auto status = binlog.init(std::move(path), Binlog::Callback()); - if (status.is_error() && status.code() != Binlog::Error::WrongPassword) { - LOG(WARNING) << "Failed to check binlog: " << status; - return Status::Error(400, status.message()); - } - TdDb::EncryptionInfo info; - info.is_encrypted = binlog.get_info().wrong_password; - binlog.close(false /*need_sync*/).ensure(); - return info; -} - Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_pmc, BinlogKeyValue &config_pmc, TdDb::OpenedDatabase &events, DbKey key) { auto callback = [&](const BinlogEvent &event) { @@ -405,7 +393,7 @@ void TdDb::open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise< stop(); } }; - send_closure(create_actor_on_scheduler("worker", scheduler_id), &Worker::open, std::move(parameters), + send_closure(create_actor_on_scheduler("Worker", scheduler_id), &Worker::open, std::move(parameters), std::move(key), std::move(promise)); return; } @@ -502,8 +490,62 @@ void TdDb::open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise< TdDb::TdDb() = default; TdDb::~TdDb() = default; -Result TdDb::check_encryption(const TdParameters ¶meters) { - return ::td::check_encryption(get_binlog_path(parameters)); +void TdDb::check_parameters(int32 scheduler_id, TdParameters parameters, Promise promise) { + if (scheduler_id >= 0 && Scheduler::instance()->sched_id() != scheduler_id) { + class Worker final : public Actor { + public: + void run(TdParameters parameters, Promise promise) { + TdDb::check_parameters(-1, std::move(parameters), std::move(promise)); + stop(); + } + }; + send_closure(create_actor_on_scheduler("Worker", scheduler_id), &Worker::run, std::move(parameters), + std::move(promise)); + return; + } + CheckedParameters result; + + auto prepare_dir = [](string dir) -> Result { + CHECK(!dir.empty()); + if (dir.back() != TD_DIR_SLASH) { + dir += TD_DIR_SLASH; + } + TRY_STATUS(mkpath(dir, 0750)); + TRY_RESULT(real_dir, realpath(dir, true)); + if (dir.back() != TD_DIR_SLASH) { + dir += TD_DIR_SLASH; + } + return real_dir; + }; + + auto r_database_directory = prepare_dir(parameters.database_directory); + if (r_database_directory.is_error()) { + VLOG(td_init) << "Invalid database_directory"; + return promise.set_error(Status::Error(PSLICE() + << "Can't init database in the directory \"" << parameters.database_directory + << "\": " << r_database_directory.error())); + } + result.database_directory = r_database_directory.move_as_ok(); + parameters.database_directory = result.database_directory; + + auto r_files_directory = prepare_dir(parameters.files_directory); + if (r_files_directory.is_error()) { + VLOG(td_init) << "Invalid files_directory"; + return promise.set_error(Status::Error(PSLICE() << "Can't init files directory \"" << parameters.files_directory + << "\": " << r_files_directory.error())); + } + result.files_directory = r_files_directory.move_as_ok(); + + Binlog binlog; + auto status = binlog.init(get_binlog_path(parameters), Binlog::Callback()); + if (status.is_error() && status.code() != Binlog::Error::WrongPassword) { + LOG(WARNING) << "Failed to check binlog: " << status; + return promise.set_error(std::move(status)); + } + result.is_database_encrypted = binlog.get_info().wrong_password; + binlog.close(false /*need_sync*/).ensure(); + + promise.set_value(std::move(result)); } void TdDb::change_key(DbKey key, Promise<> promise) { diff --git a/td/telegram/TdDb.h b/td/telegram/TdDb.h index 34eda69b0..957d0d842 100644 --- a/td/telegram/TdDb.h +++ b/td/telegram/TdDb.h @@ -48,6 +48,13 @@ class TdDb { TdDb &operator=(TdDb &&) = delete; ~TdDb(); + struct CheckedParameters { + string database_directory; + string files_directory; + bool is_database_encrypted{false}; + }; + static void check_parameters(int32 scheduler_id, TdParameters parameters, Promise promise); + struct OpenedDatabase { unique_ptr database; @@ -64,11 +71,6 @@ class TdDb { }; static void open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise &&promise); - struct EncryptionInfo { - bool is_encrypted{false}; - }; - static Result check_encryption(const TdParameters ¶meters); - static Status destroy(const TdParameters ¶meters); std::shared_ptr get_file_db_shared(); diff --git a/td/telegram/ThemeManager.cpp b/td/telegram/ThemeManager.cpp index 264db0c12..0007b3de4 100644 --- a/td/telegram/ThemeManager.cpp +++ b/td/telegram/ThemeManager.cpp @@ -270,6 +270,7 @@ string get_theme_parameters_json_string_impl(const td_api::object_ptr(json_object([&theme](auto &o) { auto get_color = &get_color_json; o("bg_color", get_color(theme->background_color_)); + o("secondary_bg_color", get_color(theme->secondary_background_color_)); o("text_color", get_color(theme->text_color_)); o("hint_color", get_color(theme->hint_color_)); o("link_color", get_color(theme->link_color_)); diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 14ad39740..4fbe74252 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -53,6 +53,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.hpp" #include "td/telegram/ThemeManager.h" +#include "td/telegram/VoiceNotesManager.h" #include "td/telegram/WebPagesManager.h" #include "td/actor/MultiPromise.h" @@ -1236,20 +1237,35 @@ int32 UpdatesManager::get_update_edit_message_pts(const telegram_api::Updates *u int32 pts = 0; auto updates = get_updates(updates_ptr); if (updates != nullptr) { - for (auto &update : *updates) { + for (auto &update_ptr : *updates) { int32 update_pts = [&] { - switch (update->get_id()) { + switch (update_ptr->get_id()) { case telegram_api::updateEditMessage::ID: { - auto update_ptr = static_cast(update.get()); - if (MessagesManager::get_full_message_id(update_ptr->message_, false) == full_message_id) { - return update_ptr->pts_; + auto update = static_cast(update_ptr.get()); + if (MessagesManager::get_full_message_id(update->message_, false) == full_message_id) { + return update->pts_; } return 0; } case telegram_api::updateEditChannelMessage::ID: { - auto update_ptr = static_cast(update.get()); - if (MessagesManager::get_full_message_id(update_ptr->message_, false) == full_message_id) { - return update_ptr->pts_; + auto update = static_cast(update_ptr.get()); + if (MessagesManager::get_full_message_id(update->message_, false) == full_message_id) { + return update->pts_; + } + return 0; + } + case telegram_api::updateNewScheduledMessage::ID: { + auto update = static_cast(update_ptr.get()); + auto new_full_message_id = MessagesManager::get_full_message_id(update->message_, true); + if (new_full_message_id.get_dialog_id() == full_message_id.get_dialog_id()) { + auto new_message_id = new_full_message_id.get_message_id(); + auto old_message_id = full_message_id.get_message_id(); + if (new_message_id.is_valid_scheduled() && new_message_id.is_scheduled_server() && + old_message_id.is_valid_scheduled() && old_message_id.is_scheduled_server() && + old_message_id.get_scheduled_server_message_id() == + new_message_id.get_scheduled_server_message_id()) { + return -2; + } } return 0; } @@ -2267,27 +2283,27 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up } case telegram_api::updateChatParticipant::ID: { auto update = move_tl_object_as(update_ptr); - td_->contacts_manager_->on_update_chat_participant(ChatId(update->chat_id_), UserId(update->actor_id_), - update->date_, DialogInviteLink(std::move(update->invite_)), - std::move(update->prev_participant_), - std::move(update->new_participant_)); + td_->contacts_manager_->on_update_chat_participant( + ChatId(update->chat_id_), UserId(update->actor_id_), update->date_, + DialogInviteLink(std::move(update->invite_), "updateChatParticipant"), std::move(update->prev_participant_), + std::move(update->new_participant_)); add_qts(qts).set_value(Unit()); break; } case telegram_api::updateChannelParticipant::ID: { auto update = move_tl_object_as(update_ptr); - td_->contacts_manager_->on_update_channel_participant(ChannelId(update->channel_id_), UserId(update->actor_id_), - update->date_, DialogInviteLink(std::move(update->invite_)), - std::move(update->prev_participant_), - std::move(update->new_participant_)); + td_->contacts_manager_->on_update_channel_participant( + ChannelId(update->channel_id_), UserId(update->actor_id_), update->date_, + DialogInviteLink(std::move(update->invite_), "updateChannelParticipant"), + std::move(update->prev_participant_), std::move(update->new_participant_)); add_qts(qts).set_value(Unit()); break; } case telegram_api::updateBotChatInviteRequester::ID: { auto update = move_tl_object_as(update_ptr); - td_->contacts_manager_->on_update_chat_invite_requester(DialogId(update->peer_), UserId(update->user_id_), - std::move(update->about_), update->date_, - DialogInviteLink(std::move(update->invite_))); + td_->contacts_manager_->on_update_chat_invite_requester( + DialogId(update->peer_), UserId(update->user_id_), std::move(update->about_), update->date_, + DialogInviteLink(std::move(update->invite_), "updateBotChatInviteRequester")); add_qts(qts).set_value(Unit()); break; } @@ -3183,7 +3199,7 @@ void UpdatesManager::on_update(tl_object_ptr upda } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - send_closure(td_->config_manager_, &ConfigManager::request_config); + send_closure(td_->config_manager_, &ConfigManager::request_config, false); promise.set_value(Unit()); } @@ -3416,6 +3432,12 @@ void UpdatesManager::on_update(tl_object_ptr td_->notification_settings_manager_->reload_saved_ringtones(std::move(promise)); } +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->voice_notes_manager_->on_update_transcribed_audio(std::move(update->text_), update->transcription_id_, + !update->pending_); + promise.set_value(Unit()); +} + // unsupported updates } // namespace td diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index 54f8b1855..b637bb4a7 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -526,6 +526,8 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + // unsupported updates }; diff --git a/td/telegram/Version.h b/td/telegram/Version.h index 4c66b6a48..ae69d2a30 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -10,7 +10,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 140; +constexpr int32 MTPROTO_LAYER = 143; enum class Version : int32 { Initial, // 0 @@ -51,6 +51,10 @@ enum class Version : int32 { AddKeyboardButtonFlags, // 35 AddAudioFlags, UseServerForwardAsCopy, + AddMainDialogListPosition, + AddVoiceNoteFlags, + AddMessageStickerFlags, // 40 + AddStickerSetListFlags, Next }; diff --git a/td/telegram/VideoNotesManager.cpp b/td/telegram/VideoNotesManager.cpp index 415809104..825de398c 100644 --- a/td/telegram/VideoNotesManager.cpp +++ b/td/telegram/VideoNotesManager.cpp @@ -16,7 +16,6 @@ #include "td/telegram/ConfigShared.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" #include "td/utils/Status.h" namespace td { @@ -158,7 +157,7 @@ void VideoNotesManager::create_video_note(FileId file_id, string minithumbnail, SecretInputMedia VideoNotesManager::get_secret_input_media(FileId video_note_file_id, tl_object_ptr input_file, - BufferSlice thumbnail) const { + BufferSlice thumbnail, int32 layer) const { const VideoNote *video_note = get_video_note(video_note_file_id); CHECK(video_note != nullptr); auto file_view = td_->file_manager_->get_file_view(video_note_file_id); @@ -185,7 +184,8 @@ SecretInputMedia VideoNotesManager::get_secret_input_media(FileId video_note_fil "video/mp4", file_view, std::move(attributes), - string()}; + string(), + layer}; } tl_object_ptr VideoNotesManager::get_input_media( diff --git a/td/telegram/VideoNotesManager.h b/td/telegram/VideoNotesManager.h index 89a59a7f0..e26201bca 100644 --- a/td/telegram/VideoNotesManager.h +++ b/td/telegram/VideoNotesManager.h @@ -40,7 +40,7 @@ class VideoNotesManager { SecretInputMedia get_secret_input_media(FileId video_note_file_id, tl_object_ptr input_file, - BufferSlice thumbnail) const; + BufferSlice thumbnail, int32 layer) const; FileId get_video_note_thumbnail_file_id(FileId file_id) const; diff --git a/td/telegram/VideosManager.cpp b/td/telegram/VideosManager.cpp index 9da674d94..ffd40b295 100644 --- a/td/telegram/VideosManager.cpp +++ b/td/telegram/VideosManager.cpp @@ -204,7 +204,8 @@ void VideosManager::create_video(FileId file_id, string minithumbnail, PhotoSize SecretInputMedia VideosManager::get_secret_input_media(FileId video_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const { + const string &caption, BufferSlice thumbnail, + int32 layer) const { const Video *video = get_video(video_file_id); CHECK(video != nullptr); auto file_view = td_->file_manager_->get_file_view(video_file_id); @@ -215,7 +216,7 @@ SecretInputMedia VideosManager::get_secret_input_media(FileId video_file_id, input_file = file_view.main_remote_location().as_input_encrypted_file(); } if (!input_file) { - return SecretInputMedia{}; + return {}; } if (video->thumbnail.file_id.is_valid() && thumbnail.empty()) { return {}; @@ -230,7 +231,8 @@ SecretInputMedia VideosManager::get_secret_input_media(FileId video_file_id, video->mime_type, file_view, std::move(attributes), - caption}; + caption, + layer}; } tl_object_ptr VideosManager::get_input_media( diff --git a/td/telegram/VideosManager.h b/td/telegram/VideosManager.h index b9b29a220..a529e4b37 100644 --- a/td/telegram/VideosManager.h +++ b/td/telegram/VideosManager.h @@ -42,7 +42,7 @@ class VideosManager { SecretInputMedia get_secret_input_media(FileId video_file_id, tl_object_ptr input_file, - const string &caption, BufferSlice thumbnail) const; + const string &caption, BufferSlice thumbnail, int32 layer) const; FileId get_video_thumbnail_file_id(FileId file_id) const; diff --git a/td/telegram/VoiceNotesManager.cpp b/td/telegram/VoiceNotesManager.cpp index ddbd6ad18..bfa8869fe 100644 --- a/td/telegram/VoiceNotesManager.cpp +++ b/td/telegram/VoiceNotesManager.cpp @@ -6,8 +6,12 @@ // #include "td/telegram/VoiceNotesManager.h" +#include "td/telegram/AccessRights.h" +#include "td/telegram/DialogId.h" #include "td/telegram/Dimensions.h" #include "td/telegram/files/FileManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessagesManager.h" #include "td/telegram/secret_api.h" #include "td/telegram/Td.h" #include "td/telegram/td_api.h" @@ -15,12 +19,101 @@ #include "td/utils/buffer.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" -#include "td/utils/Status.h" namespace td { -VoiceNotesManager::VoiceNotesManager(Td *td) : td_(td) { +class TranscribeAudioQuery final : public Td::ResultHandler { + DialogId dialog_id_; + FileId file_id_; + + public: + void send(FileId file_id, FullMessageId full_message_id) { + dialog_id_ = full_message_id.get_dialog_id(); + file_id_ = file_id; + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Can't access the chat")); + } + send_query(G()->net_query_creator().create(telegram_api::messages_transcribeAudio( + std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get()))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for TranscribeAudioQuery: " << to_string(result); + if (result->transcription_id_ == 0) { + return on_error(Status::Error(500, "Receive no recognition identifier")); + } + td_->voice_notes_manager_->on_voice_note_transcribed(file_id_, std::move(result->text_), result->transcription_id_, + !result->pending_); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "TranscribeAudioQuery"); + td_->voice_notes_manager_->on_voice_note_transcription_failed(file_id_, std::move(status)); + } +}; + +class RateTranscribedAudioQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit RateTranscribedAudioQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(FullMessageId full_message_id, int64 transcription_id, bool is_good) { + dialog_id_ = full_message_id.get_dialog_id(); + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Can't access the chat")); + } + send_query(G()->net_query_creator().create(telegram_api::messages_rateTranscribedAudio( + std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get(), transcription_id, + is_good))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(INFO) << "Receive result for RateTranscribedAudioQuery: " << result; + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "RateTranscribedAudioQuery"); + promise_.set_error(std::move(status)); + } +}; + +VoiceNotesManager::VoiceNotesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + voice_note_transcription_timeout_.set_callback(on_voice_note_transcription_timeout_callback); + voice_note_transcription_timeout_.set_callback_data(static_cast(this)); +} + +void VoiceNotesManager::tear_down() { + parent_.reset(); +} + +void VoiceNotesManager::on_voice_note_transcription_timeout_callback(void *voice_notes_manager_ptr, + int64 transcription_id) { + if (G()->close_flag()) { + return; + } + + auto voice_notes_manager = static_cast(voice_notes_manager_ptr); + send_closure_later(voice_notes_manager->actor_id(voice_notes_manager), + &VoiceNotesManager::on_pending_voice_note_transcription_failed, transcription_id, + Status::Error(500, "Timeout expired")); } int32 VoiceNotesManager::get_voice_note_duration(FileId file_id) const { @@ -41,6 +134,7 @@ tl_object_ptr VoiceNotesManager::get_voice_note_object(FileId auto voice_note = it->second.get(); CHECK(voice_note != nullptr); return make_tl_object(voice_note->duration, voice_note->waveform, voice_note->mime_type, + voice_note->is_transcribed, voice_note->text, td_->file_manager_->get_file_object(file_id)); } @@ -62,11 +156,29 @@ FileId VoiceNotesManager::on_get_voice_note(unique_ptr new_voice_note v->duration = new_voice_note->duration; v->waveform = new_voice_note->waveform; } + if (new_voice_note->is_transcribed && v->transcription_id == 0) { + CHECK(!v->is_transcribed); + CHECK(new_voice_note->transcription_id != 0); + v->is_transcribed = true; + v->transcription_id = new_voice_note->transcription_id; + v->text = std::move(new_voice_note->text); + on_voice_note_transcription_updated(file_id); + } } return file_id; } +VoiceNotesManager::VoiceNote *VoiceNotesManager::get_voice_note(FileId file_id) { + auto voice_note = voice_notes_.find(file_id); + if (voice_note == voice_notes_.end()) { + return nullptr; + } + + CHECK(voice_note->second->file_id == file_id); + return voice_note->second.get(); +} + const VoiceNotesManager::VoiceNote *VoiceNotesManager::get_voice_note(FileId file_id) const { auto voice_note = voice_notes_.find(file_id); if (voice_note == voice_notes_.end()) { @@ -128,12 +240,172 @@ void VoiceNotesManager::create_voice_note(FileId file_id, string mime_type, int3 on_get_voice_note(std::move(v), replace); } -SecretInputMedia VoiceNotesManager::get_secret_input_media(FileId voice_file_id, - tl_object_ptr input_file, - const string &caption) const { - auto *voice_note = get_voice_note(voice_file_id); +void VoiceNotesManager::register_voice_note(FileId voice_note_file_id, FullMessageId full_message_id, + const char *source) { + if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server()) { + return; + } + LOG(INFO) << "Register voice note " << voice_note_file_id << " from " << full_message_id << " from " << source; + bool is_inserted = voice_note_messages_[voice_note_file_id].insert(full_message_id).second; + LOG_CHECK(is_inserted) << source << ' ' << voice_note_file_id << ' ' << full_message_id; + is_inserted = message_voice_notes_.emplace(full_message_id, voice_note_file_id).second; + CHECK(is_inserted); +} + +void VoiceNotesManager::unregister_voice_note(FileId voice_note_file_id, FullMessageId full_message_id, + const char *source) { + if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server()) { + return; + } + LOG(INFO) << "Unregister voice note " << voice_note_file_id << " from " << full_message_id << " from " << source; + auto &message_ids = voice_note_messages_[voice_note_file_id]; + auto is_deleted = message_ids.erase(full_message_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << voice_note_file_id << ' ' << full_message_id; + if (message_ids.empty()) { + voice_note_messages_.erase(voice_note_file_id); + } + is_deleted = message_voice_notes_.erase(full_message_id) > 0; + CHECK(is_deleted); +} + +void VoiceNotesManager::recognize_speech(FullMessageId full_message_id, Promise &&promise) { + if (!td_->messages_manager_->have_message_force(full_message_id, "recognize_speech")) { + return promise.set_error(Status::Error(400, "Message not found")); + } + + auto it = message_voice_notes_.find(full_message_id); + if (it == message_voice_notes_.end()) { + return promise.set_error(Status::Error(400, "Invalid message specified")); + } + + auto file_id = it->second; + auto voice_note = get_voice_note(file_id); CHECK(voice_note != nullptr); - auto file_view = td_->file_manager_->get_file_view(voice_file_id); + if (voice_note->is_transcribed) { + return promise.set_value(Unit()); + } + auto &queries = speech_recognition_queries_[file_id]; + queries.push_back(std::move(promise)); + if (queries.size() == 1) { + td_->create_handler()->send(file_id, full_message_id); + } +} + +void VoiceNotesManager::on_voice_note_transcribed(FileId file_id, string &&text, int64 transcription_id, + bool is_final) { + auto voice_note = get_voice_note(file_id); + CHECK(voice_note != nullptr); + CHECK(!voice_note->is_transcribed); + CHECK(voice_note->transcription_id == 0 || voice_note->transcription_id == transcription_id); + bool is_changed = voice_note->is_transcribed != is_final || voice_note->text != text; + voice_note->transcription_id = transcription_id; + voice_note->is_transcribed = is_final; + voice_note->text = std::move(text); + + if (is_changed) { + on_voice_note_transcription_updated(file_id); + } + + if (is_final) { + auto it = speech_recognition_queries_.find(file_id); + CHECK(it != speech_recognition_queries_.end()); + CHECK(!it->second.empty()); + auto promises = std::move(it->second); + speech_recognition_queries_.erase(it); + + set_promises(promises); + } else { + if (pending_voice_note_transcription_queries_.count(transcription_id) != 0) { + on_pending_voice_note_transcription_failed(transcription_id, + Status::Error(500, "Receive duplicate recognition identifier")); + } + bool is_inserted = pending_voice_note_transcription_queries_.emplace(transcription_id, file_id).second; + CHECK(is_inserted); + voice_note_transcription_timeout_.set_timeout_in(transcription_id, TRANSCRIPTION_TIMEOUT); + } +} + +void VoiceNotesManager::on_voice_note_transcription_failed(FileId file_id, Status &&error) { + auto voice_note = get_voice_note(file_id); + CHECK(voice_note != nullptr); + CHECK(!voice_note->is_transcribed); + + if (voice_note->transcription_id != 0) { + CHECK(pending_voice_note_transcription_queries_.count(voice_note->transcription_id) == 0); + voice_note->transcription_id = 0; + if (!voice_note->text.empty()) { + voice_note->text.clear(); + on_voice_note_transcription_updated(file_id); + } + } + + auto it = speech_recognition_queries_.find(file_id); + CHECK(it != speech_recognition_queries_.end()); + CHECK(!it->second.empty()); + auto promises = std::move(it->second); + speech_recognition_queries_.erase(it); + + fail_promises(promises, std::move(error)); +} + +void VoiceNotesManager::on_update_transcribed_audio(string &&text, int64 transcription_id, bool is_final) { + auto it = pending_voice_note_transcription_queries_.find(transcription_id); + if (it == pending_voice_note_transcription_queries_.end()) { + return; + } + auto file_id = it->second; + pending_voice_note_transcription_queries_.erase(it); + voice_note_transcription_timeout_.cancel_timeout(transcription_id); + + on_voice_note_transcribed(file_id, std::move(text), transcription_id, is_final); +} + +void VoiceNotesManager::on_pending_voice_note_transcription_failed(int64 transcription_id, Status &&error) { + auto it = pending_voice_note_transcription_queries_.find(transcription_id); + if (it == pending_voice_note_transcription_queries_.end()) { + return; + } + auto file_id = it->second; + pending_voice_note_transcription_queries_.erase(it); + voice_note_transcription_timeout_.cancel_timeout(transcription_id); + + on_voice_note_transcription_failed(file_id, std::move(error)); +} + +void VoiceNotesManager::on_voice_note_transcription_updated(FileId file_id) { + auto it = voice_note_messages_.find(file_id); + if (it != voice_note_messages_.end()) { + for (const auto &full_message_id : it->second) { + td_->messages_manager_->on_external_update_message_content(full_message_id); + } + } +} + +void VoiceNotesManager::rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise &&promise) { + if (!td_->messages_manager_->have_message_force(full_message_id, "rate_speech_recognition")) { + return promise.set_error(Status::Error(400, "Message not found")); + } + + auto it = message_voice_notes_.find(full_message_id); + if (it == message_voice_notes_.end()) { + return promise.set_error(Status::Error(400, "Invalid message specified")); + } + + auto file_id = it->second; + auto voice_note = get_voice_note(file_id); + CHECK(voice_note != nullptr); + if (!voice_note->is_transcribed) { + return promise.set_value(Unit()); + } + CHECK(voice_note->transcription_id != 0); + td_->create_handler(std::move(promise)) + ->send(full_message_id, voice_note->transcription_id, is_good); +} + +SecretInputMedia VoiceNotesManager::get_secret_input_media(FileId voice_note_file_id, + tl_object_ptr input_file, + const string &caption, int32 layer) const { + auto file_view = td_->file_manager_->get_file_view(voice_note_file_id); if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) { return SecretInputMedia{}; } @@ -143,13 +415,16 @@ SecretInputMedia VoiceNotesManager::get_secret_input_media(FileId voice_file_id, if (!input_file) { return SecretInputMedia{}; } + + auto *voice_note = get_voice_note(voice_note_file_id); + CHECK(voice_note != nullptr); vector> attributes; attributes.push_back(make_tl_object( secret_api::documentAttributeAudio::VOICE_MASK | secret_api::documentAttributeAudio::WAVEFORM_MASK, false /*ignored*/, voice_note->duration, "", "", BufferSlice(voice_note->waveform))); return {std::move(input_file), BufferSlice(), Dimensions(), voice_note->mime_type, file_view, - std::move(attributes), caption}; + std::move(attributes), caption, layer}; } tl_object_ptr VoiceNotesManager::get_input_media( diff --git a/td/telegram/VoiceNotesManager.h b/td/telegram/VoiceNotesManager.h index 97ce31595..11138157c 100644 --- a/td/telegram/VoiceNotesManager.h +++ b/td/telegram/VoiceNotesManager.h @@ -7,20 +7,27 @@ #pragma once #include "td/telegram/files/FileId.h" +#include "td/telegram/FullMessageId.h" #include "td/telegram/SecretInputMedia.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/actor/actor.h" +#include "td/actor/PromiseFuture.h" +#include "td/actor/Timeout.h" + #include "td/utils/common.h" #include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Status.h" namespace td { class Td; -class VoiceNotesManager { +class VoiceNotesManager final : public Actor { public: - explicit VoiceNotesManager(Td *td); + VoiceNotesManager(Td *td, ActorShared<> parent); int32 get_voice_note_duration(FileId file_id) const; @@ -28,12 +35,26 @@ class VoiceNotesManager { void create_voice_note(FileId file_id, string mime_type, int32 duration, string waveform, bool replace); + void register_voice_note(FileId voice_note_file_id, FullMessageId full_message_id, const char *source); + + void unregister_voice_note(FileId voice_note_file_id, FullMessageId full_message_id, const char *source); + + void recognize_speech(FullMessageId full_message_id, Promise &&promise); + + void rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise &&promise); + + void on_update_transcribed_audio(string &&text, int64 transcription_id, bool is_final); + + void on_voice_note_transcribed(FileId file_id, string &&text, int64 transcription_id, bool is_final); + + void on_voice_note_transcription_failed(FileId file_id, Status &&error); + tl_object_ptr get_input_media(FileId file_id, tl_object_ptr input_file) const; - SecretInputMedia get_secret_input_media(FileId voice_file_id, + SecretInputMedia get_secret_input_media(FileId voice_note_file_id, tl_object_ptr input_file, - const string &caption) const; + const string &caption, int32 layer) const; FileId dup_voice_note(FileId new_id, FileId old_id); @@ -46,21 +67,45 @@ class VoiceNotesManager { FileId parse_voice_note(ParserT &parser); private: + static constexpr int32 TRANSCRIPTION_TIMEOUT = 60; + class VoiceNote { public: string mime_type; int32 duration = 0; + bool is_transcribed = false; string waveform; + int64 transcription_id = 0; + string text; FileId file_id; }; + static void on_voice_note_transcription_timeout_callback(void *voice_notes_manager_ptr, int64 transcription_id); + + VoiceNote *get_voice_note(FileId file_id); + const VoiceNote *get_voice_note(FileId file_id) const; FileId on_get_voice_note(unique_ptr new_voice_note, bool replace); + void on_pending_voice_note_transcription_failed(int64 transcription_id, Status &&error); + + void on_voice_note_transcription_updated(FileId file_id); + + void tear_down() final; + Td *td_; + ActorShared<> parent_; + FlatHashMap, FileIdHash> voice_notes_; + + FlatHashMap>, FileIdHash> speech_recognition_queries_; + FlatHashMap pending_voice_note_transcription_queries_; + MultiTimeout voice_note_transcription_timeout_{"VoiceNoteTranscriptionTimeout"}; + + FlatHashMap, FileIdHash> voice_note_messages_; + FlatHashMap message_voice_notes_; }; } // namespace td diff --git a/td/telegram/VoiceNotesManager.hpp b/td/telegram/VoiceNotesManager.hpp index 97ffa419e..46af7ac2c 100644 --- a/td/telegram/VoiceNotesManager.hpp +++ b/td/telegram/VoiceNotesManager.hpp @@ -9,6 +9,7 @@ #include "td/telegram/VoiceNotesManager.h" #include "td/telegram/files/FileId.hpp" +#include "td/telegram/Version.h" #include "td/utils/common.h" #include "td/utils/tl_helpers.h" @@ -20,18 +21,63 @@ void VoiceNotesManager::store_voice_note(FileId file_id, StorerT &storer) const auto it = voice_notes_.find(file_id); CHECK(it != voice_notes_.end()); const VoiceNote *voice_note = it->second.get(); - store(voice_note->mime_type, storer); - store(voice_note->duration, storer); - store(voice_note->waveform, storer); + bool has_mime_type = !voice_note->mime_type.empty(); + bool has_duration = voice_note->duration != 0; + bool has_waveform = !voice_note->waveform.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_mime_type); + STORE_FLAG(has_duration); + STORE_FLAG(has_waveform); + STORE_FLAG(voice_note->is_transcribed); + END_STORE_FLAGS(); + if (has_mime_type) { + store(voice_note->mime_type, storer); + } + if (has_duration) { + store(voice_note->duration, storer); + } + if (has_waveform) { + store(voice_note->waveform, storer); + } + if (voice_note->is_transcribed) { + store(voice_note->transcription_id, storer); + store(voice_note->text, storer); + } store(file_id, storer); } template FileId VoiceNotesManager::parse_voice_note(ParserT &parser) { auto voice_note = make_unique(); - parse(voice_note->mime_type, parser); - parse(voice_note->duration, parser); - parse(voice_note->waveform, parser); + bool has_mime_type; + bool has_duration; + bool has_waveform; + if (parser.version() >= static_cast(Version::AddVoiceNoteFlags)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_mime_type); + PARSE_FLAG(has_duration); + PARSE_FLAG(has_waveform); + PARSE_FLAG(voice_note->is_transcribed); + END_PARSE_FLAGS(); + } else { + has_mime_type = true; + has_duration = true; + has_waveform = true; + voice_note->is_transcribed = false; + } + if (has_mime_type) { + parse(voice_note->mime_type, parser); + } + if (has_duration) { + parse(voice_note->duration, parser); + } + if (has_waveform) { + parse(voice_note->waveform, parser); + } + if (voice_note->is_transcribed) { + parse(voice_note->transcription_id, parser); + parse(voice_note->text, parser); + } parse(voice_note->file_id, parser); if (parser.get_error() != nullptr || !voice_note->file_id.is_valid()) { return FileId(); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 8981e8b1f..100924012 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -352,9 +352,9 @@ class CliClient final : public Actor { int64 id = 0; string destination; string source; - int32 part_size = 0; - int32 local_size = 0; - int32 size = 0; + int64 part_size = 0; + int64 local_size = 0; + int64 size = 0; bool test_local_size_decrease = false; }; @@ -372,14 +372,14 @@ class CliClient final : public Actor { return; } else { file_generation.source = update.original_path_; - file_generation.part_size = to_integer(update.conversion_); + file_generation.part_size = to_integer(update.conversion_); file_generation.test_local_size_decrease = !update.conversion_.empty() && update.conversion_.back() == 't'; } auto r_stat = stat(file_generation.source); if (r_stat.is_ok()) { auto size = r_stat.ok().size_; - if (size <= 0 || size > (2000 << 20)) { + if (size <= 0 || size > (static_cast(4000) << 20)) { r_stat = Status::Error(400, size == 0 ? Slice("File is empty") : Slice("File is too big")); } } @@ -411,6 +411,8 @@ class CliClient final : public Actor { parameters->system_language_code_ = "en"; parameters->device_model_ = "Desktop"; parameters->application_version_ = "1.0"; + send_request( + td_api::make_object("use_pfs", td_api::make_object(true))); send_request(td_api::make_object(std::move(parameters))); break; } @@ -435,7 +437,7 @@ class CliClient final : public Actor { static char get_delimiter(Slice str) { FlatHashSet chars; for (auto c : trim(str)) { - if (!is_alnum(c) && c != '-' && c != '.' && c != '/' && c != '\0' && static_cast(c) <= 127) { + if (!is_alnum(c) && c != '-' && c != '@' && c != '.' && c != '/' && c != '\0' && static_cast(c) <= 127) { chars.insert(c); } } @@ -636,7 +638,7 @@ class CliClient final : public Actor { } static td_api::object_ptr as_generated_file(string original_path, string conversion, - int32 expected_size = 0) { + int64 expected_size = 0) { return td_api::make_object(trim(std::move(original_path)), trim(std::move(conversion)), expected_size); } @@ -803,6 +805,32 @@ class CliClient final : public Actor { arg.file_id = as_file_id(args); } + struct InputInvoice { + int64 chat_id = 0; + int64 message_id = 0; + string invoice_name; + + operator td_api::object_ptr() const { + if (invoice_name.empty()) { + return td_api::make_object(chat_id, message_id); + } else { + return td_api::make_object(invoice_name); + } + } + }; + + void get_args(string &args, InputInvoice &arg) const { + if (args.size() > 1 && args[0] == '#') { + arg.invoice_name = args; + } else { + string chat_id; + string message_id; + std::tie(chat_id, message_id) = split(args, get_delimiter(args)); + arg.chat_id = as_chat_id(chat_id); + arg.message_id = as_message_id(message_id); + } + } + template void get_args(string &args, FirstType &first_arg, SecondType &second_arg, Types &...other_args) const { string arg; @@ -825,6 +853,17 @@ class CliClient final : public Actor { result_str += " }"; break; } + case td_api::trendingStickerSets::ID: { + auto sticker_sets = static_cast(result.get()); + result_str = PSTRING() << "TrendingStickerSets { is_premium = " << sticker_sets->is_premium_ + << ", total_count = " << sticker_sets->total_count_ + << ", count = " << sticker_sets->sets_.size(); + for (auto &sticker_set : sticker_sets->sets_) { + result_str += PSTRING() << ", " << sticker_set->name_; + } + result_str += " }"; + break; + } default: break; } @@ -1650,7 +1689,7 @@ class CliClient final : public Actor { } static td_api::object_ptr get_theme_parameters() { - return td_api::make_object(0, -1, 256, 65536, 123456789, 65535); + return td_api::make_object(0, 1, -1, 256, 65536, 123456789, 65535); } static td_api::object_ptr get_background_fill(int32 color) { @@ -2000,40 +2039,36 @@ class CliClient final : public Actor { } else if (op == "gbci") { send_request(td_api::make_object(args)); } else if (op == "gpf") { - ChatId chat_id; - MessageId message_id; - get_args(args, chat_id, message_id); - send_request(td_api::make_object(chat_id, message_id, get_theme_parameters())); + InputInvoice input_invoice; + get_args(args, input_invoice); + send_request(td_api::make_object(input_invoice, get_theme_parameters())); } else if (op == "voi") { - ChatId chat_id; - MessageId message_id; + InputInvoice input_invoice; bool allow_save; - get_args(args, chat_id, message_id, allow_save); - send_request(td_api::make_object(chat_id, message_id, nullptr, allow_save)); + get_args(args, input_invoice, allow_save); + send_request(td_api::make_object(input_invoice, nullptr, allow_save)); } else if (op == "spfs") { - ChatId chat_id; - MessageId message_id; + InputInvoice input_invoice; int64 tip_amount; int64 payment_form_id; string order_info_id; string shipping_option_id; string saved_credentials_id; - get_args(args, chat_id, message_id, tip_amount, payment_form_id, order_info_id, shipping_option_id, + get_args(args, input_invoice, tip_amount, payment_form_id, order_info_id, shipping_option_id, saved_credentials_id); send_request(td_api::make_object( - chat_id, message_id, payment_form_id, order_info_id, shipping_option_id, + input_invoice, payment_form_id, order_info_id, shipping_option_id, td_api::make_object(saved_credentials_id), tip_amount)); } else if (op == "spfn") { - ChatId chat_id; - MessageId message_id; + InputInvoice input_invoice; int64 tip_amount; int64 payment_form_id; string order_info_id; string shipping_option_id; string data; - get_args(args, chat_id, message_id, tip_amount, payment_form_id, order_info_id, shipping_option_id, data); + get_args(args, input_invoice, tip_amount, payment_form_id, order_info_id, shipping_option_id, data); send_request(td_api::make_object( - chat_id, message_id, payment_form_id, order_info_id, shipping_option_id, + input_invoice, payment_form_id, order_info_id, shipping_option_id, td_api::make_object(data, true), tip_amount)); } else if (op == "gpre") { ChatId chat_id; @@ -2522,6 +2557,22 @@ class CliClient final : public Actor { execute(td_api::make_object(rand_bool() ? "en" : "", args)); } else if (op == "gadl") { send_request(td_api::make_object()); + } else if (op == "gprl") { + auto limit_type = td_api::make_object(); + send_request(td_api::make_object(std::move(limit_type))); + } else if (op == "gprf") { + auto source = td_api::make_object( + td_api::make_object()); + send_request(td_api::make_object(std::move(source))); + } else if (op == "gprst") { + send_request(td_api::make_object()); + } else if (op == "vprf") { + auto feature = td_api::make_object(); + send_request(td_api::make_object(std::move(feature))); + } else if (op == "cprsb") { + send_request(td_api::make_object()); + } else if (op == "gprs") { + send_request(td_api::make_object()); } else if (op == "atos") { send_request(td_api::make_object(args)); } else if (op == "gdli") { @@ -2718,6 +2769,8 @@ class CliClient final : public Actor { send_request(td_api::make_object(args, false, vector{"ru_RU"})); } else if (op == "gae") { send_request(td_api::make_object(args)); + } else if (op == "gaae") { + send_request(td_api::make_object()); } else if (op == "gesu") { send_request(td_api::make_object(args)); } else if (op == "gsan") { @@ -2856,19 +2909,30 @@ class CliClient final : public Actor { string to_language_code; get_args(args, text, from_language_code, to_language_code); send_request(td_api::make_object(text, from_language_code, to_language_code)); + } else if (op == "rs") { + ChatId chat_id; + MessageId message_id; + get_args(args, chat_id, message_id); + send_request(td_api::make_object(chat_id, message_id)); + } else if (op == "rsr") { + ChatId chat_id; + MessageId message_id; + bool is_good; + get_args(args, chat_id, message_id, is_good); + send_request(td_api::make_object(chat_id, message_id, is_good)); } else if (op == "gf" || op == "GetFile") { FileId file_id; get_args(args, file_id); send_request(td_api::make_object(file_id)); } else if (op == "gfdps") { FileId file_id; - int32 offset; + int64 offset; get_args(args, file_id, offset); send_request(td_api::make_object(file_id, offset)); } else if (op == "rfp") { FileId file_id; - int32 offset; - int32 count; + int64 offset; + int64 count; get_args(args, file_id, offset, count); send_request(td_api::make_object(file_id, offset, count)); } else if (op == "grf") { @@ -2886,8 +2950,8 @@ class CliClient final : public Actor { width, height, scale, chat_id)); } else if (op == "df" || op == "DownloadFile" || op == "dff" || op == "dfs") { FileId file_id; - int32 offset; - int32 limit; + int64 offset; + int64 limit; int32 priority; get_args(args, file_id, offset, limit, priority); if (priority <= 0) { @@ -3921,7 +3985,7 @@ class CliClient final : public Actor { ChatId chat_id; string photo_path; string conversion; - int32 expected_size; + int64 expected_size; get_args(args, chat_id, photo_path, conversion, expected_size); send_message(chat_id, td_api::make_object( as_generated_file(photo_path, conversion, expected_size), nullptr, vector(), 0, @@ -4127,7 +4191,11 @@ class CliClient final : public Actor { } else if (op == "dcf") { send_request(td_api::make_object(as_chat_filter_id(args))); } else if (op == "rcf") { - send_request(td_api::make_object(as_chat_filter_ids(args))); + int32 main_chat_list_position; + string chat_filter_ids; + get_args(args, main_chat_list_position, chat_filter_ids); + send_request(td_api::make_object(as_chat_filter_ids(chat_filter_ids), + main_chat_list_position)); } else if (op == "grcf") { send_request(td_api::make_object()); } else if (op == "gcfdin") { @@ -4375,6 +4443,18 @@ class CliClient final : public Actor { get_args(args, supergroup_id, sign_messages); send_request( td_api::make_object(as_supergroup_id(supergroup_id), sign_messages)); + } else if (op == "tsgjtsm") { + string supergroup_id; + bool join_to_send_message; + get_args(args, supergroup_id, join_to_send_message); + send_request(td_api::make_object(as_supergroup_id(supergroup_id), + join_to_send_message)); + } else if (op == "tsgjbr") { + string supergroup_id; + bool join_by_request; + get_args(args, supergroup_id, join_by_request); + send_request( + td_api::make_object(as_supergroup_id(supergroup_id), join_by_request)); } else if (op == "scar") { ChatId chat_id; string available_reactions; @@ -4847,7 +4927,7 @@ class CliClient final : public Actor { if (it->part_size > left_size) { it->part_size = left_size; } - BufferSlice block(it->part_size); + BufferSlice block(narrow_cast(it->part_size)); FileFd::open(it->source, FileFd::Flags::Read).move_as_ok().pread(block.as_slice(), it->local_size).ensure(); if (rand_bool()) { auto open_flags = FileFd::Flags::Write | (it->local_size ? 0 : FileFd::Flags::Truncate | FileFd::Flags::Create); diff --git a/td/telegram/files/FileData.hpp b/td/telegram/files/FileData.hpp index b916a1ccc..a6ffe6c6d 100644 --- a/td/telegram/files/FileData.hpp +++ b/td/telegram/files/FileData.hpp @@ -115,10 +115,10 @@ void FileData::parse(ParserT &parser, bool register_file_sources) { parser); if (has_sources && register_file_sources) { Td *td = G()->td().get_actor_unsafe(); - int32 size; - parse(size, parser); - if (0 < size && size < 5) { - for (int i = 0; i < size; i++) { + int32 file_source_count; + parse(file_source_count, parser); + if (0 < file_source_count && file_source_count < 5) { + for (int i = 0; i < file_source_count; i++) { if (parser.get_error()) { return; } diff --git a/td/telegram/files/FileDownloader.cpp b/td/telegram/files/FileDownloader.cpp index bc701dbba..967e6dc3e 100644 --- a/td/telegram/files/FileDownloader.cpp +++ b/td/telegram/files/FileDownloader.cpp @@ -81,20 +81,22 @@ Result FileDownloader::init() { next_part_ = narrow_cast(bitmask.get_ready_parts(0)); } fd_ = result_fd.move_as_ok(); - part_size = partial.part_size_; + CHECK(partial.part_size_ <= (1 << 20)); + CHECK(0 <= partial.part_size_); + part_size = static_cast(partial.part_size_); + CHECK((part_size & (part_size - 1)) == 0); } } - if (search_file_ && fd_.empty() && size_ > 0 && size_ < 1000 * (1 << 20) && encryption_key_.empty() && - !remote_.is_web()) { + if (search_file_ && fd_.empty() && size_ > 0 && encryption_key_.empty() && !remote_.is_web()) { [&] { - TRY_RESULT(path, search_file(get_files_dir(remote_.file_type_), name_, size_)); + TRY_RESULT(path, search_file(remote_.file_type_, name_, size_)); TRY_RESULT(fd, FileFd::open(path, FileFd::Read)); LOG(INFO) << "Check hash of local file " << path; path_ = std::move(path); fd_ = std::move(fd); need_check_ = true; only_check_ = true; - part_size = 32 * (1 << 10); + part_size = 128 * (1 << 10); bitmask = Bitmask{Bitmask::Ones{}, (size_ + part_size - 1) / part_size}; return Status::OK(); }(); @@ -119,8 +121,6 @@ Result FileDownloader::init() { } Status FileDownloader::on_ok(int64 size) { - auto dir = get_files_dir(remote_.file_type_); - std::string path; fd_.close(); if (encryption_key_.is_secure()) { @@ -136,7 +136,7 @@ Status FileDownloader::on_ok(int64 size) { if (only_check_) { path = path_; } else { - TRY_RESULT_ASSIGN(path, create_from_temp(path_, dir, name_)); + TRY_RESULT_ASSIGN(path, create_from_temp(remote_.file_type_, path_, name_)); } callback_->on_ok(FullLocalFileLocation(remote_.file_type_, std::move(path), 0), size, !only_check_); return Status::OK(); @@ -247,26 +247,25 @@ Result> FileDownloader::start_part(Part part, int32 #endif DcId dc_id = remote_.is_web() ? G()->get_webfile_dc_id() : remote_.get_dc_id(); auto id = UniqueId::next(UniqueId::Type::Default, static_cast(QueryType::Default)); - net_query = remote_.is_web() - ? G()->net_query_creator().create( - id, - telegram_api::upload_getWebFile(remote_.as_input_web_file_location(), - static_cast(part.offset), static_cast(size)), - {}, dc_id, net_query_type, NetQuery::AuthFlag::On) - : G()->net_query_creator().create( - id, - telegram_api::upload_getFile(flags, false /*ignored*/, false /*ignored*/, - remote_.as_input_file_location(), - static_cast(part.offset), static_cast(size)), - {}, dc_id, net_query_type, NetQuery::AuthFlag::On); + net_query = + remote_.is_web() + ? G()->net_query_creator().create( + id, + telegram_api::upload_getWebFile(remote_.as_input_web_file_location(), narrow_cast(part.offset), + narrow_cast(size)), + {}, dc_id, net_query_type, NetQuery::AuthFlag::On) + : G()->net_query_creator().create( + id, + telegram_api::upload_getFile(flags, false /*ignored*/, false /*ignored*/, + remote_.as_input_file_location(), part.offset, narrow_cast(size)), + {}, dc_id, net_query_type, NetQuery::AuthFlag::On); } else { if (remote_.is_web()) { return Status::Error("Can't download web file from CDN"); } auto it = cdn_part_reupload_token_.find(part.id); if (it == cdn_part_reupload_token_.end()) { - auto query = telegram_api::upload_getCdnFile(BufferSlice(cdn_file_token_), static_cast(part.offset), - static_cast(size)); + auto query = telegram_api::upload_getCdnFile(BufferSlice(cdn_file_token_), part.offset, narrow_cast(size)); cdn_part_file_token_generation_[part.id] = cdn_file_token_generation_; LOG(DEBUG) << part.id << " " << to_string(query); net_query = @@ -313,7 +312,7 @@ Result FileDownloader::process_part(Part part, NetQueryPtr net_query) { TRY_RESULT(file_base, fetch_result(net_query->ok())); CHECK(file_base->get_id() == telegram_api::upload_file::ID); auto file = move_tl_object_as(file_base); - LOG(DEBUG) << part.id << " upload_getFile result " << to_string(file); + LOG(DEBUG) << part.id << " upload.getFile result " << to_string(file); bytes = std::move(file->bytes_); } break; @@ -322,7 +321,7 @@ Result FileDownloader::process_part(Part part, NetQueryPtr net_query) { TRY_RESULT(file_base, fetch_result(net_query->ok())); CHECK(file_base->get_id() == telegram_api::upload_cdnFile::ID); auto file = move_tl_object_as(file_base); - LOG(DEBUG) << part.id << " upload_getCdnFile result " << to_string(file); + LOG(DEBUG) << part.id << " upload.getCdnFile result " << to_string(file); bytes = std::move(file->bytes_); need_cdn_decrypt = true; break; @@ -412,7 +411,7 @@ FileLoader::Callback *FileDownloader::get_callback() { Status FileDownloader::process_check_query(NetQueryPtr net_query) { has_hash_query_ = false; TRY_STATUS(check_net_query(net_query)); - TRY_RESULT(file_hashes, fetch_result(std::move(net_query))); + TRY_RESULT(file_hashes, fetch_result(std::move(net_query))); add_hash_info(file_hashes); return Status::OK(); } @@ -467,8 +466,7 @@ Result FileDownloader::check_loop(int64 checked_prefix_si } if (!has_hash_query_) { has_hash_query_ = true; - auto query = - telegram_api::upload_getFileHashes(remote_.as_input_file_location(), narrow_cast(checked_prefix_size)); + auto query = telegram_api::upload_getFileHashes(remote_.as_input_file_location(), checked_prefix_size); auto net_query_type = is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download; auto net_query = G()->net_query_creator().create(query, {}, remote_.get_dc_id(), net_query_type); info.queries.push_back(std::move(net_query)); diff --git a/td/telegram/files/FileGenerateManager.cpp b/td/telegram/files/FileGenerateManager.cpp index accea6aad..57e18a866 100644 --- a/td/telegram/files/FileGenerateManager.cpp +++ b/td/telegram/files/FileGenerateManager.cpp @@ -36,10 +36,10 @@ namespace td { class FileGenerateActor : public Actor { public: - virtual void file_generate_write_part(int32 offset, string data, Promise<> promise) { + virtual void file_generate_write_part(int64 offset, string data, Promise<> promise) { LOG(ERROR) << "Receive unexpected file_generate_write_part"; } - virtual void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) = 0; + virtual void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) = 0; virtual void file_generate_finish(Status status, Promise<> promise) = 0; }; @@ -49,7 +49,7 @@ class FileDownloadGenerateActor final : public FileGenerateActor { ActorShared<> parent) : file_type_(file_type), file_id_(file_id), callback_(std::move(callback)), parent_(std::move(parent)) { } - void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) final { + void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) final { UNREACHABLE(); } void file_generate_finish(Status status, Promise<> promise) final { @@ -118,7 +118,7 @@ class MapDownloadGenerateActor final : public FileGenerateActor { MapDownloadGenerateActor(string conversion, unique_ptr callback, ActorShared<> parent) : conversion_(std::move(conversion)), callback_(std::move(callback)), parent_(std::move(parent)) { } - void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) final { + void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) final { UNREACHABLE(); } void file_generate_finish(Status status, Promise<> promise) final { @@ -250,11 +250,11 @@ class FileExternalGenerateActor final : public FileGenerateActor { , parent_(std::move(parent)) { } - void file_generate_write_part(int32 offset, string data, Promise<> promise) final { + void file_generate_write_part(int64 offset, string data, Promise<> promise) final { check_status(do_file_generate_write_part(offset, data), std::move(promise)); } - void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) final { + void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) final { check_status(do_file_generate_progress(expected_size, local_prefix_size), std::move(promise)); } @@ -306,7 +306,7 @@ class FileExternalGenerateActor final : public FileGenerateActor { check_status(Status::Error(1, "Canceled")); } - Status do_file_generate_write_part(int32 offset, const string &data) { + Status do_file_generate_write_part(int64 offset, const string &data) { if (offset < 0) { return Status::Error("Wrong offset specified"); } @@ -320,9 +320,9 @@ class FileExternalGenerateActor final : public FileGenerateActor { return Status::OK(); } - Status do_file_generate_progress(int32 expected_size, int32 local_prefix_size) { + Status do_file_generate_progress(int64 expected_size, int64 local_prefix_size) { if (local_prefix_size < 0) { - return Status::Error(1, "Invalid local prefix size"); + return Status::Error(400, "Invalid local prefix size"); } callback_->on_partial_generate(PartialLocalFileLocation{generate_location_.file_type_, local_prefix_size, path_, "", Bitmask(Bitmask::Ones{}, 1).encode()}, @@ -331,9 +331,7 @@ class FileExternalGenerateActor final : public FileGenerateActor { } Status do_file_generate_finish() { - auto dir = get_files_dir(generate_location_.file_type_); - - TRY_RESULT(perm_path, create_from_temp(path_, dir, name_)); + TRY_RESULT(perm_path, create_from_temp(generate_location_.file_type_, path_, name_)); callback_->on_ok(FullLocalFileLocation(generate_location_.file_type_, std::move(perm_path), 0)); callback_.reset(); stop(); @@ -438,7 +436,7 @@ void FileGenerateManager::cancel(uint64 query_id) { it->second.worker_.reset(); } -void FileGenerateManager::external_file_generate_write_part(uint64 query_id, int32 offset, string data, +void FileGenerateManager::external_file_generate_write_part(uint64 query_id, int64 offset, string data, Promise<> promise) { auto it = query_id_to_query_.find(query_id); if (it == query_id_to_query_.end()) { @@ -448,7 +446,7 @@ void FileGenerateManager::external_file_generate_write_part(uint64 query_id, int std::move(promise)); } -void FileGenerateManager::external_file_generate_progress(uint64 query_id, int32 expected_size, int32 local_prefix_size, +void FileGenerateManager::external_file_generate_progress(uint64 query_id, int64 expected_size, int64 local_prefix_size, Promise<> promise) { auto it = query_id_to_query_.find(query_id); if (it == query_id_to_query_.end()) { diff --git a/td/telegram/files/FileGenerateManager.h b/td/telegram/files/FileGenerateManager.h index e01af0149..22c20f4db 100644 --- a/td/telegram/files/FileGenerateManager.h +++ b/td/telegram/files/FileGenerateManager.h @@ -26,7 +26,7 @@ class FileGenerateCallback { FileGenerateCallback &operator=(const FileGenerateCallback &) = delete; virtual ~FileGenerateCallback() = default; - virtual void on_partial_generate(PartialLocalFileLocation partial_local, int32 expected_size) = 0; + virtual void on_partial_generate(PartialLocalFileLocation partial_local, int64 expected_size) = 0; virtual void on_ok(FullLocalFileLocation local) = 0; virtual void on_error(Status error) = 0; }; @@ -41,8 +41,8 @@ class FileGenerateManager final : public Actor { void cancel(uint64 query_id); // external updates about file generation state - void external_file_generate_write_part(uint64 query_id, int32 offset, string data, Promise<> promise); - void external_file_generate_progress(uint64 query_id, int32 expected_size, int32 local_prefix_size, + void external_file_generate_write_part(uint64 query_id, int64 offset, string data, Promise<> promise); + void external_file_generate_progress(uint64 query_id, int64 expected_size, int64 local_prefix_size, Promise<> promise); void external_file_generate_finish(uint64 query_id, Status status, Promise<> promise); diff --git a/td/telegram/files/FileHashUploader.cpp b/td/telegram/files/FileHashUploader.cpp index f2f7ff606..75427bbbb 100644 --- a/td/telegram/files/FileHashUploader.cpp +++ b/td/telegram/files/FileHashUploader.cpp @@ -66,12 +66,11 @@ Status FileHashUploader::loop_impl() { TRY_STATUS(loop_sha()); } if (state_ == State::NetRequest) { - // messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; + // messages.getDocumentByHash#338e2464 sha256:bytes size:long mime_type:string = Document; auto hash = BufferSlice(32); sha256_state_.extract(hash.as_slice(), true); auto mime_type = MimeType::from_extension(PathView(local_.path_).extension(), "image/gif"); - auto query = - telegram_api::messages_getDocumentByHash(std::move(hash), static_cast(size_), std::move(mime_type)); + auto query = telegram_api::messages_getDocumentByHash(std::move(hash), size_, std::move(mime_type)); LOG(INFO) << "Send getDocumentByHash request: " << to_string(query); auto ptr = G()->net_query_creator().create(query); G()->net_query_dispatcher().dispatch_with_callback(std::move(ptr), actor_shared(this)); diff --git a/td/telegram/files/FileLoadManager.cpp b/td/telegram/files/FileLoadManager.cpp index 712b4dc9e..1662f99b0 100644 --- a/td/telegram/files/FileLoadManager.cpp +++ b/td/telegram/files/FileLoadManager.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/files/FileLoadManager.h" +#include "td/telegram/ConfigShared.h" #include "td/telegram/Global.h" #include "td/telegram/net/DcId.h" #include "td/telegram/TdParameters.h" @@ -13,6 +14,7 @@ #include "td/utils/common.h" #include "td/utils/filesystem.h" #include "td/utils/format.h" +#include "td/utils/port/path.h" #include "td/utils/SliceBuilder.h" namespace td { @@ -22,17 +24,20 @@ FileLoadManager::FileLoadManager(ActorShared callback, ActorShared<> p } void FileLoadManager::start_up() { - upload_resource_manager_ = - create_actor("UploadResourceManager", !G()->parameters().use_file_db /*tdlib_engine*/ - ? ResourceManager::Mode::Greedy - : ResourceManager::Mode::Baseline); + if (G()->shared_config().get_option_boolean("is_premium")) { + max_resource_limit_ *= 8; + } + upload_resource_manager_ = create_actor("UploadResourceManager", max_resource_limit_, + !G()->parameters().use_file_db /*tdlib_engine*/ + ? ResourceManager::Mode::Greedy + : ResourceManager::Mode::Baseline); } ActorOwn &FileLoadManager::get_download_resource_manager(bool is_small, DcId dc_id) { auto &actor = is_small ? download_small_resource_manager_map_[dc_id] : download_resource_manager_map_[dc_id]; if (actor.empty()) { actor = create_actor( - PSLICE() << "DownloadResourceManager " << tag("is_small", is_small) << tag("dc_id", dc_id), + PSLICE() << "DownloadResourceManager " << tag("is_small", is_small) << tag("dc_id", dc_id), max_resource_limit_, ResourceManager::Mode::Baseline); } return actor; @@ -45,7 +50,6 @@ void FileLoadManager::download(QueryId id, const FullRemoteFileLocation &remote_ if (stop_flag_) { return; } - CHECK(query_id_to_node_id_.count(id) == 0); NodeId node_id = nodes_container_.create(Node()); Node *node = nodes_container_.get(node_id); CHECK(node); @@ -59,7 +63,8 @@ void FileLoadManager::download(QueryId id, const FullRemoteFileLocation &remote_ auto &resource_manager = get_download_resource_manager(is_small, dc_id); send_closure(resource_manager, &ResourceManager::register_worker, ActorShared(node->loader_.get(), static_cast(-1)), priority); - query_id_to_node_id_[id] = node_id; + bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second; + CHECK(is_inserted); } void FileLoadManager::upload(QueryId id, const LocalFileLocation &local_location, @@ -68,7 +73,6 @@ void FileLoadManager::upload(QueryId id, const LocalFileLocation &local_location if (stop_flag_) { return; } - CHECK(query_id_to_node_id_.count(id) == 0); NodeId node_id = nodes_container_.create(Node()); Node *node = nodes_container_.get(node_id); CHECK(node); @@ -78,7 +82,8 @@ void FileLoadManager::upload(QueryId id, const LocalFileLocation &local_location std::move(bad_parts), std::move(callback)); send_closure(upload_resource_manager_, &ResourceManager::register_worker, ActorShared(node->loader_.get(), static_cast(-1)), priority); - query_id_to_node_id_[id] = node_id; + bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second; + CHECK(is_inserted); } void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &local_location, int64 size, @@ -86,7 +91,6 @@ void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &lo if (stop_flag_) { return; } - CHECK(query_id_to_node_id_.count(id) == 0); NodeId node_id = nodes_container_.create(Node()); Node *node = nodes_container_.get(node_id); CHECK(node); @@ -95,7 +99,8 @@ void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &lo node->loader_ = create_actor("HashUploader", local_location, size, std::move(callback)); send_closure(upload_resource_manager_, &ResourceManager::register_worker, ActorShared(node->loader_.get(), static_cast(-1)), priority); - query_id_to_node_id_[id] = node_id; + bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second; + CHECK(is_inserted); } void FileLoadManager::update_priority(QueryId id, int8 priority) { @@ -117,7 +122,6 @@ void FileLoadManager::from_bytes(QueryId id, FileType type, BufferSlice bytes, s if (stop_flag_) { return; } - CHECK(query_id_to_node_id_.count(id) == 0); NodeId node_id = nodes_container_.create(Node()); Node *node = nodes_container_.get(node_id); CHECK(node); @@ -125,12 +129,21 @@ void FileLoadManager::from_bytes(QueryId id, FileType type, BufferSlice bytes, s auto callback = make_unique(actor_shared(this, node_id)); node->loader_ = create_actor("FromBytes", type, std::move(bytes), std::move(name), std::move(callback)); - query_id_to_node_id_[id] = node_id; + bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second; + CHECK(is_inserted); } -void FileLoadManager::get_content(const FullLocalFileLocation &local_location, Promise promise) { - // TODO: send query to other thread - promise.set_result(read_file(local_location.path_)); +void FileLoadManager::get_content(string file_path, Promise promise) { + promise.set_result(read_file(file_path)); +} + +void FileLoadManager::read_file_part(string file_path, int64 offset, int64 count, Promise promise) { + promise.set_result(read_file_str(file_path, count, offset)); +} + +void FileLoadManager::unlink_file(string file_path, Promise promise) { + unlink(file_path).ignore(); + promise.set_value(Unit()); } // void upload_reload_parts(QueryId id, vector parts); @@ -172,7 +185,7 @@ void FileLoadManager::update_downloaded_part(QueryId id, int64 offset, int64 lim if (node == nullptr) { return; } - send_closure(node->loader_, &FileLoaderActor::update_downloaded_part, offset, limit); + send_closure(node->loader_, &FileLoaderActor::update_downloaded_part, offset, limit, max_resource_limit_); } void FileLoadManager::hangup() { diff --git a/td/telegram/files/FileLoadManager.h b/td/telegram/files/FileLoadManager.h index 942ad1ffb..95884eca7 100644 --- a/td/telegram/files/FileLoadManager.h +++ b/td/telegram/files/FileLoadManager.h @@ -20,6 +20,7 @@ #include "td/actor/PromiseFuture.h" #include "td/utils/buffer.h" +#include "td/utils/common.h" #include "td/utils/Container.h" #include "td/utils/Status.h" @@ -57,7 +58,11 @@ class FileLoadManager final : public Actor { void update_local_file_location(QueryId id, const LocalFileLocation &local); void update_downloaded_part(QueryId id, int64 offset, int64 limit); - void get_content(const FullLocalFileLocation &local_location, Promise promise); + void get_content(string file_path, Promise promise); + + void read_file_part(string file_path, int64 offset, int64 count, Promise promise); + + void unlink_file(string file_path, Promise promise); private: struct Node { @@ -75,6 +80,7 @@ class FileLoadManager final : public Actor { ActorShared callback_; ActorShared<> parent_; std::map query_id_to_node_id_; + int64 max_resource_limit_ = 1 << 21; bool stop_flag_ = false; void start_up() final; diff --git a/td/telegram/files/FileLoader.cpp b/td/telegram/files/FileLoader.cpp index 48979d843..0c7c22dc3 100644 --- a/td/telegram/files/FileLoader.cpp +++ b/td/telegram/files/FileLoader.cpp @@ -72,12 +72,12 @@ void FileLoader::update_local_file_location(const LocalFileLocation &local) { loop(); } -void FileLoader::update_downloaded_part(int64 offset, int64 limit) { +void FileLoader::update_downloaded_part(int64 offset, int64 limit, int64 max_resource_limit) { if (parts_manager_.get_streaming_offset() != offset) { auto begin_part_id = parts_manager_.set_streaming_offset(offset, limit); auto new_end_part_id = limit <= 0 ? parts_manager_.get_part_count() - : static_cast((offset + limit - 1) / parts_manager_.get_part_size()) + 1; - auto max_parts = static_cast(ResourceManager::MAX_RESOURCE_LIMIT / parts_manager_.get_part_size()); + : narrow_cast((offset + limit - 1) / parts_manager_.get_part_size()) + 1; + auto max_parts = narrow_cast(max_resource_limit / parts_manager_.get_part_size()); auto end_part_id = begin_part_id + td::min(max_parts, new_end_part_id - begin_part_id); VLOG(file_loader) << "Protect parts " << begin_part_id << " ... " << end_part_id - 1; for (auto &it : part_map_) { @@ -196,7 +196,7 @@ Status FileLoader::do_loop() { if (blocking_id_ != 0) { break; } - if (resource_state_.unused() < static_cast(parts_manager_.get_part_size())) { + if (resource_state_.unused() < narrow_cast(parts_manager_.get_part_size())) { VLOG(file_loader) << "Got only " << resource_state_.unused() << " resource"; break; } diff --git a/td/telegram/files/FileLoader.h b/td/telegram/files/FileLoader.h index 777362d88..941692965 100644 --- a/td/telegram/files/FileLoader.h +++ b/td/telegram/files/FileLoader.h @@ -38,7 +38,7 @@ class FileLoader : public FileLoaderActor { void update_resources(const ResourceState &other) final; void update_local_file_location(const LocalFileLocation &local) final; - void update_downloaded_part(int64 offset, int64 limit) final; + void update_downloaded_part(int64 offset, int64 limit, int64 max_resource_limit) final; protected: void set_ordered_flag(bool flag); diff --git a/td/telegram/files/FileLoaderActor.h b/td/telegram/files/FileLoaderActor.h index c9181fef0..3bbb6e79d 100644 --- a/td/telegram/files/FileLoaderActor.h +++ b/td/telegram/files/FileLoaderActor.h @@ -22,10 +22,10 @@ class FileLoaderActor : public NetQueryCallback { virtual void update_priority(int8 priority) = 0; virtual void update_resources(const ResourceState &other) = 0; - // TODO: existence of these three functions is a dirty hack. Refactoring is highly appreciated + // TODO: existence of these two functions is a dirty hack. Refactoring is highly appreciated virtual void update_local_file_location(const LocalFileLocation &local) { } - virtual void update_downloaded_part(int64 offset, int64 limit) { + virtual void update_downloaded_part(int64 offset, int64 limit, int64 max_resource_limit) { } }; diff --git a/td/telegram/files/FileLoaderUtils.cpp b/td/telegram/files/FileLoaderUtils.cpp index f2d73fb25..41b0f8a5b 100644 --- a/td/telegram/files/FileLoaderUtils.cpp +++ b/td/telegram/files/FileLoaderUtils.cpp @@ -6,7 +6,6 @@ // #include "td/telegram/files/FileLoaderUtils.h" -#include "td/telegram/files/FileLocation.h" #include "td/telegram/Global.h" #include "td/telegram/TdDb.h" #include "td/telegram/TdParameters.h" @@ -14,11 +13,9 @@ #include "td/utils/common.h" #include "td/utils/filesystem.h" #include "td/utils/format.h" -#include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/port/Clocks.h" -#include "td/utils/port/FileFd.h" #include "td/utils/port/path.h" #include "td/utils/port/Stat.h" #include "td/utils/Random.h" @@ -32,11 +29,33 @@ namespace td { int VERBOSITY_NAME(file_loader) = VERBOSITY_NAME(DEBUG) + 2; namespace { -Result> try_create_new_file(CSlice name) { - LOG(DEBUG) << "Trying to create new file " << name; - TRY_RESULT(fd, FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640)); - return std::make_pair(std::move(fd), name.str()); + +Result> try_create_new_file(CSlice path, CSlice file_name) { + LOG(DEBUG) << "Trying to create new file " << file_name << " in the directory \"" << path << '"'; + auto name = PSTRING() << path << file_name; + auto r_fd = FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640); + if (r_fd.is_error()) { + auto status = mkdir(path, 0750); + if (status.is_error()) { + auto r_stat = stat(path); + if (r_stat.is_ok() && r_stat.ok().is_dir_) { + LOG(ERROR) << "Creation of directory \"" << path << "\" failed with " << status << ", but directory exists"; + } else { + LOG(ERROR) << "Creation of directory \"" << path << "\" failed with " << status; + } + return r_fd.move_as_error(); + } +#if TD_ANDROID + FileFd::open(PSLICE() << path << ".nomedia", FileFd::Create | FileFd::Read).ignore(); +#endif + r_fd = FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640); + if (r_fd.is_error()) { + return r_fd.move_as_error(); + } + } + return std::make_pair(r_fd.move_as_ok(), std::move(name)); } + Result> try_open_file(CSlice name) { LOG(DEBUG) << "Trying to open file " << name; TRY_RESULT(fd, FileFd::open(name, FileFd::Read, 0640)); @@ -52,6 +71,7 @@ StringBuilder &operator<<(StringBuilder &sb, const RandSuff &) { } return sb; } + struct Ext { Slice ext; }; @@ -66,13 +86,13 @@ StringBuilder &operator<<(StringBuilder &sb, const Ext &ext) { Result> open_temp_file(FileType file_type) { auto pmc = G()->td_db()->get_binlog_pmc(); // TODO: CAS? - auto file_id = to_integer(pmc->get("tmp_file_id")); - pmc->set("tmp_file_id", to_string(file_id + 1)); + auto file_id = pmc->get("tmp_file_id"); + pmc->set("tmp_file_id", to_string(to_integer(file_id) + 1)); auto temp_dir = get_files_temp_dir(file_type); - auto res = try_create_new_file(PSLICE() << temp_dir << file_id); + auto res = try_create_new_file(temp_dir, file_id); if (res.is_error()) { - res = try_create_new_file(PSLICE() << temp_dir << file_id << "_" << RandSuff{6}); + res = try_create_new_file(temp_dir, PSLICE() << file_id << '_' << RandSuff{6}); } return res; } @@ -104,12 +124,13 @@ bool for_suggested_file_name(CSlice name, bool use_pmc, bool use_random, F &&cal return active; } -Result create_from_temp(CSlice temp_path, CSlice dir, CSlice name) { - LOG(INFO) << "Create file in directory " << dir << " with suggested name " << name << " from temporary file " - << temp_path; +Result create_from_temp(FileType file_type, CSlice temp_path, CSlice name) { + auto dir = get_files_dir(file_type); + LOG(INFO) << "Create file of type " << file_type << " in directory " << dir << " with suggested name " << name + << " from temporary file " << temp_path; Result> res = Status::Error(500, "Can't find suitable file name"); for_suggested_file_name(name, true, true, [&](CSlice suggested_name) { - res = try_create_new_file(PSLICE() << dir << suggested_name); + res = try_create_new_file(dir, suggested_name); return res.is_error(); }); TRY_RESULT(tmp, std::move(res)); @@ -119,15 +140,16 @@ Result create_from_temp(CSlice temp_path, CSlice dir, CSlice name) { return perm_path; } -Result search_file(CSlice dir, CSlice name, int64 expected_size) { - Result res = Status::Error(500, "Can't find suitable file name"); +Result search_file(FileType file_type, CSlice name, int64 expected_size) { + Result res = Status::Error(500, "Can't find suitable file name"); + auto dir = get_files_dir(file_type); for_suggested_file_name(name, false, false, [&](CSlice suggested_name) { auto r_pair = try_open_file(PSLICE() << dir << suggested_name); if (r_pair.is_error()) { return false; } FileFd fd; - std::string path; + string path; std::tie(fd, path) = r_pair.move_as_ok(); auto r_size = fd.get_size(); if (r_size.is_error() || r_size.ok() != expected_size) { @@ -185,17 +207,17 @@ Result get_suggested_file_name(CSlice directory, Slice file_name) { return PSTRING() << stem << " - " << StringBuilder::FixedDouble(Clocks::system(), 3) << Ext{ext}; } -Result save_file_bytes(FileType type, BufferSlice bytes, CSlice file_name) { - auto r_old_path = search_file(get_files_dir(type), file_name, bytes.size()); +Result save_file_bytes(FileType file_type, BufferSlice bytes, CSlice file_name) { + auto r_old_path = search_file(file_type, file_name, bytes.size()); if (r_old_path.is_ok()) { auto r_old_bytes = read_file(r_old_path.ok()); if (r_old_bytes.is_ok() && r_old_bytes.ok().as_slice() == bytes.as_slice()) { LOG(INFO) << "Found previous file with the same name " << r_old_path.ok(); - return FullLocalFileLocation(type, r_old_path.ok(), 0); + return FullLocalFileLocation(file_type, r_old_path.ok(), 0); } } - TRY_RESULT(fd_path, open_temp_file(type)); + TRY_RESULT(fd_path, open_temp_file(file_type)); FileFd fd = std::move(fd_path.first); string path = std::move(fd_path.second); @@ -206,10 +228,9 @@ Result save_file_bytes(FileType type, BufferSlice bytes, return Status::Error("Failed to write bytes to the file"); } - auto dir = get_files_dir(type); - TRY_RESULT(perm_path, create_from_temp(path, dir, file_name)); + TRY_RESULT(perm_path, create_from_temp(file_type, path, file_name)); - return FullLocalFileLocation(type, std::move(perm_path), 0); + return FullLocalFileLocation(file_type, std::move(perm_path), 0); } static Slice get_file_base_dir(const FileDirType &file_dir_type) { @@ -227,9 +248,11 @@ static Slice get_file_base_dir(const FileDirType &file_dir_type) { Slice get_files_base_dir(FileType file_type) { return get_file_base_dir(get_file_dir_type(file_type)); } + string get_files_temp_dir(FileType file_type) { return PSTRING() << get_files_base_dir(file_type) << "temp" << TD_DIR_SLASH; } + string get_files_dir(FileType file_type) { return PSTRING() << get_files_base_dir(file_type) << get_file_type_name(file_type) << TD_DIR_SLASH; } diff --git a/td/telegram/files/FileLoaderUtils.h b/td/telegram/files/FileLoaderUtils.h index 97228c553..5fb1c1e02 100644 --- a/td/telegram/files/FileLoaderUtils.h +++ b/td/telegram/files/FileLoaderUtils.h @@ -24,13 +24,13 @@ extern int VERBOSITY_NAME(file_loader); Result> open_temp_file(FileType file_type) TD_WARN_UNUSED_RESULT; -Result create_from_temp(CSlice temp_path, CSlice dir, CSlice name) TD_WARN_UNUSED_RESULT; +Result create_from_temp(FileType file_type, CSlice temp_path, CSlice name) TD_WARN_UNUSED_RESULT; -Result search_file(CSlice dir, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT; +Result search_file(FileType type, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT; Result get_suggested_file_name(CSlice dir, Slice file_name) TD_WARN_UNUSED_RESULT; -Result save_file_bytes(FileType type, BufferSlice bytes, CSlice file_name); +Result save_file_bytes(FileType file_type, BufferSlice bytes, CSlice file_name); Slice get_files_base_dir(FileType file_type); diff --git a/td/telegram/files/FileLocation.h b/td/telegram/files/FileLocation.h index 2a120afc4..4a80c9251 100644 --- a/td/telegram/files/FileLocation.h +++ b/td/telegram/files/FileLocation.h @@ -684,7 +684,7 @@ inline bool operator!=(const EmptyLocalFileLocation &lhs, const EmptyLocalFileLo struct PartialLocalFileLocation { FileType file_type_; - int32 part_size_; + int64 part_size_; string path_; string iv_; string ready_bitmask_; diff --git a/td/telegram/files/FileLocation.hpp b/td/telegram/files/FileLocation.hpp index 751a6deb1..19e16976d 100644 --- a/td/telegram/files/FileLocation.hpp +++ b/td/telegram/files/FileLocation.hpp @@ -313,11 +313,15 @@ void PartialLocalFileLocation::store(StorerT &storer) const { using td::store; store(file_type_, storer); store(path_, storer); - store(part_size_, storer); - int32 deprecated_ready_part_count = -1; + store(static_cast(part_size_ & 0x7FFFFFFF), storer); + int32 deprecated_ready_part_count = part_size_ > 0x7FFFFFFF ? -2 : -1; store(deprecated_ready_part_count, storer); store(iv_, storer); store(ready_bitmask_, storer); + if (deprecated_ready_part_count == -2) { + CHECK(part_size_ < (static_cast(1) << 62)); + store(static_cast(part_size_ >> 31), storer); + } } template @@ -328,12 +332,19 @@ void PartialLocalFileLocation::parse(ParserT &parser) { return parser.set_error("Invalid type in PartialLocalFileLocation"); } parse(path_, parser); - parse(part_size_, parser); + int32 part_size_low; + parse(part_size_low, parser); + part_size_ = part_size_low; int32 deprecated_ready_part_count; parse(deprecated_ready_part_count, parser); parse(iv_, parser); - if (deprecated_ready_part_count == -1) { + if (deprecated_ready_part_count == -1 || deprecated_ready_part_count == -2) { parse(ready_bitmask_, parser); + if (deprecated_ready_part_count == -2) { + int32 part_size_high; + parse(part_size_high, parser); + part_size_ += static_cast(part_size_high) << 31; + } } else { CHECK(0 <= deprecated_ready_part_count); CHECK(deprecated_ready_part_count <= (1 << 22)); diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index e4682533d..7284e0b5c 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -34,7 +34,6 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/PathView.h" -#include "td/utils/port/FileFd.h" #include "td/utils/port/path.h" #include "td/utils/port/Stat.h" #include "td/utils/ScopeGuard.h" @@ -54,7 +53,7 @@ namespace td { namespace { -constexpr int64 MAX_FILE_SIZE = 2000 * (1 << 20) /* 2000MB */; +constexpr int64 MAX_FILE_SIZE = static_cast(4000) << 20; // 4000MB } // namespace int VERBOSITY_NAME(update_file) = VERBOSITY_NAME(INFO); @@ -223,6 +222,9 @@ void FileNode::set_download_limit(int64 download_limit) { // KEEP_DOWNLOAD_LIMIT is handled here return; } + if (download_limit > MAX_FILE_SIZE) { + download_limit = MAX_FILE_SIZE; + } auto old_download_limit = get_download_limit(); private_download_limit_ = download_limit; update_effective_download_limit(old_download_limit); @@ -692,7 +694,7 @@ string FileView::path() const { case LocalFileLocation::Type::Partial: return node_->local_.partial().path_; default: - return ""; + return string(); } } @@ -828,29 +830,6 @@ FileManager::FileManager(unique_ptr context) : context_(std::move(conte next_file_id(); next_file_node_id(); - FlatHashSet dir_paths; - for (int32 i = 0; i < MAX_FILE_TYPE; i++) { - dir_paths.insert(get_files_dir(static_cast(i))); - } - // add both temp dirs - dir_paths.insert(get_files_temp_dir(FileType::Encrypted)); - dir_paths.insert(get_files_temp_dir(FileType::Video)); - - for (const auto &path : dir_paths) { - auto status = mkdir(path, 0750); - if (status.is_error()) { - auto r_stat = stat(path); - if (r_stat.is_ok() && r_stat.ok().is_dir_) { - LOG(ERROR) << "Creation of directory \"" << path << "\" failed with " << status << ", but directory exists"; - } else { - LOG(ERROR) << "Creation of directory \"" << path << "\" failed with " << status; - } - } -#if TD_ANDROID - FileFd::open(path + ".nomedia", FileFd::Create | FileFd::Read).ignore(); -#endif - }; - G()->td_db()->with_db_path([bad_paths = &bad_paths_](CSlice path) { bad_paths->insert(path.str()); }); } @@ -1057,11 +1036,11 @@ bool FileManager::try_fix_partial_local_location(FileNodePtr node) { LOG(INFO) << " failed - partial location has nonempty iv"; return false; } - if (partial.part_size_ >= 512 * (1 << 10)) { + if (partial.part_size_ >= 512 * (1 << 10) || (partial.part_size_ & (partial.part_size_ - 1)) != 0) { LOG(INFO) << " failed - too big part_size already: " << partial.part_size_; return false; } - auto old_part_size = partial.part_size_; + auto old_part_size = narrow_cast(partial.part_size_); int new_part_size = 512 * (1 << 10); auto k = new_part_size / old_part_size; Bitmask mask(Bitmask::Decode(), partial.ready_bitmask_); @@ -1227,6 +1206,15 @@ Result FileManager::register_file(FileData &&data, FileLocationSource fi return Status::Error(400, "No location"); } + if (data.size_ < 0) { + LOG(ERROR) << "Receive file of size " << data.size_; + data.size_ = 0; + } + if (data.expected_size_ < 0) { + LOG(ERROR) << "Receive file of expected size " << data.expected_size_; + data.expected_size_ = 0; + } + FileId file_id = next_file_id(); LOG(INFO) << "Register file data " << data << " as " << file_id << " from " << source; @@ -2066,10 +2054,10 @@ void FileManager::get_content(FileId file_id, Promise promise) { return promise.set_error(Status::Error("No local location")); } - send_closure(file_load_manager_, &FileLoadManager::get_content, node->local_.full(), std::move(promise)); + send_closure(file_load_manager_, &FileLoadManager::get_content, node->local_.full().path_, std::move(promise)); } -void FileManager::read_file_part(FileId file_id, int32 offset, int32 count, int left_tries, +void FileManager::read_file_part(FileId file_id, int64 offset, int64 count, int left_tries, Promise> promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); @@ -2090,14 +2078,17 @@ void FileManager::read_file_part(FileId file_id, int32 offset, int32 count, int auto file_view = FileView(node); if (count == 0) { - count = narrow_cast(file_view.downloaded_prefix(offset)); + count = file_view.downloaded_prefix(offset); if (count == 0) { return promise.set_value(td_api::make_object()); } - } else if (file_view.downloaded_prefix(offset) < static_cast(count)) { + } else if (file_view.downloaded_prefix(offset) < count) { // TODO this check is safer to do in another thread return promise.set_error(Status::Error(400, "There is not enough downloaded bytes in the file to read")); } + if (count >= static_cast(std::numeric_limits::max() / 2 - 1)) { + return promise.set_error(Status::Error(400, "Part length is too big")); + } const string *path = nullptr; bool is_partial = false; @@ -2112,38 +2103,32 @@ void FileManager::read_file_part(FileId file_id, int32 offset, int32 count, int is_partial = true; } - // TODO move file reading to another thread - auto r_bytes = [&]() -> Result { - TRY_RESULT(fd, FileFd::open(*path, FileFd::Read)); - string data; - data.resize(count); - TRY_RESULT(read_bytes, fd.pread(data, offset)); - if (read_bytes != static_cast(count)) { - return Status::Error("Read less bytes than expected"); - } - return std::move(data); - }(); - if (r_bytes.is_error()) { - LOG(INFO) << "Failed to read file bytes: " << r_bytes.error(); - if (--left_tries == 0 || !is_partial) { - return promise.set_error(Status::Error(400, "Failed to read the file")); - } + auto read_file_part_promise = + PromiseCreator::lambda([actor_id = actor_id(this), file_id, offset, count, left_tries, is_partial, + promise = std::move(promise)](Result r_bytes) mutable { + if (r_bytes.is_error()) { + LOG(INFO) << "Failed to read file bytes: " << r_bytes.error(); + if (left_tries == 1 || !is_partial) { + return promise.set_error(Status::Error(400, "Failed to read the file")); + } - // the temporary file could be moved from temp to persistent folder - // we need to wait for the corresponding update and repeat the reading - create_actor("RepeatReadFilePartActor", 0.01, - PromiseCreator::lambda([actor_id = actor_id(this), file_id, offset, count, left_tries, - promise = std::move(promise)](Result result) mutable { - send_closure(actor_id, &FileManager::read_file_part, file_id, offset, count, left_tries, - std::move(promise)); - })) - .release(); - return; - } - - auto result = td_api::make_object(); - result->data_ = r_bytes.move_as_ok(); - promise.set_value(std::move(result)); + // the temporary file could be moved from temp to persistent directory + // we need to wait for the corresponding update and repeat the reading + create_actor("RepeatReadFilePartActor", 0.01, + PromiseCreator::lambda([actor_id, file_id, offset, count, left_tries, + promise = std::move(promise)](Result result) mutable { + send_closure(actor_id, &FileManager::read_file_part, file_id, offset, count, + left_tries - 1, std::move(promise)); + })) + .release(); + } else { + auto result = td_api::make_object(); + result->data_ = r_bytes.move_as_ok(); + promise.set_value(std::move(result)); + } + }); + send_closure(file_load_manager_, &FileLoadManager::read_file_part, *path, offset, count, + std::move(read_file_part_promise)); } void FileManager::delete_file(FileId file_id, Promise promise, const char *source) { @@ -2156,30 +2141,30 @@ void FileManager::delete_file(FileId file_id, Promise promise, const char auto file_view = FileView(node); send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, file_view.file_id()); - // TODO review delete condition + string path; if (file_view.has_local_location()) { if (begins_with(file_view.local_location().path_, get_files_dir(file_view.get_type()))) { - LOG(INFO) << "Unlink file " << file_id << " at " << file_view.local_location().path_; clear_from_pmc(node); - context_->on_new_file(-file_view.size(), -file_view.get_allocated_local_size(), -1); - unlink(file_view.local_location().path_).ignore(); - node->drop_local_location(); - try_flush_node(node, "delete_file 1"); + path = std::move(node->local_.full().path_); } } else { if (file_view.get_type() == FileType::Encrypted) { clear_from_pmc(node); } if (node->local_.type() == LocalFileLocation::Type::Partial) { - LOG(INFO) << "Unlink partial file " << file_id << " at " << node->local_.partial().path_; - unlink(node->local_.partial().path_).ignore(); - node->drop_local_location(); - try_flush_node(node, "delete_file 2"); + path = std::move(node->local_.partial().path_); } } - promise.set_value(Unit()); + if (path.empty()) { + return promise.set_value(Unit()); + } + + LOG(INFO) << "Unlink file " << file_id << " at " << path; + node->drop_local_location(); + try_flush_node(node, "delete_file"); + send_closure(file_load_manager_, &FileLoadManager::unlink_file, path, std::move(promise)); } void FileManager::download(FileId file_id, std::shared_ptr callback, int32 new_priority, int64 offset, @@ -2310,7 +2295,7 @@ void FileManager::run_download(FileNodePtr node, bool force_update_priority) { auto download_limit = node->get_download_limit(); if (file_view.is_encrypted_any()) { CHECK(download_offset <= MAX_FILE_SIZE); - CHECK(download_limit <= std::numeric_limits::max()); + CHECK(download_limit <= MAX_FILE_SIZE); download_limit += download_offset; download_offset = 0; } @@ -2379,7 +2364,7 @@ void FileManager::run_download(FileNodePtr node, bool force_update_priority) { auto download_limit = node->get_download_limit(); if (file_view.is_encrypted_any()) { CHECK(download_offset <= MAX_FILE_SIZE); - CHECK(download_limit <= std::numeric_limits::max()); + CHECK(download_limit <= MAX_FILE_SIZE); download_limit += download_offset; download_offset = 0; } @@ -2664,12 +2649,12 @@ void FileManager::delete_file_reference(FileId file_id, Slice file_reference) { try_flush_node_pmc(node, "delete_file_reference"); } -void FileManager::external_file_generate_write_part(int64 id, int32 offset, string data, Promise<> promise) { +void FileManager::external_file_generate_write_part(int64 id, int64 offset, string data, Promise<> promise) { send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_write_part, id, offset, std::move(data), std::move(promise)); } -void FileManager::external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size, +void FileManager::external_file_generate_progress(int64 id, int64 expected_size, int64 local_prefix_size, Promise<> promise) { send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_progress, id, expected_size, local_prefix_size, std::move(promise)); @@ -2745,7 +2730,7 @@ void FileManager::run_generate(FileNodePtr node) { public: Callback(ActorId actor, QueryId id) : actor_(std::move(actor)), query_id_(id) { } - void on_partial_generate(PartialLocalFileLocation partial_local, int32 expected_size) final { + void on_partial_generate(PartialLocalFileLocation partial_local, int64 expected_size) final { send_closure(actor_, &FileManager::on_partial_generate, query_id_, std::move(partial_local), expected_size); } @@ -3015,12 +3000,12 @@ td_api::object_ptr FileManager::get_file_object(FileId file_id, bo string persistent_file_id = file_view.get_persistent_file_id(); string unique_file_id = file_view.get_unique_file_id(); bool is_uploading_completed = !persistent_file_id.empty(); - auto size = narrow_cast(file_view.size()); - auto expected_size = narrow_cast(file_view.expected_size()); - auto download_offset = narrow_cast(file_view.download_offset()); - auto local_prefix_size = narrow_cast(file_view.local_prefix_size()); - auto local_total_size = narrow_cast(file_view.local_total_size()); - auto remote_size = narrow_cast(file_view.remote_size()); + auto size = file_view.size(); + auto expected_size = file_view.expected_size(); + auto download_offset = file_view.download_offset(); + auto local_prefix_size = file_view.local_prefix_size(); + auto local_total_size = file_view.local_total_size(); + auto remote_size = file_view.remote_size(); string path = file_view.path(); bool can_be_downloaded = file_view.can_download_from_server() || file_view.can_generate(); bool can_be_deleted = file_view.can_delete(); @@ -3616,7 +3601,7 @@ void FileManager::on_upload_full_ok(QueryId query_id, FullRemoteFileLocation rem LOG_STATUS(merge(new_file_id, file_id)); } -void FileManager::on_partial_generate(QueryId query_id, PartialLocalFileLocation partial_local, int32 expected_size) { +void FileManager::on_partial_generate(QueryId query_id, PartialLocalFileLocation partial_local, int64 expected_size) { if (is_closed_) { return; } @@ -3728,28 +3713,6 @@ void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_act SCOPE_EXIT { try_flush_node(node, "on_error"); }; - if (status.code() != 1 && !G()->close_flag()) { - LOG(WARNING) << "Failed to " << type << " file " << node->main_file_id_ << " of type " << FileView(node).get_type() - << ": " << status; - if (status.code() == 0) { - // Remove partial locations - if (node->local_.type() == LocalFileLocation::Type::Partial && - !begins_with(status.message(), "FILE_UPLOAD_RESTART") && - !begins_with(status.message(), "FILE_DOWNLOAD_RESTART") && - !begins_with(status.message(), "FILE_DOWNLOAD_ID_INVALID") && - !begins_with(status.message(), "FILE_DOWNLOAD_LIMIT")) { - CSlice path = node->local_.partial().path_; - if (begins_with(path, get_files_temp_dir(FileType::Encrypted)) || - begins_with(path, get_files_temp_dir(FileType::Video))) { - LOG(INFO) << "Unlink file " << path; - unlink(path).ignore(); - node->drop_local_location(); - } - } - node->delete_partial_remote_location(); - status = Status::Error(400, status.message()); - } - } if (status.message() == "FILE_PART_INVALID") { bool has_partial_small_location = node->remote_.partial && !node->remote_.partial->is_big_; @@ -3763,9 +3726,9 @@ void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_act return; } - LOG(WARNING) << "Failed to upload file " << node->main_file_id_ << ": unexpected " << status - << ", is_small = " << has_partial_small_location << ", should_be_big = " << should_be_big_location - << ", expected size = " << expected_size; + LOG(ERROR) << "Failed to upload file " << node->main_file_id_ << ": unexpected " << status + << ", is_small = " << has_partial_small_location << ", should_be_big = " << should_be_big_location + << ", expected size = " << expected_size; } if (begins_with(status.message(), "FILE_GENERATE_LOCATION_INVALID")) { @@ -3807,6 +3770,7 @@ void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_act run_upload(node, {}); return; } + if (begins_with(status.message(), "FILE_DOWNLOAD_RESTART")) { if (ends_with(status.message(), "WITH_FILE_REFERENCE")) { node->download_was_update_file_reference_ = true; @@ -3824,10 +3788,37 @@ void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_act } } + if (status.message() == "MTPROTO_CLUSTER_INVALID") { + run_download(node, true); + return; + } + if (!was_active) { return; } + if (status.code() != 1 && !G()->close_flag()) { + LOG(WARNING) << "Failed to " << type << " file " << node->main_file_id_ << " of type " << FileView(node).get_type() + << ": " << status; + if (status.code() == 0) { + // Remove partial locations + if (node->local_.type() == LocalFileLocation::Type::Partial && + !begins_with(status.message(), "FILE_DOWNLOAD_ID_INVALID") && + !begins_with(status.message(), "FILE_DOWNLOAD_LIMIT")) { + CSlice path = node->local_.partial().path_; + if (begins_with(path, get_files_temp_dir(FileType::Encrypted)) || + begins_with(path, get_files_temp_dir(FileType::Video))) { + LOG(INFO) << "Unlink file " << path; + send_closure(file_load_manager_, &FileLoadManager::unlink_file, std::move(node->local_.partial().path_), + Promise()); + node->drop_local_location(); + } + } + node->delete_partial_remote_location(); + status = Status::Error(400, status.message()); + } + } + // Stop everything on error do_cancel_generate(node); do_cancel_download(node); diff --git a/td/telegram/files/FileManager.h b/td/telegram/files/FileManager.h index 38214c04b..a393fc311 100644 --- a/td/telegram/files/FileManager.h +++ b/td/telegram/files/FileManager.h @@ -461,13 +461,13 @@ class FileManager final : public FileLoadManager::Callback { Result get_suggested_file_name(FileId file_id, const string &directory); - void read_file_part(FileId file_id, int32 offset, int32 count, int left_tries, + void read_file_part(FileId file_id, int64 offset, int64 count, int left_tries, Promise> promise); void delete_file(FileId file_id, Promise promise, const char *source); - void external_file_generate_write_part(int64 id, int32 offset, string data, Promise<> promise); - void external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size, Promise<> promise); + void external_file_generate_write_part(int64 id, int64 offset, string data, Promise<> promise); + void external_file_generate_progress(int64 id, int64 expected_size, int64 local_prefix_size, Promise<> promise); void external_file_generate_finish(int64 id, Status status, Promise<> promise); Result from_persistent_id(CSlice persistent_id, FileType file_type) TD_WARN_UNUSED_RESULT; @@ -658,7 +658,7 @@ class FileManager final : public FileLoadManager::Callback { void on_error_impl(FileNodePtr node, Query::Type type, bool was_active, Status status); - void on_partial_generate(QueryId, PartialLocalFileLocation partial_local, int32 expected_size); + void on_partial_generate(QueryId, PartialLocalFileLocation partial_local, int64 expected_size); void on_generate_ok(QueryId, FullLocalFileLocation local); std::pair finish_query(QueryId query_id); diff --git a/td/telegram/files/FileManager.hpp b/td/telegram/files/FileManager.hpp index da4d08e10..520739016 100644 --- a/td/telegram/files/FileManager.hpp +++ b/td/telegram/files/FileManager.hpp @@ -20,6 +20,8 @@ #include "td/utils/SliceBuilder.h" #include "td/utils/tl_helpers.h" +#include + namespace td { enum class FileStoreType : int32 { Empty, Url, Generate, Local, Remote }; @@ -45,13 +47,21 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const { bool has_expected_size = file_store_type == FileStoreType::Remote && file_view.size() == 0 && file_view.expected_size() != 0; bool has_secure_key = false; + int64 size = 0; + bool has_64bit_size = false; if (file_store_type != FileStoreType::Empty) { has_encryption_key = !file_view.empty() && file_view.is_encrypted_secret(); has_secure_key = !file_view.empty() && file_view.is_encrypted_secure(); + if (file_store_type != FileStoreType::Url) { + size = has_expected_size || file_store_type == FileStoreType::Generate ? file_view.expected_size() + : file_view.size(); + has_64bit_size = (size > std::numeric_limits::max()); + } BEGIN_STORE_FLAGS(); STORE_FLAG(has_encryption_key); STORE_FLAG(has_expected_size); STORE_FLAG(has_secure_key); + STORE_FLAG(has_64bit_size); END_STORE_FLAGS(); } @@ -65,10 +75,10 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const { break; case FileStoreType::Remote: { store(file_view.remote_location(), storer); - if (has_expected_size) { - store(narrow_cast(file_view.expected_size()), storer); + if (has_64bit_size) { + store(size, storer); } else { - store(narrow_cast(file_view.size()), storer); + store(narrow_cast(size), storer); } store(file_view.remote_name(), storer); store(file_view.owner_dialog_id(), storer); @@ -76,7 +86,11 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const { } case FileStoreType::Local: { store(file_view.local_location(), storer); - store(narrow_cast(file_view.size()), storer); + if (has_64bit_size) { + store(size, storer); + } else { + store(narrow_cast(size), storer); + } store(static_cast(file_view.get_by_hash()), storer); store(file_view.owner_dialog_id(), storer); break; @@ -95,8 +109,12 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const { have_file_id = true; } store(generate_location, storer); - store(static_cast(file_view.expected_size()), storer); - store(static_cast(0), storer); + if (has_64bit_size) { + store(size, storer); + } else { + store(narrow_cast(size), storer); + store(static_cast(0), storer); // legacy + } store(file_view.owner_dialog_id(), storer); if (have_file_id) { @@ -124,12 +142,14 @@ FileId FileManager::parse_file(ParserT &parser) { bool has_encryption_key = false; bool has_expected_size = false; bool has_secure_key = false; + bool has_64bit_size = false; if (file_store_type != FileStoreType::Empty) { if (parser.version() >= static_cast(Version::StoreFileEncryptionKey)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_encryption_key); PARSE_FLAG(has_expected_size); PARSE_FLAG(has_secure_key); + PARSE_FLAG(has_64bit_size); END_PARSE_FLAGS(); } } @@ -141,13 +161,19 @@ FileId FileManager::parse_file(ParserT &parser) { case FileStoreType::Remote: { FullRemoteFileLocation full_remote_location; parse(full_remote_location, parser); - int32 size = 0; - int32 expected_size = 0; - if (has_expected_size) { - parse(expected_size, parser); + int64 stored_size; + if (has_64bit_size) { + parse(stored_size, parser); } else { - parse(size, parser); + int32 int_size; + parse(int_size, parser); + stored_size = int_size; + if (stored_size < 0) { + stored_size += static_cast(1) << 32; + } } + int64 size = has_expected_size ? 0 : stored_size; + int64 expected_size = has_expected_size ? stored_size : 0; string name; parse(name, parser); DialogId owner_dialog_id; @@ -160,8 +186,17 @@ FileId FileManager::parse_file(ParserT &parser) { case FileStoreType::Local: { FullLocalFileLocation full_local_location; parse(full_local_location, parser); - int32 size; - parse(size, parser); + int64 size; + if (has_64bit_size) { + parse(size, parser); + } else { + int32 int_size; + parse(int_size, parser); + size = int_size; + if (size < 0) { + size += static_cast(1) << 32; + } + } int32 get_by_hash; parse(get_by_hash, parser); DialogId owner_dialog_id; @@ -179,8 +214,17 @@ FileId FileManager::parse_file(ParserT &parser) { case FileStoreType::Generate: { FullGenerateFileLocation full_generated_location; parse(full_generated_location, parser); - int32 expected_size; - parse(expected_size, parser); + int64 expected_size; + if (has_64bit_size) { + parse(expected_size, parser); + } else { + int32 int_size; + parse(int_size, parser); + expected_size = int_size; + if (expected_size < 0) { + expected_size += static_cast(1) << 32; + } + } int32 zero; parse(zero, parser); DialogId owner_dialog_id; diff --git a/td/telegram/files/PartsManager.cpp b/td/telegram/files/PartsManager.cpp index 362f48edb..3e6a7c8af 100644 --- a/td/telegram/files/PartsManager.cpp +++ b/td/telegram/files/PartsManager.cpp @@ -47,7 +47,7 @@ int32 PartsManager::set_streaming_offset(int64 offset, int64 limit) { } auto part_i = offset / part_size_; - if (use_part_count_limit_ && part_i >= MAX_PART_COUNT) { + if (use_part_count_limit_ && part_i >= MAX_PART_COUNT_PREMIUM) { streaming_offset_ = 0; LOG(ERROR) << "Ignore streaming_offset " << offset << " in part " << part_i; @@ -92,9 +92,8 @@ Status PartsManager::init_no_size(size_t part_size, const std::vector &read part_size_ = part_size; } else { part_size_ = 32 << 10; - while (calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { + while (part_size_ < MAX_PART_SIZE && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { part_size_ *= 2; - CHECK(part_size_ <= MAX_PART_SIZE); } // just in case if expected_size_ is wrong if (part_size_ < MAX_PART_SIZE) { @@ -128,19 +127,19 @@ Status PartsManager::init(int64 size, int64 expected_size, bool is_size_final, s if (part_size != 0) { part_size_ = part_size; - if (use_part_count_limit_ && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { + if (use_part_count_limit_ && part_size_ < MAX_PART_SIZE && + calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { CHECK(is_upload_); return Status::Error("FILE_UPLOAD_RESTART"); } } else { part_size_ = 64 << 10; - while (calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { + while (part_size_ < MAX_PART_SIZE && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { part_size_ *= 2; - CHECK(part_size_ <= MAX_PART_SIZE); } } LOG_CHECK(1 <= size_) << tag("size_", size_); - LOG_CHECK(!use_part_count_limit || calc_part_count(expected_size_, part_size_) <= MAX_PART_COUNT) + LOG_CHECK(!use_part_count_limit || calc_part_count(expected_size_, part_size_) <= MAX_PART_COUNT_PREMIUM) << tag("size_", size_) << tag("expected_size", size_) << tag("is_size_final", is_size_final) << tag("part_size_", part_size_) << tag("ready_parts", ready_parts.size()); part_count_ = static_cast(calc_part_count(size_, part_size_)); @@ -287,7 +286,7 @@ Result PartsManager::start_part() { if (part_i == part_count_) { if (unknown_size_flag_) { part_count_++; - if (part_count_ > MAX_PART_COUNT + (use_part_count_limit_ ? 0 : 64)) { + if (part_count_ > MAX_PART_COUNT_PREMIUM + (use_part_count_limit_ ? 0 : 64)) { if (!is_upload_) { // Caller will try to increase part size if it is possible return Status::Error("FILE_DOWNLOAD_RESTART_INCREASE_PART_SIZE"); @@ -334,7 +333,8 @@ Status PartsManager::set_known_prefix(size_t size, bool is_ready) { LOG_CHECK(static_cast(part_count_) >= part_status_.size()) << size << " " << is_ready << " " << part_count_ << " " << part_size_ << " " << part_status_.size(); part_status_.resize(part_count_); - if (use_part_count_limit_ && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { + if (use_part_count_limit_ && part_size_ < MAX_PART_SIZE && + calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { CHECK(is_upload_); return Status::Error("FILE_UPLOAD_RESTART"); } diff --git a/td/telegram/files/PartsManager.h b/td/telegram/files/PartsManager.h index 11a41d7d1..fc8c708d7 100644 --- a/td/telegram/files/PartsManager.h +++ b/td/telegram/files/PartsManager.h @@ -55,8 +55,9 @@ class PartsManager { private: static constexpr int MAX_PART_COUNT = 4000; - static constexpr size_t MAX_PART_SIZE = 512 * (1 << 10); - static constexpr int64 MAX_FILE_SIZE = static_cast(MAX_PART_SIZE) * MAX_PART_COUNT; + static constexpr int MAX_PART_COUNT_PREMIUM = 8000; + static constexpr size_t MAX_PART_SIZE = 512 << 10; + static constexpr int64 MAX_FILE_SIZE = static_cast(MAX_PART_SIZE) * MAX_PART_COUNT_PREMIUM; enum class PartStatus : int32 { Empty, Pending, Ready }; diff --git a/td/telegram/files/ResourceManager.cpp b/td/telegram/files/ResourceManager.cpp index e57815def..2f9b7bbc8 100644 --- a/td/telegram/files/ResourceManager.cpp +++ b/td/telegram/files/ResourceManager.cpp @@ -132,7 +132,7 @@ void ResourceManager::loop() { return; } auto active_limit = resource_state_.active_limit(); - resource_state_.update_limit(MAX_RESOURCE_LIMIT - active_limit); + resource_state_.update_limit(max_resource_limit_ - active_limit); LOG(INFO) << tag("unused", resource_state_.unused()); if (mode_ == Mode::Greedy) { diff --git a/td/telegram/files/ResourceManager.h b/td/telegram/files/ResourceManager.h index ae6ec36e3..688a004c5 100644 --- a/td/telegram/files/ResourceManager.h +++ b/td/telegram/files/ResourceManager.h @@ -21,7 +21,7 @@ namespace td { class ResourceManager final : public Actor { public: enum class Mode : int32 { Baseline, Greedy }; - explicit ResourceManager(Mode mode) : mode_(mode) { + ResourceManager(int64 max_resource_limit, Mode mode) : max_resource_limit_(max_resource_limit), mode_(mode) { } // use through ActorShared void update_priority(int8 priority); @@ -29,10 +29,10 @@ class ResourceManager final : public Actor { void register_worker(ActorShared callback, int8 priority); - static constexpr int64 MAX_RESOURCE_LIMIT = 1 << 21; - private: + int64 max_resource_limit_ = 0; Mode mode_; + using NodeId = uint64; struct Node final : public HeapNode { NodeId node_id = 0; diff --git a/td/telegram/net/ConnectionCreator.cpp b/td/telegram/net/ConnectionCreator.cpp index e0874d62d..eaa9671c6 100644 --- a/td/telegram/net/ConnectionCreator.cpp +++ b/td/telegram/net/ConnectionCreator.cpp @@ -179,8 +179,8 @@ void ConnectionCreator::add_proxy(int32 old_proxy_id, string server, int32 port, proxy_id = max_proxy_id_++; G()->td_db()->get_binlog_pmc()->set("proxy_max_id", to_string(max_proxy_id_)); } - CHECK(proxies_.count(proxy_id) == 0); - proxies_.emplace(proxy_id, std::move(new_proxy)); + bool is_inserted = proxies_.emplace(proxy_id, std::move(new_proxy)).second; + CHECK(is_inserted); G()->td_db()->get_binlog_pmc()->set(get_proxy_database_key(proxy_id), log_event_store(proxies_[proxy_id]).as_slice().str()); return proxy_id; @@ -420,7 +420,7 @@ void ConnectionCreator::set_active_proxy_id(int32 proxy_id, bool from_binlog) { if (!from_binlog) { if (proxy_id == 0) { G()->td_db()->get_binlog_pmc()->erase("proxy_active_id"); - send_closure(G()->config_manager(), &ConfigManager::request_config); + send_closure(G()->config_manager(), &ConfigManager::request_config, false); } else { G()->td_db()->get_binlog_pmc()->set("proxy_active_id", to_string(proxy_id)); } diff --git a/td/telegram/net/NetQuery.cpp b/td/telegram/net/NetQuery.cpp index 6eb0f0006..7250fcd1f 100644 --- a/td/telegram/net/NetQuery.cpp +++ b/td/telegram/net/NetQuery.cpp @@ -14,6 +14,7 @@ #include "td/utils/as.h" #include "td/utils/misc.h" #include "td/utils/SliceBuilder.h" +#include "td/utils/Time.h" #include diff --git a/td/telegram/net/NetQueryDispatcher.cpp b/td/telegram/net/NetQueryDispatcher.cpp index 263816ef0..ccbe5a642 100644 --- a/td/telegram/net/NetQueryDispatcher.cpp +++ b/td/telegram/net/NetQueryDispatcher.cpp @@ -168,9 +168,10 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) { int32 slow_net_scheduler_id = G()->get_slow_net_scheduler_id(); auto raw_dc_id = dc_id.get_raw_id(); - int32 upload_session_count = raw_dc_id != 2 && raw_dc_id != 4 ? 8 : 4; - int32 download_session_count = 2; - int32 download_small_session_count = 2; + bool is_premium = G()->shared_config().get_option_boolean("is_premium"); + int32 upload_session_count = (raw_dc_id != 2 && raw_dc_id != 4) || is_premium ? 8 : 4; + int32 download_session_count = is_premium ? 8 : 2; + int32 download_small_session_count = is_premium ? 8 : 2; dc.main_session_ = create_actor(PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":main", session_count, auth_data, raw_dc_id == main_dc_id_, use_pfs, false, false, is_cdn, need_destroy_key); diff --git a/td/telegram/net/Session.cpp b/td/telegram/net/Session.cpp index 655086071..362adc812 100644 --- a/td/telegram/net/Session.cpp +++ b/td/telegram/net/Session.cpp @@ -31,6 +31,7 @@ #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/port/thread_local.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" @@ -41,6 +42,7 @@ #include "td/utils/utf8.h" #include "td/utils/VectorQueue.h" +#include #include #include @@ -65,6 +67,11 @@ class SemaphoreActor final : public Actor { size_t capacity_; VectorQueue>> pending_; + void start_up() final { + set_context(std::make_shared()); + set_tag(string()); + } + void finish(Result) { capacity_++; if (!pending_.empty()) { @@ -80,9 +87,8 @@ class SemaphoreActor final : public Actor { }; struct Semaphore { - public: explicit Semaphore(size_t capacity) { - semaphore_ = create_actor("semaphore", capacity).release(); + semaphore_ = create_actor("Semaphore", capacity).release(); } void execute(Promise> promise) { @@ -221,6 +227,9 @@ Session::Session(unique_ptr callback, std::shared_ptr auth_data_.set_future_salts(shared_auth_data_->get_future_salts(), Time::now()); if (use_pfs && !tmp_auth_key.empty()) { auth_data_.set_tmp_auth_key(tmp_auth_key); + if (is_main_) { + registered_temp_auth_key_ = TempAuthKeyWatchdog::register_auth_key_id(auth_data_.get_tmp_auth_key().id()); + } auth_data_.set_future_salts(server_salts, Time::now()); } uint64 session_id = 0; @@ -575,10 +584,10 @@ void Session::on_closed(Status status) { raw_connection->close(); if (status.is_error()) { - LOG(WARNING) << "Session with " << sent_queries_.size() << " pending requests was closed: " << status << " " - << current_info_->connection_->get_name(); + LOG(WARNING) << "Session connection with " << sent_queries_.size() << " pending requests was closed: " << status + << ' ' << current_info_->connection_->get_name(); } else { - LOG(INFO) << "Session with " << sent_queries_.size() << " pending requests was closed: " << status << " " + LOG(INFO) << "Session connection with " << sent_queries_.size() << " pending requests was closed: " << status << ' ' << current_info_->connection_->get_name(); } diff --git a/td/telegram/net/SessionProxy.cpp b/td/telegram/net/SessionProxy.cpp index ed0d74119..cb2de374c 100644 --- a/td/telegram/net/SessionProxy.cpp +++ b/td/telegram/net/SessionProxy.cpp @@ -173,6 +173,7 @@ void SessionProxy::close_session() { send_closure(std::move(session_), &Session::close); session_generation_++; } + void SessionProxy::open_session(bool force) { if (!session_.empty()) { return; diff --git a/td/telegram/net/TempAuthKeyWatchdog.h b/td/telegram/net/TempAuthKeyWatchdog.h index 52c970fa1..f81aef411 100644 --- a/td/telegram/net/TempAuthKeyWatchdog.h +++ b/td/telegram/net/TempAuthKeyWatchdog.h @@ -63,6 +63,7 @@ class TempAuthKeyWatchdog final : public NetQueryCallback { bool run_sync_ = false; void register_auth_key_id_impl(int64 id) { + LOG(INFO) << "Register key " << id; if (!++id_count_[id]) { id_count_.erase(id); } @@ -70,6 +71,7 @@ class TempAuthKeyWatchdog final : public NetQueryCallback { } void unregister_auth_key_id_impl(int64 id) { + LOG(INFO) << "Unregister key " << id; if (!--id_count_[id]) { id_count_.erase(id); } diff --git a/tdactor/test/actors_simple.cpp b/tdactor/test/actors_simple.cpp index 9551053c8..a76b80884 100644 --- a/tdactor/test/actors_simple.cpp +++ b/tdactor/test/actors_simple.cpp @@ -329,7 +329,7 @@ class MasterActor final : public MsgActor { public: void loop() final { alive_ = true; - slave = td::create_actor("slave", static_cast>(actor_id(this))); + slave = td::create_actor("Slave", static_cast>(actor_id(this))); stop(); } td::ActorOwn slave; diff --git a/tdnet/td/net/HttpReader.h b/tdnet/td/net/HttpReader.h index a0c10d5d1..4e86795a3 100644 --- a/tdnet/td/net/HttpReader.h +++ b/tdnet/td/net/HttpReader.h @@ -105,7 +105,7 @@ class HttpReader { static constexpr size_t MAX_TOTAL_PARAMETERS_LENGTH = 1 << 20; // Some reasonable limit static constexpr size_t MAX_TOTAL_HEADERS_LENGTH = 1 << 18; // Some reasonable limit static constexpr size_t MAX_BOUNDARY_LENGTH = 70; // As defined by RFC1341 - static constexpr int64 MAX_FILE_SIZE = 2000 << 20; // Telegram server file size limit + static constexpr int64 MAX_FILE_SIZE = static_cast(4000) << 20; // Telegram server file size limit static constexpr const char TEMP_DIRECTORY_PREFIX[] = "tdlib-server-tmp"; }; diff --git a/tdutils/td/utils/SetNode.h b/tdutils/td/utils/SetNode.h index e04127494..7e99cb1fb 100644 --- a/tdutils/td/utils/SetNode.h +++ b/tdutils/td/utils/SetNode.h @@ -9,6 +9,9 @@ #include "td/utils/common.h" #include "td/utils/HashTableUtils.h" +#include +#include + namespace td { template @@ -71,7 +74,7 @@ struct SetNode 28 * sizeof(void KeyT first{}; template - Impl(InputKeyT &&key) : first(std::forward(key)) { + explicit Impl(InputKeyT &&key) : first(std::forward(key)) { DCHECK(!is_hash_table_key_empty(first)); } Impl(const Impl &other) = delete; diff --git a/tdutils/td/utils/emoji.cpp b/tdutils/td/utils/emoji.cpp index cbaa9c7f4..53994a3ee 100644 --- a/tdutils/td/utils/emoji.cpp +++ b/tdutils/td/utils/emoji.cpp @@ -9,7 +9,6 @@ #include "td/utils/base64.h" #include "td/utils/FlatHashSet.h" #include "td/utils/Gzip.h" -#include "td/utils/misc.h" namespace td { diff --git a/tdutils/test/port.cpp b/tdutils/test/port.cpp index fcaafe5b2..a7c673e18 100644 --- a/tdutils/test/port.cpp +++ b/tdutils/test/port.cpp @@ -122,6 +122,30 @@ TEST(Port, SparseFiles) { td::unlink(path).ensure(); } +TEST(Port, LargeFiles) { + td::CSlice path = "large.txt"; + td::unlink(path).ignore(); + auto fd = td::FileFd::open(path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok(); + ASSERT_EQ(0, fd.get_size().move_as_ok()); + td::int64 offset = static_cast(3) << 30; + if (fd.pwrite("abcd", offset).is_error()) { + LOG(ERROR) << "Writing to large files isn't supported"; + td::unlink(path).ensure(); + return; + } + fd = td::FileFd::open(path, td::FileFd::Read).move_as_ok(); + ASSERT_EQ(offset + 4, fd.get_size().move_as_ok()); + td::string res(4, '\0'); + if (fd.pread(res, offset).is_error()) { + LOG(ERROR) << "Reading of large files isn't supported"; + td::unlink(path).ensure(); + return; + } + ASSERT_STREQ(res, "abcd"); + fd.close(); + td::unlink(path).ensure(); +} + TEST(Port, Writev) { td::vector vec; td::CSlice test_file_path = "test.txt"; diff --git a/test/link.cpp b/test/link.cpp index 405ad2c33..af4338745 100644 --- a/test/link.cpp +++ b/test/link.cpp @@ -95,13 +95,26 @@ static void parse_internal_link(const td::string &url, td::td_api::object_ptr(allow_users, allow_bots, allow_groups, allow_channels); + }; + auto active_sessions = [] { return td::td_api::make_object(); }; - auto attachment_menu_bot = [](td::td_api::object_ptr chat_link, + auto attachment_menu_bot = [](td::td_api::object_ptr chat_types, + td::td_api::object_ptr chat_link, const td::string &bot_username, const td::string &start_parameter) { + td::td_api::object_ptr target_chat; + if (chat_link != nullptr) { + target_chat = td::td_api::make_object(std::move(chat_link)); + } else if (chat_types != nullptr) { + target_chat = std::move(chat_types); + } else { + target_chat = td::td_api::make_object(); + } return td::td_api::make_object( - std::move(chat_link), bot_username, start_parameter.empty() ? td::string() : "start://" + start_parameter); + std::move(target_chat), bot_username, start_parameter.empty() ? td::string() : "start://" + start_parameter); }; auto authentication_code = [](const td::string &code) { return td::td_api::make_object(code); @@ -134,6 +147,9 @@ TEST(Link, parse_internal_link) { auto game = [](const td::string &bot_username, const td::string &game_short_name) { return td::td_api::make_object(bot_username, game_short_name); }; + auto invoice = [](const td::string &invoice_name) { + return td::td_api::make_object(invoice_name); + }; auto language_pack = [](const td::string &language_pack_name) { return td::td_api::make_object(language_pack_name); }; @@ -156,6 +172,9 @@ TEST(Link, parse_internal_link) { auto phone_number_confirmation = [](const td::string &hash, const td::string &phone_number) { return td::td_api::make_object(hash, phone_number); }; + auto premium_features = [](const td::string &referrer) { + return td::td_api::make_object(referrer); + }; auto privacy_and_security_settings = [] { return td::td_api::make_object(); }; @@ -236,15 +255,29 @@ TEST(Link, parse_internal_link) { parse_internal_link("tg:resolve?domain=telegram&post=&single", public_chat("telegram")); parse_internal_link("tg:resolve?domain=123456&post=&single", unknown_deep_link("tg://resolve?domain=123456&post=&single")); - parse_internal_link("tg:resolve?domain=telegram&startattach", attachment_menu_bot(nullptr, "telegram", "")); - parse_internal_link("tg:resolve?domain=telegram&startattach=1", attachment_menu_bot(nullptr, "telegram", "1")); - parse_internal_link("tg:resolve?domain=telegram&attach=&startattach", attachment_menu_bot(nullptr, "telegram", "")); + parse_internal_link("tg:resolve?domain=telegram&startattach", attachment_menu_bot(nullptr, nullptr, "telegram", "")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1", + attachment_menu_bot(nullptr, nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=cats+dogs", + attachment_menu_bot(nullptr, nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=users", + attachment_menu_bot(target_chat_chosen(true, false, false, false), nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=bots", + attachment_menu_bot(target_chat_chosen(false, true, false, false), nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=groups", + attachment_menu_bot(target_chat_chosen(false, false, true, false), nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=channels", + attachment_menu_bot(target_chat_chosen(false, false, false, true), nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=users+channels", + attachment_menu_bot(target_chat_chosen(true, false, false, true), nullptr, "telegram", "1")); + parse_internal_link("tg:resolve?domain=telegram&attach=&startattach", + attachment_menu_bot(nullptr, nullptr, "telegram", "")); parse_internal_link("tg:resolve?domain=telegram&attach=&startattach=1", - attachment_menu_bot(nullptr, "telegram", "1")); + attachment_menu_bot(nullptr, nullptr, "telegram", "1")); parse_internal_link("tg:resolve?domain=telegram&attach=test&startattach", - attachment_menu_bot(public_chat("telegram"), "test", "")); + attachment_menu_bot(nullptr, public_chat("telegram"), "test", "")); parse_internal_link("tg:resolve?domain=telegram&attach=test&startattach=1", - attachment_menu_bot(public_chat("telegram"), "test", "1")); + attachment_menu_bot(nullptr, public_chat("telegram"), "test", "1")); parse_internal_link("tg:resolve?phone=1", user_phone_number("1")); parse_internal_link("tg:resolve?phone=123456", user_phone_number("123456")); @@ -254,11 +287,11 @@ TEST(Link, parse_internal_link) { parse_internal_link("tg:resolve?phone=123456&attach=&startattach", user_phone_number("123456")); parse_internal_link("tg:resolve?phone=123456&attach=&startattach=123", user_phone_number("123456")); parse_internal_link("tg:resolve?phone=123456&attach=test", - attachment_menu_bot(user_phone_number("123456"), "test", "")); - parse_internal_link("tg:resolve?phone=123456&attach=test&startattach", - attachment_menu_bot(user_phone_number("123456"), "test", "")); + attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "")); + parse_internal_link("tg:resolve?phone=123456&attach=test&startattach&choose=users", + attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "")); parse_internal_link("tg:resolve?phone=123456&attach=test&startattach=123", - attachment_menu_bot(user_phone_number("123456"), "test", "123")); + attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "123")); parse_internal_link("tg:resolve?phone=01234567890123456789012345678912", user_phone_number("01234567890123456789012345678912")); parse_internal_link("tg:resolve?phone=012345678901234567890123456789123", @@ -283,15 +316,28 @@ TEST(Link, parse_internal_link) { parse_internal_link("t.me/username/-12345", public_chat("username")); parse_internal_link("t.me//12345?single", nullptr); parse_internal_link("https://telegram.dog/telegram/?single", public_chat("telegram")); - parse_internal_link("t.me/username?startattach", attachment_menu_bot(nullptr, "username", "")); - parse_internal_link("t.me/username?startattach=1", attachment_menu_bot(nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach", attachment_menu_bot(nullptr, nullptr, "username", "")); + parse_internal_link("t.me/username?startattach=1", attachment_menu_bot(nullptr, nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach=1&choose=cats+dogs", + attachment_menu_bot(nullptr, nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach=1&choose=users", + attachment_menu_bot(target_chat_chosen(true, false, false, false), nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach=1&choose=bots", + attachment_menu_bot(target_chat_chosen(false, true, false, false), nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach=1&choose=groups", + attachment_menu_bot(target_chat_chosen(false, false, true, false), nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach=1&choose=channels", + attachment_menu_bot(target_chat_chosen(false, false, false, true), nullptr, "username", "1")); + parse_internal_link("t.me/username?startattach=1&choose=bots+groups", + attachment_menu_bot(target_chat_chosen(false, true, true, false), nullptr, "username", "1")); parse_internal_link("t.me/username?attach=", public_chat("username")); - parse_internal_link("t.me/username?attach=&startattach", attachment_menu_bot(nullptr, "username", "")); - parse_internal_link("t.me/username?attach=&startattach=1", attachment_menu_bot(nullptr, "username", "1")); - parse_internal_link("t.me/username?attach=bot", attachment_menu_bot(public_chat("username"), "bot", "")); - parse_internal_link("t.me/username?attach=bot&startattach", attachment_menu_bot(public_chat("username"), "bot", "")); - parse_internal_link("t.me/username?attach=bot&startattach=1", - attachment_menu_bot(public_chat("username"), "bot", "1")); + parse_internal_link("t.me/username?attach=&startattach", attachment_menu_bot(nullptr, nullptr, "username", "")); + parse_internal_link("t.me/username?attach=&startattach=1", attachment_menu_bot(nullptr, nullptr, "username", "1")); + parse_internal_link("t.me/username?attach=bot", attachment_menu_bot(nullptr, public_chat("username"), "bot", "")); + parse_internal_link("t.me/username?attach=bot&startattach", + attachment_menu_bot(nullptr, public_chat("username"), "bot", "")); + parse_internal_link("t.me/username?attach=bot&startattach=1&choose=users", + attachment_menu_bot(nullptr, public_chat("username"), "bot", "1")); parse_internal_link("tg:privatepost?domain=username/12345&single", unknown_deep_link("tg://privatepost?domain=username/12345&single")); @@ -368,6 +414,38 @@ TEST(Link, parse_internal_link) { parse_internal_link("t.me/bg/%20/", background("%20")); parse_internal_link("t.me/bg/", nullptr); + parse_internal_link("t.me/invoice?slug=abcdef", nullptr); + parse_internal_link("t.me/invoice", nullptr); + parse_internal_link("t.me/invoice/", nullptr); + parse_internal_link("t.me/invoice//abcdef", nullptr); + parse_internal_link("t.me/invoice?/abcdef", nullptr); + parse_internal_link("t.me/invoice/?abcdef", nullptr); + parse_internal_link("t.me/invoice/#abcdef", nullptr); + parse_internal_link("t.me/invoice/abacaba", invoice("abacaba")); + parse_internal_link("t.me/invoice/aba%20aba", invoice("aba aba")); + parse_internal_link("t.me/invoice/123456a", invoice("123456a")); + parse_internal_link("t.me/invoice/12345678901", invoice("12345678901")); + parse_internal_link("t.me/invoice/123456", invoice("123456")); + parse_internal_link("t.me/invoice/123456/123123/12/31/a/s//21w/?asdas#test", invoice("123456")); + + parse_internal_link("t.me/$?slug=abcdef", nullptr); + parse_internal_link("t.me/$", nullptr); + parse_internal_link("t.me/$/abcdef", nullptr); + parse_internal_link("t.me/$?/abcdef", nullptr); + parse_internal_link("t.me/$?abcdef", nullptr); + parse_internal_link("t.me/$#abcdef", nullptr); + parse_internal_link("t.me/$abacaba", invoice("abacaba")); + parse_internal_link("t.me/$aba%20aba", invoice("aba aba")); + parse_internal_link("t.me/$123456a", invoice("123456a")); + parse_internal_link("t.me/$12345678901", invoice("12345678901")); + parse_internal_link("t.me/$123456", invoice("123456")); + parse_internal_link("t.me/%24123456", invoice("123456")); + parse_internal_link("t.me/$123456/123123/12/31/a/s//21w/?asdas#test", invoice("123456")); + + parse_internal_link("tg:invoice?slug=abcdef", invoice("abcdef")); + parse_internal_link("tg:invoice?slug=abc%30ef", invoice("abc0ef")); + parse_internal_link("tg://invoice?slug=", unknown_deep_link("tg://invoice?slug=")); + parse_internal_link("tg:share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true)); parse_internal_link("tg:share?url=google.com&text=", message_draft("google.com", false)); parse_internal_link("tg:share?url=&text=google.com", message_draft("google.com", false)); @@ -459,11 +537,11 @@ TEST(Link, parse_internal_link) { parse_internal_link("t.me/+123456?attach=", user_phone_number("123456")); parse_internal_link("t.me/+123456?attach=&startattach", user_phone_number("123456")); parse_internal_link("t.me/+123456?attach=&startattach=1", user_phone_number("123456")); - parse_internal_link("t.me/+123456?attach=bot", attachment_menu_bot(user_phone_number("123456"), "bot", "")); + parse_internal_link("t.me/+123456?attach=bot", attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", "")); parse_internal_link("t.me/+123456?attach=bot&startattach", - attachment_menu_bot(user_phone_number("123456"), "bot", "")); + attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", "")); parse_internal_link("t.me/+123456?attach=bot&startattach=1", - attachment_menu_bot(user_phone_number("123456"), "bot", "1")); + attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", "1")); parse_internal_link("tg:join?invite=abcdef", chat_invite("abcdef")); parse_internal_link("tg:join?invite=abc%20def", chat_invite("abc%20def")); @@ -788,6 +866,10 @@ TEST(Link, parse_internal_link) { parse_internal_link("t.me/telegrampassport?bot_id=12345&public_key=key&scope=asd&payload=nonce", public_chat("telegrampassport")); + parse_internal_link("tg:premium_offer?ref=abcdef", premium_features("abcdef")); + parse_internal_link("tg:premium_offer?ref=abc%30ef", premium_features("abc0ef")); + parse_internal_link("tg://premium_offer?ref=", premium_features("")); + parse_internal_link("tg://settings", settings()); parse_internal_link("tg://setting", unknown_deep_link("tg://setting")); parse_internal_link("tg://settings?asdsa?D?SADasD?asD", settings()); diff --git a/test/mtproto.cpp b/test/mtproto.cpp index 91c82abe5..58eff4356 100644 --- a/test/mtproto.cpp +++ b/test/mtproto.cpp @@ -470,7 +470,7 @@ class Socks5TestActor final : public td::Actor { if (r_socket.is_error()) { return promise.set_error(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error())); } - td::create_actor("socks5", r_socket.move_as_ok(), mtproto_ip_address, "", "", + td::create_actor("Socks5", r_socket.move_as_ok(), mtproto_ip_address, "", "", td::make_unique(std::move(promise)), actor_shared(this)) .release(); } diff --git a/test/online.cpp b/test/online.cpp index 7cdc5af99..458e4ba4e 100644 --- a/test/online.cpp +++ b/test/online.cpp @@ -4,26 +4,31 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include -#include -#include -#include -#include -#include -#include -#include "td/telegram/TdCallback.h" -#include "td/utils/port/signals.h" +#include "td/telegram/ClientActor.h" #include "td/telegram/Log.h" -#include "td/utils/crypto.h" -#include "td/utils/misc.h" -#include "td/utils/Random.h" +#include "td/telegram/td_api_json.h" +#include "td/telegram/TdCallback.h" + #include "td/actor/actor.h" #include "td/actor/ConcurrentScheduler.h" -#include "td/actor/PromiseFuture.h" #include "td/actor/MultiPromise.h" -#include "td/telegram/td_api_json.h" +#include "td/actor/PromiseFuture.h" + +#include "td/utils/crypto.h" +#include "td/utils/FileLog.h" +#include "td/utils/filesystem.h" +#include "td/utils/misc.h" +#include "td/utils/OptionParser.h" +#include "td/utils/port/path.h" +#include "td/utils/port/signals.h" +#include "td/utils/Random.h" + +#include +#include +#include namespace td { + template static void check_td_error(T &result) { LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result); @@ -283,7 +288,7 @@ class GetMe : public Task { int64 user_id; int64 chat_id; }; - GetMe(Promise promise) : promise_(std::move(promise)) { + explicit GetMe(Promise promise) : promise_(std::move(promise)) { } void start_up() override { send_query(td::make_tl_object(), [this](auto res) { with_user_id(res.move_as_ok()->id_); }); @@ -419,7 +424,7 @@ class TestDownloadFile : public Task { unlink(file.local_->path_).ignore(); } - size_t size = file.size_; + auto size = narrow_cast(file.size_); Random::Xorshift128plus rnd(123); size_t begin = 0; @@ -454,13 +459,14 @@ class TestDownloadFile : public Task { } void start_chunk() { - send_query(td::make_tl_object(file_id_, 1, int(ranges_.back().begin), - int(ranges_.back().end - ranges_.back().begin), true), + send_query(td::make_tl_object( + file_id_, 1, static_cast(ranges_.back().begin), + static_cast(ranges_.back().end - ranges_.back().begin), true), [this](auto res) { got_chunk(*res.ok()); }); } }; -std::string gen_readable_file(size_t block_size, size_t block_count) { +static std::string gen_readable_file(size_t block_size, size_t block_count) { std::string content; for (size_t block_id = 0; block_id < block_count; block_id++) { std::string block; @@ -482,7 +488,7 @@ class TestTd : public Actor { string api_hash; }; - TestTd(Options options) : options_(std::move(options)) { + explicit TestTd(Options options) : options_(std::move(options)) { } private: