Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2021-06-30 13:31:50 +02:00
commit c41ec7df43
161 changed files with 6667 additions and 2567 deletions

View File

@ -51,19 +51,21 @@ function(td_set_up_compiler)
elseif (CLANG OR GCC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG} -fno-omit-frame-pointer -fno-exceptions -fno-rtti")
if (APPLE)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-dead_strip,-x,-S")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip,-x,-S")
set(TD_LINKER_FLAGS "-Wl,-dead_strip")
if (NOT CMAKE_BUILD_TYPE MATCHES "Deb")
set(TD_LINKER_FLAGS "${TD_LINKER_FLAGS},-x,-S")
endif()
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,ignore")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,ignore")
set(TD_LINKER_FLAGS "-Wl,-z,ignore")
else()
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL")
set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL")
endif()
endif()
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
if (WIN32 OR CYGWIN)
if (GCC)

View File

@ -1,5 +1,11 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if (POLICY CMP0065)
# do not export symbols from executables
# affects compiler checks in project(), so must be set before it
cmake_policy(SET CMP0065 NEW)
endif()
project(TDLib VERSION 1.7.4 LANGUAGES CXX C)
if (NOT DEFINED CMAKE_MODULE_PATH)
@ -276,6 +282,8 @@ set(TDLIB_SOURCE
td/telegram/AutoDownloadSettings.cpp
td/telegram/BackgroundManager.cpp
td/telegram/BackgroundType.cpp
td/telegram/BotCommand.cpp
td/telegram/BotCommandScope.cpp
td/telegram/CallActor.cpp
td/telegram/CallDiscardReason.cpp
td/telegram/CallManager.cpp
@ -327,6 +335,7 @@ set(TDLIB_SOURCE
td/telegram/GroupCallManager.cpp
td/telegram/GroupCallParticipant.cpp
td/telegram/GroupCallParticipantOrder.cpp
td/telegram/GroupCallVideoPayload.cpp
td/telegram/HashtagHints.cpp
td/telegram/InlineQueriesManager.cpp
td/telegram/InputDialogId.cpp
@ -334,6 +343,7 @@ set(TDLIB_SOURCE
td/telegram/InputMessageText.cpp
td/telegram/JsonValue.cpp
td/telegram/LanguagePackManager.cpp
td/telegram/LinkManager.cpp
td/telegram/Location.cpp
td/telegram/logevent/LogEventHelper.cpp
td/telegram/Logging.cpp
@ -415,13 +425,13 @@ set(TDLIB_SOURCE
td/mtproto/HttpTransport.h
td/mtproto/IStreamTransport.h
td/mtproto/KDF.h
td/mtproto/MtprotoQuery.h
td/mtproto/NoCryptoStorer.h
td/mtproto/PacketInfo.h
td/mtproto/PacketStorer.h
td/mtproto/Ping.h
td/mtproto/PingConnection.h
td/mtproto/ProxySecret.h
td/mtproto/Query.h
td/mtproto/RawConnection.h
td/mtproto/RSA.h
td/mtproto/SessionConnection.h
@ -440,6 +450,8 @@ set(TDLIB_SOURCE
td/telegram/BackgroundId.h
td/telegram/BackgroundManager.h
td/telegram/BackgroundType.h
td/telegram/BotCommand.h
td/telegram/BotCommandScope.h
td/telegram/CallActor.h
td/telegram/CallDiscardReason.h
td/telegram/CallId.h
@ -508,6 +520,7 @@ set(TDLIB_SOURCE
td/telegram/GroupCallManager.h
td/telegram/GroupCallParticipant.h
td/telegram/GroupCallParticipantOrder.h
td/telegram/GroupCallVideoPayload.h
td/telegram/HashtagHints.h
td/telegram/InlineQueriesManager.h
td/telegram/InputDialogId.h
@ -515,6 +528,7 @@ set(TDLIB_SOURCE
td/telegram/InputMessageText.h
td/telegram/JsonValue.h
td/telegram/LanguagePackManager.h
td/telegram/LinkManager.h
td/telegram/Location.h
td/telegram/logevent/LogEvent.h
td/telegram/logevent/LogEventHelper.h
@ -525,6 +539,7 @@ set(TDLIB_SOURCE
td/telegram/MessageCopyOptions.h
td/telegram/MessageEntity.h
td/telegram/MessageId.h
td/telegram/MessageLinkInfo.h
td/telegram/MessageReplyInfo.h
td/telegram/MessagesDb.h
td/telegram/MessageSearchFilter.h
@ -689,7 +704,7 @@ if (MEMPROF)
endif()
add_library(tdapi STATIC ${TL_TD_API_SOURCE})
add_library(tdapi ${TL_TD_API_SOURCE})
target_include_directories(tdapi PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> INTERFACE $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>)
target_link_libraries(tdapi PRIVATE tdutils)
@ -921,4 +936,5 @@ install(FILES "TdConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/TdConfigVersion.cmak
# Add SOVERSION to shared libraries
set_property(TARGET tdclient PROPERTY SOVERSION "${TDLib_VERSION}")
set_property(TARGET tdapi PROPERTY SOVERSION "${TDLib_VERSION}")
set_property(TARGET tdjson PROPERTY SOVERSION "${TDLib_VERSION}")

View File

@ -290,6 +290,7 @@ function split_file($file, $chunks, $undo) {
'HashtagHints' => 'HashtagHints',
'inline_queries_manager[_(-][^.]|InlineQueriesManager' => 'InlineQueriesManager',
'language_pack_manager[_(-][^.]|LanguagePackManager' => 'LanguagePackManager',
'link_manager[_(-][^.]|LinkManager' => 'LinkManager',
'LogeventIdWithGeneration|add_log_event|delete_log_event|get_erase_log_event_promise|parse_time|store_time' => 'logevent/LogEventHelper',
'MessageCopyOptions' => 'MessageCopyOptions',
'messages_manager[_(-][^.]|MessagesManager' => 'MessagesManager',

View File

@ -1,4 +1,6 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
message(FATAL_ERROR "CMake >= 3.0.2 is required")
endif()
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)

View File

@ -229,7 +229,7 @@ function onLanguageChanged(initial) {
if (supported_os.length) {
document.getElementById('osSelectDiv').style.display = 'block';
} else {
if (history.state != '') {
if (history.state !== '' && history.state !== null) {
history.pushState('', '', 'build.html');
}
document.getElementById('osSelectDiv').style.display = 'none';

View File

@ -39,6 +39,6 @@ This may take a while, because TDLib will be built about 10 times.
Resulting library for iOS will work on any architecture (armv7, armv7s, arm64) and even on a simulator.
We use [CMake/iOS.cmake](https://github.com/tdlib/td/blob/master/CMake/iOS.cmake) toolchain, other toolchains may work too.
Built libraries will be store in `tdjson` directory.
Built libraries will be stored in `tdjson` directory.
Documentation for all available classes and methods can be found at https://core.telegram.org/tdlib/docs.

View File

@ -1,4 +1,6 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
message(FATAL_ERROR "CMake >= 3.0.2 is required")
endif()
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
set(CMAKE_INSTALL_LIBDIR "lib")

View File

@ -1,4 +1,6 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
message(FATAL_ERROR "CMake >= 3.0.2 is required")
endif()
if (NOT DEFINED CMAKE_INSTALL_BINDIR)
set(CMAKE_INSTALL_BINDIR "bin")

View File

@ -52,7 +52,7 @@ authenticationCodeTypeSms length:int32 = AuthenticationCodeType;
//@description An authentication code is delivered via a phone call to the specified phone number @length Length of the code
authenticationCodeTypeCall length:int32 = AuthenticationCodeType;
//@description An authentication code is delivered by an immediately cancelled call to the specified phone number. The number from which the call was made is the code @pattern Pattern of the phone number from which the call will be made
//@description An authentication code is delivered by an immediately canceled call to the specified phone number. The number from which the call was made is the code @pattern Pattern of the phone number from which the call will be made
authenticationCodeTypeFlashCall pattern:string = AuthenticationCodeType;
@ -350,8 +350,8 @@ userTypeUnknown = UserType;
//@description Represents a command supported by a bot @command Text of the bot command @param_description Description of the bot command
botCommand command:string description:string = BotCommand;
//@description Provides information about a bot and its supported commands @param_description Long description shown on the user info page @commands A list of commands supported by the bot
botInfo description:string commands:vector<botCommand> = BotInfo;
//@description Contains a list of bot commands @bot_user_id Bot's user identifier @commands List of bot commands
botCommands bot_user_id:int32 commands:vector<botCommand> = BotCommands;
//@description Represents a location to which a chat is connected @location The location @address Location address; 1-64 characters, as defined by the chat owner
@ -418,10 +418,12 @@ user id:int32 first_name:string last_name:string username:string phone_number:st
//@supports_video_calls True, if a video call can be created with the user
//@has_private_calls True, if the user can't be called 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 @share_text For bots, the text that is included with the link when users share the bot
//@bio A short user bio
//@share_text For bots, 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 For bots, the text shown in the chat with the bot if the chat is empty
//@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 If the user is a bot, 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 need_phone_number_privacy_exception:Bool bio:string share_text:string group_in_common_count:int32 bot_info:botInfo = UserFullInfo;
//@commands For bots, list of the bot commands
userFullInfo photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool need_phone_number_privacy_exception:Bool bio:string share_text:string description:string group_in_common_count:int32 commands:vector<botCommand> = UserFullInfo;
//@description Represents a list of users @total_count Approximate total count of users found @user_ids A list of user identifiers
users total_count:int32 user_ids:vector<int32> = Users;
@ -448,13 +450,13 @@ chatPermissions can_send_messages:Bool can_send_media_messages:Bool can_send_pol
//@class ChatMemberStatus @description Provides information about the status of a member in a chat
//@description The user is the owner of a chat and has all the administrator privileges
//@description The user is the owner of the chat and has all the administrator privileges
//@custom_title A custom title of the owner; 0-16 characters without emojis; applicable to supergroups only
//@is_anonymous True, if the creator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only
//@is_member True, if the user is a member of the chat
chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = ChatMemberStatus;
//@description The user is a member of a chat and has some additional privileges. In basic groups, administrators can edit and delete messages sent by others, add new members, ban unprivileged members, and manage voice chats. In supergroups and channels, there are more detailed options for administrator privileges
//@description The user is a member of the chat and has some additional privileges. In basic groups, administrators can edit and delete messages sent by others, add new members, ban unprivileged members, and manage voice chats. In supergroups and channels, there are more detailed options for administrator privileges
//@custom_title A custom title of the administrator; 0-16 characters without emojis; applicable to supergroups only
//@can_be_edited True, if the current user can edit the administrator privileges for the called user
//@can_manage_chat True, if the administrator can get chat event log, get chat statistics, get message statistics in channels, get channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other privilege; applicable to supergroups and channels only
@ -470,7 +472,7 @@ chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = C
//@is_anonymous True, if the administrator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only
chatMemberStatusAdministrator custom_title:string can_be_edited:Bool can_manage_chat:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_promote_members:Bool can_manage_voice_chats:Bool is_anonymous:Bool = ChatMemberStatus;
//@description The user is a member of a chat, without any additional privileges or restrictions
//@description The user is a member of the chat, without any additional privileges or restrictions
chatMemberStatusMember = ChatMemberStatus;
//@description The user is under certain restrictions in the chat. Not supported in basic groups and channels
@ -492,8 +494,7 @@ chatMemberStatusBanned banned_until_date:int32 = ChatMemberStatus;
//@inviter_user_id Identifier of a user that invited/promoted/banned this member in the chat; 0 if unknown
//@joined_chat_date Point in time (Unix timestamp) when the user joined the chat
//@status Status of the member in the chat
//@bot_info If the user is a bot, information about the bot; may be null. Can be null even for a bot if the bot is not the chat member
chatMember member_id:MessageSender inviter_user_id:int32 joined_chat_date:int32 status:ChatMemberStatus bot_info:botInfo = ChatMember;
chatMember member_id:MessageSender inviter_user_id:int32 joined_chat_date:int32 status:ChatMemberStatus = ChatMember;
//@description Contains a list of chat members @total_count Approximate total count of chat members found @members A list of chat members
chatMembers total_count:int32 members:vector<chatMember> = ChatMembers;
@ -604,7 +605,8 @@ basicGroup id:int32 member_count:int32 status:ChatMemberStatus is_active:Bool up
//@creator_user_id User identifier of the creator of the group; 0 if unknown
//@members Group members
//@invite_link Primary invite link for this group; may be null. For chat administrators with can_invite_users right only. Updated only after the basic group is opened
basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int32 members:vector<chatMember> invite_link:chatInviteLink = BasicGroupFullInfo;
//@bot_commands List of commands of bots in the group
basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int32 members:vector<chatMember> invite_link:chatInviteLink bot_commands:vector<botCommands> = BasicGroupFullInfo;
//@description Represents a supergroup or channel with zero or more members (subscribers in the case of channels). From the point of view of the system, a channel is a special kind of a supergroup: only administrators can post and see the list of members, and posts from all administrators use the name and photo of the channel instead of individual names and profile photos. Unlike supergroups, channels can have an unlimited number of subscribers
@ -644,9 +646,10 @@ supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_co
//@sticker_set_id Identifier of the supergroup sticker set; 0 if none
//@location Location to which the supergroup is connected; may be null
//@invite_link Primary invite link for this chat; may be null. For chat administrators with can_invite_users right only
//@bot_commands List of commands of bots in the group
//@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none
//@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none
supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:chatInviteLink upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo;
supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:chatInviteLink bot_commands:vector<botCommands> upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo;
//@class SecretChatState @description Describes the current secret chat state
@ -668,7 +671,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. Video notes are supported if the layer >= 66; 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
secretChat id:int32 user_id:int32 state:SecretChatState is_outbound:Bool key_hash:bytes layer:int32 = SecretChat;
@ -781,7 +784,7 @@ messages total_count:int32 messages:vector<message> = Messages;
foundMessages total_count:int32 messages:vector<message> next_offset:string = FoundMessages;
//@class NotificationSettingsScope @description Describes the types of chats to which notification settings are applied
//@class NotificationSettingsScope @description Describes the types of chats to which notification settings are relevant
//@description Notification settings applied to all private and secret chats when the corresponding chat setting has a default value
notificationSettingsScopePrivateChats = NotificationSettingsScope;
@ -1028,14 +1031,16 @@ replyMarkupRemoveKeyboard is_personal:Bool = ReplyMarkup;
//@description Instructs application to force a reply to this message
//@is_personal True, if a forced reply must automatically be shown to the current user. For outgoing messages, specify true to show the forced reply only for the mentioned users and for the target user of a reply
replyMarkupForceReply is_personal:Bool = ReplyMarkup;
//@input_field_placeholder If non-empty, the placeholder to be shown in the input field when the reply is active; 0-64 characters
replyMarkupForceReply is_personal:Bool input_field_placeholder:string = ReplyMarkup;
//@description Contains a custom keyboard layout to quickly reply to bots
//@rows A list of rows of bot keyboard buttons
//@resize_keyboard True, if the application needs to resize the keyboard vertically
//@one_time True, if the application needs to hide the keyboard after use
//@is_personal True, if the keyboard must automatically be shown to the current user. For outgoing messages, specify true to show the keyboard only for the mentioned users and for the target user of a reply
replyMarkupShowKeyboard rows:vector<vector<keyboardButton>> resize_keyboard:Bool one_time:Bool is_personal:Bool = ReplyMarkup;
//@input_field_placeholder If non-empty, the placeholder to be shown in the input field when the keyboard is active; 0-64 characters
replyMarkupShowKeyboard rows:vector<vector<keyboardButton>> resize_keyboard:Bool one_time:Bool is_personal:Bool input_field_placeholder:string = ReplyMarkup;
//@description Contains an inline keyboard layout
//@rows A list of rows of inline keyboard buttons
@ -2028,7 +2033,7 @@ chatActionStartPlayingGame = ChatAction;
chatActionRecordingVideoNote = ChatAction;
//@description The user is uploading a video note @progress Upload progress, as a percentage
chatActionUploadingVideoNote progress:int32 = ChatAction;
//@description The user has cancelled the previous action
//@description The user has canceled the previous action
chatActionCancel = ChatAction;
@ -2084,7 +2089,7 @@ stickerSets total_count:int32 sets:vector<stickerSetInfo> = StickerSets;
//@description The call wasn't discarded, or the reason is unknown
callDiscardReasonEmpty = CallDiscardReason;
//@description The call was ended before the conversation started. It was cancelled by the caller or missed by the other party
//@description The call was ended before the conversation started. It was canceled by the caller or missed by the other party
callDiscardReasonMissed = CallDiscardReason;
//@description The call was ended before the conversation started. It was declined by the other party
@ -2162,50 +2167,42 @@ groupCallRecentSpeaker participant_id:MessageSender is_speaking:Bool = GroupCall
//@participant_count Number of participants in the group call
//@loaded_all_participants True, if all group call participants are loaded
//@recent_speakers Recently speaking users in the group call
//@is_my_video_enabled True, if the current user's video is enabled
//@is_my_video_paused True, if the current user's video is paused
//@can_start_video True, if video can be enabled by group call participants. This flag is generally useless after the user has already joined the group call
//@mute_new_participants True, if only group call administrators can unmute new participants
//@can_change_mute_new_participants True, if the current user can enable or disable mute_new_participants setting
//@record_duration Duration of the ongoing group call recording, in seconds; 0 if none. An updateGroupCall update is not triggered when value of this field changes, but the same recording goes on
//@duration Call duration; for ended calls only
groupCall id:int32 title:string scheduled_start_date:int32 enabled_start_notification:Bool is_active:Bool is_joined:Bool need_rejoin:Bool can_be_managed:Bool participant_count:int32 loaded_all_participants:Bool recent_speakers:vector<groupCallRecentSpeaker> mute_new_participants:Bool can_change_mute_new_participants:Bool record_duration:int32 duration:int32 = GroupCall;
groupCall id:int32 title:string scheduled_start_date:int32 enabled_start_notification:Bool is_active:Bool is_joined:Bool need_rejoin:Bool can_be_managed:Bool participant_count:int32 loaded_all_participants:Bool recent_speakers:vector<groupCallRecentSpeaker> is_my_video_enabled:Bool is_my_video_paused:Bool can_start_video:Bool mute_new_participants:Bool can_change_mute_new_participants:Bool record_duration:int32 duration:int32 = GroupCall;
//@description Describes a payload fingerprint for interaction with tgcalls @hash Value of the field hash @setup Value of the field setup @fingerprint Value of the field fingerprint
groupCallPayloadFingerprint hash:string setup:string fingerprint:string = GroupCallPayloadFingerprint;
//@description Describes a payload for interaction with tgcalls @ufrag Value of the field ufrag @pwd Value of the field pwd @fingerprints The list of fingerprints
groupCallPayload ufrag:string pwd:string fingerprints:vector<groupCallPayloadFingerprint> = GroupCallPayload;
//@description Describes a join response candidate for interaction with tgcalls @port Value of the field port @protocol Value of the field protocol @network Value of the field network
//@generation Value of the field generation @id Value of the field id @component Value of the field component @foundation Value of the field foundation @priority Value of the field priority
//@ip Value of the field ip @type Value of the field type @tcp_type Value of the field tcp_type @rel_addr Value of the field rel_addr @rel_port Value of the field rel_port
groupCallJoinResponseCandidate port:string protocol:string network:string generation:string id:string component:string foundation:string priority:string ip:string type:string tcp_type:string rel_addr:string rel_port:string = GroupCallJoinResponseCandidate;
//@class GroupCallJoinResponse @description Describes a group call join response
//@description Contains data needed to join the group call with WebRTC @payload Group call payload to pass to tgcalls @candidates Join response candidates to pass to tgcalls
groupCallJoinResponseWebrtc payload:groupCallPayload candidates:vector<groupCallJoinResponseCandidate> = GroupCallJoinResponse;
//@description Describes that group call needs to be joined as a stream
groupCallJoinResponseStream = GroupCallJoinResponse;
//@description Describes a group of video synchronization source identifiers @semantics The semantics of sources, one of "SIM" or "FID" @source_ids The list of synchronization source identifiers
groupCallVideoSourceGroup semantics:string source_ids:vector<int32> = GroupCallVideoSourceGroup;
//@description Contains information about a group call participant's video channel @source_groups List of synchronization source groups of the video @endpoint_id Video channel endpoint identifier
//@is_paused True if the video is paused. This flag needs to be ignored, if new video frames are received
groupCallParticipantVideoInfo source_groups:vector<groupCallVideoSourceGroup> endpoint_id:string is_paused:Bool = GroupCallParticipantVideoInfo;
//@description Represents a group call participant
//@participant_id Identifier of the group call participant
//@source User's synchronization source
//@audio_source_id User's audio channel synchronization source identifier
//@can_enable_video True, if the user can broadcast video or share screen
//@video_info Information about user's video channel; may be null if there is no active video
//@screen_sharing_video_info Information about user's screen sharing video channel; may be null if there is no active screen sharing video
//@bio The participant user's bio or the participant chat's description
//@is_current_user True, if the participant is the current user
//@is_speaking True, if the participant is speaking as set by setGroupCallParticipantIsSpeaking
//@is_hand_raised True, if the participant hand is raised
//@can_be_muted_for_all_users True, if the current user can mute the participant for all other group call participants
//@can_be_unmuted_for_all_users True, if the current user can allow the participant to unmute themself or unmute the participant (if the participant is the current user)
//@can_be_unmuted_for_all_users True, if the current user can allow the participant to unmute themselves or unmute the participant (if the participant is the current user)
//@can_be_muted_for_current_user True, if the current user can mute the participant only for self
//@can_be_unmuted_for_current_user True, if the current user can unmute the participant for self
//@is_muted_for_all_users True, if the participant is muted for all users
//@is_muted_for_current_user True, if the participant is muted for the current user
//@can_unmute_self True, if the participant is muted for all users, but can unmute themself
//@can_unmute_self True, if the participant is muted for all users, but can unmute themselves
//@volume_level Participant's volume level; 1-20000 in hundreds of percents
//@order User's order in the group call participant list. Orders must be compared lexicographically. The bigger is order, the higher is user in the list. If order is empty, the user must be removed from the participant list
groupCallParticipant participant_id:MessageSender source:int32 bio:string is_current_user:Bool is_speaking:Bool is_hand_raised:Bool can_be_muted_for_all_users:Bool can_be_unmuted_for_all_users:Bool can_be_muted_for_current_user:Bool can_be_unmuted_for_current_user:Bool is_muted_for_all_users:Bool is_muted_for_current_user:Bool can_unmute_self:Bool volume_level:int32 order:string = GroupCallParticipant;
groupCallParticipant participant_id:MessageSender audio_source_id:int32 can_enable_video:Bool video_info:groupCallParticipantVideoInfo screen_sharing_video_info:groupCallParticipantVideoInfo bio:string is_current_user:Bool is_speaking:Bool is_hand_raised:Bool can_be_muted_for_all_users:Bool can_be_unmuted_for_all_users:Bool can_be_muted_for_current_user:Bool can_be_unmuted_for_current_user:Bool is_muted_for_all_users:Bool is_muted_for_current_user:Bool can_unmute_self:Bool volume_level:int32 order:string = GroupCallParticipant;
//@class CallProblem @description Describes the exact type of a problem with a call
@ -2627,6 +2624,9 @@ backgroundFillSolid color:int32 = BackgroundFill;
//@rotation_angle Clockwise rotation angle of the gradient, in degrees; 0-359. Should be always divisible by 45
backgroundFillGradient top_color:int32 bottom_color:int32 rotation_angle:int32 = BackgroundFill;
//@description Describes a freeform gradient fill of a background @colors A list of 3 or 4 colors of the freeform gradients in the RGB24 format
backgroundFillFreeformGradient colors:vector<int32> = BackgroundFill;
//@class BackgroundType @description Describes the type of a background
@ -2637,7 +2637,7 @@ backgroundTypeWallpaper is_blurred:Bool is_moving:Bool = BackgroundType;
//@description A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user
//@fill Description of the background fill
//@intensity Intensity of the pattern when it is shown above the filled background; 0-100
//@intensity Intensity of the pattern when it is shown above the filled background; -100-100. If negative, the pattern color and the filled background colors needs to be inverted
//@is_moving True, if the background needs to be slightly moved when device is tilted
backgroundTypePattern fill:BackgroundFill intensity:int32 is_moving:Bool = BackgroundType;
@ -2705,6 +2705,18 @@ checkChatUsernameResultPublicChatsTooMuch = CheckChatUsernameResult;
checkChatUsernameResultPublicGroupsUnavailable = CheckChatUsernameResult;
//@class CheckStickerSetNameResult @description Represents result of checking whether a name can be used for a new sticker set
//@description The name can be set
checkStickerSetNameResultOk = CheckStickerSetNameResult;
//@description The name is invalid
checkStickerSetNameResultNameInvalid = CheckStickerSetNameResult;
//@description The name is occupied
checkStickerSetNameResultNameOccupied = CheckStickerSetNameResult;
//@class MessageFileType @description Contains information about a file with messages exported from another app
//@description The messages was exported from a private chat @name Name of the other party; may be empty if unrecognized
@ -2777,7 +2789,7 @@ pushMessageContentVoiceNote voice_note:voiceNote is_pinned:Bool = PushMessageCon
pushMessageContentBasicGroupChatCreate = PushMessageContent;
//@description New chat members were invited to a group @member_name Name of the added member @is_current_user True, if the current user was added to the group
//@is_returned True, if the user has returned to the group themself
//@is_returned True, if the user has returned to the group themselves
pushMessageContentChatAddMembers member_name:string is_current_user:Bool is_returned:Bool = PushMessageContent;
//@description A chat photo was edited
@ -2787,7 +2799,7 @@ pushMessageContentChatChangePhoto = PushMessageContent;
pushMessageContentChatChangeTitle title:string = PushMessageContent;
//@description A chat member was deleted @member_name Name of the deleted member @is_current_user True, if the current user was deleted from the group
//@is_left True, if the user has left the group themself
//@is_left True, if the user has left the group themselves
pushMessageContentChatDeleteMember member_name:string is_current_user:Bool is_left:Bool = PushMessageContent;
//@description A new member joined the chat by invite link
@ -3003,6 +3015,90 @@ chatReportReasonFake = 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
//@description The link is a link to the active sessions section of the app. Use getActiveSessions to handle the link
internalLinkTypeActiveSessions = 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;
//@description The link is a link to a background. Call searchBackground with the given background name to process the link @background_name Name of the background
internalLinkTypeBackground background_name:string = InternalLinkType;
//@description The link is a link to a chat with a Telegram bot. Call searchPublicChat with the given bot username, check that the user is a bot, show START button in the chat with the bot,
//-and then call sendBotStartMessage with the given start parameter after the button is pressed
//@bot_username Username of the bot @start_parameter The parameter to be passed to sendBotStartMessage
internalLinkTypeBotStart bot_username:string start_parameter:string = InternalLinkType;
//@description The link is a link to a Telegram bot, which is supposed to be added to a group chat. Call searchPublicChat with the given bot username, check that the user is a bot and can be added to groups,
//-ask the current user to select a group to add the bot to, and then call sendBotStartMessage with the given start parameter and the chosen group chat. Bots can be added to a public group only by administrators of the group
//@bot_username Username of the bot @start_parameter The parameter to be passed to sendBotStartMessage
internalLinkTypeBotStartInGroup bot_username:string start_parameter:string = InternalLinkType;
//@description The link is a link to the change phone number section of the app
internalLinkTypeChangePhoneNumber = InternalLinkType;
//@description The link is a chat invite link. Call checkChatInviteLink to process the link
internalLinkTypeChatInvite = InternalLinkType;
//@description The link is a link to the filter settings section of the app
internalLinkTypeFilterSettings = InternalLinkType;
//@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a group to send the game, and then call sendMessage with inputMessageGame
//@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 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;
//@description The link is a link to a Telegram message. Call getMessageLinkInfo to process the link
internalLinkTypeMessage = InternalLinkType;
//@description The link contains a message draft text. A share screen needs to be shown to the user, then the chosen chat should be open and the text should be added to the input field
//@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 should be selected
internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLinkType;
//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm to process the link if the link was received outside of the app, 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:int32 scope:string public_key:string nonce:string callback_url:string = InternalLinkType;
//@description The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberConfirmationCode with the given hash and phone number to process the link
//@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 a proxy. Call addProxy to process the link and add the proxy
//@server Proxy server IP address @port Proxy server port @type Type of the proxy
internalLinkTypeProxy server:string port:int32 type:ProxyType = InternalLinkType;
//@description The link is a link to a chat by its username. Call searchPublicChat with the given chat username to process the link @chat_username Username of the chat
internalLinkTypePublicChat chat_username:string = InternalLinkType;
//@description The link can be used to login the current user on another device, but it must be scanned from QR-code using in-app camera. An alert similar to
//-"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
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
internalLinkTypeStickerSet sticker_set_name:string = InternalLinkType;
//@description The link is a link to a theme. TDLib has no theme support yet @theme_name Name of the theme
internalLinkTypeTheme theme_name:string = InternalLinkType;
//@description The link is a link to the theme settings section of the app
internalLinkTypeThemeSettings = InternalLinkType;
//@description The link is an unknown tg: link. Call getDeepLinkInfo to process the link
internalLinkTypeUnknownDeepLink = InternalLinkType;
//@description The link is a link to a voice chat. Call searchPublicChat with the given chat username, and then joinGoupCall with the given invite hash to process the link
//@chat_username Username of the chat with the voice chat @invite_hash If non-empty, invite hash to be used to join the voice chat without being muted by administrators
internalLinkTypeVoiceChat chat_username:string invite_hash:string = InternalLinkType;
//@description Contains an HTTPS link to a message in a supergroup or channel @link Message link @is_public True, if the link will work for non-members of the chat
messageLink link:string is_public:Bool = MessageLink;
@ -3210,7 +3306,10 @@ tMeUrls urls:vector<tMeUrl> = TMeUrls;
//@description Suggests the user to enable "archive_and_mute_new_chats_from_unknown_users" option
suggestedActionEnableArchiveAndMuteNewChats = SuggestedAction;
//@description Suggests the user to check authorization phone number and change the phone number if it is inaccessible
//@description Suggests the user to check whether 2-step verification password is still remembered
suggestedActionCheckPassword = SuggestedAction;
//@description Suggests the user to check whether authorization phone number is correct and change the phone number if it is inaccessible
suggestedActionCheckPhoneNumber = SuggestedAction;
//@description Suggests the user to see a hint about meaning of one and two ticks on sent message
@ -3230,7 +3329,7 @@ text text:string = Text;
seconds seconds:double = Seconds;
//@description Contains information about a tg:// deep link @text Text to be shown to the user @need_update_application True, if user should be asked to update the application
//@description Contains information about a tg: deep link @text Text to be shown to the user @need_update_application True, if user should be asked to update the application
deepLinkInfo text:formattedText need_update_application:Bool = DeepLinkInfo;
@ -3379,6 +3478,30 @@ vectorPathCommandLine end_point:point = VectorPathCommand;
vectorPathCommandCubicBezierCurve start_control_point:point end_control_point:point end_point:point = VectorPathCommand;
//@class BotCommandScope @description Represents the scope to which bot commands are relevant
//@description A scope covering all users
botCommandScopeDefault = BotCommandScope;
//@description A scope covering all private chats
botCommandScopeAllPrivateChats = BotCommandScope;
//@description A scope covering all group and supergroup chats
botCommandScopeAllGroupChats = BotCommandScope;
//@description A scope covering all group and supergroup chat administrators
botCommandScopeAllChatAdministrators = BotCommandScope;
//@description A scope covering all members of a chat @chat_id Chat identifier
botCommandScopeChat chat_id:int53 = BotCommandScope;
//@description A scope covering all administrators of a chat @chat_id Chat identifier
botCommandScopeChatAdministrators chat_id:int53 = BotCommandScope;
//@description A scope covering a member of a chat @chat_id Chat identifier @user_id User identifier
botCommandScopeChatMember chat_id:int53 user_id:int32 = BotCommandScope;
//@class Update @description Contains notifications about data changes
//@description The user authorization state has changed @authorization_state New authorization state
@ -3912,7 +4035,7 @@ removeRecentlyFoundChat chat_id:int53 = Ok;
//@description Clears the list of recently found chats
clearRecentlyFoundChats = Ok;
//@description Checks whether a username can be set for a chat @chat_id Chat identifier; should be identifier of a supergroup chat, or a channel chat, or a private chat with self, or zero if chat is being created @username Username to be checked
//@description Checks whether a username can be set for a chat @chat_id Chat identifier; should be identifier of a supergroup chat, or a channel chat, or a private chat with self, or zero if the chat is being created @username Username to be checked
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
@ -4041,7 +4164,7 @@ getMessageLink chat_id:int53 message_id:int53 for_album:Bool for_comment:Bool =
//@for_album Pass true to return an HTML code for embedding of the whole media album
getMessageEmbeddingCode chat_id:int53 message_id:int53 for_album:Bool = Text;
//@description Returns information about a public or private message link @url The message link in the format "https://t.me/c/...", or "tg://privatepost?...", or "https://t.me/username/...", or "tg://resolve?..."
//@description Returns information about a public or private message link @url The message link
getMessageLinkInfo url:string = MessageLinkInfo;
@ -4280,7 +4403,10 @@ viewMessages chat_id:int53 message_thread_id:int53 message_ids:vector<int53> for
//@description Informs TDLib that the message content has been opened (e.g., the user has opened a photo, video, document, location or venue, or has listened to an audio file or voice note message). An updateMessageContentOpened update will be generated if something has changed @chat_id Chat identifier of the message @message_id Identifier of the message with the opened content
openMessageContent chat_id:int53 message_id:int53 = Ok;
//@description Returns information about an action to be done when the current user clicks an HTTP link. This method can be used to automatically authorize the current user on a website. Don't use this method for links from secret chats if link preview is disabled in secret chats @link The HTTP link
//@description Returns information about the type of an internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link
getInternalLinkType link:string = InternalLinkType;
//@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if link preview is disabled in secret chats @link The link
getExternalLinkInfo link:string = LoginUrlInfo;
//@description Returns an HTTP URL which can be used to automatically authorize the current user on a website after clicking an HTTP link. Use the method getExternalLinkInfo to find whether a prior user confirmation is needed
@ -4482,9 +4608,9 @@ setPinnedChats chat_list:ChatList chat_ids:vector<int53> = Ok;
//@file_id Identifier of the file to download
//@priority Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile was called will be downloaded first
//@offset The starting position from which the file should be downloaded
//@limit Number of bytes which should be downloaded starting from the "offset" position before the download will be automatically cancelled; use 0 to download without a limit
//@limit Number of bytes which should be downloaded starting from the "offset" position before the download will be automatically canceled; use 0 to download without a limit
//@synchronous If false, this request returns file state just after the download has been started. If true, this request returns file state only after
//-the download has succeeded, has failed, has been cancelled or a new downloadFile request with different offset/limit parameters was sent
//-the download has succeeded, has failed, has been canceled or a new downloadFile request with different offset/limit parameters was sent
downloadFile file_id:int32 priority:int32 offset:int32 limit:int32 synchronous:Bool = File;
//@description Returns file downloaded prefix size from a given offset @file_id Identifier of the file @offset Offset from which downloaded prefix size should be calculated
@ -4590,11 +4716,10 @@ deleteRevokedChatInviteLink chat_id:int53 invite_link:string = Ok;
//@creator_user_id User identifier of a chat administrator, which links will be deleted. Must be an identifier of the current user for non-owner
deleteAllRevokedChatInviteLinks chat_id:int53 creator_user_id:int32 = 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; must have URL "t.me", "telegram.me", or "telegram.dog" and query beginning with "/joinchat/" or "/+"
//@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 import; must have URL "t.me", "telegram.me", or "telegram.dog" and query beginning with "/joinchat/" or "/+"
//@description Uses an invite link to add the current user to the chat if possible @invite_link Invite link to use
joinChatByInviteLink invite_link:string = Chat;
@ -4639,14 +4764,24 @@ startScheduledGroupCall group_call_id:int32 = Ok;
//@group_call_id Group call identifier @enabled_start_notification New value of the enabled_start_notification setting
toggleGroupCallEnabledStartNotification group_call_id:int32 enabled_start_notification:Bool = Ok;
//@description Joins an active group call
//@description Joins an active group call. Returns join response payload for tgcalls
//@group_call_id Group call identifier
//@participant_id Identifier of a group call participant, which will be used to join the call; voice chats only
//@payload Group join payload; received from tgcalls
//@source Caller synchronization source identifier; received from tgcalls
//@audio_source_id Caller audio channel synchronization source identifier; received from tgcalls
//@payload Group call join payload; received from tgcalls
//@is_muted True, if the user's microphone is muted
//@is_my_video_enabled True, if the user's video is enabled
//@invite_hash If non-empty, invite hash to be used to join the group call without being muted by administrators
joinGroupCall group_call_id:int32 participant_id:MessageSender payload:groupCallPayload source:int32 is_muted:Bool invite_hash:string = GroupCallJoinResponse;
joinGroupCall group_call_id:int32 participant_id:MessageSender audio_source_id:int32 payload:string is_muted:Bool is_my_video_enabled:Bool invite_hash:string = Text;
//@description Starts screen sharing in a joined group call. Returns join response payload for tgcalls @group_call_id Group call identifier @payload Group call join payload; received from tgcalls
startGroupCallScreenSharing group_call_id:int32 payload:string = Text;
//@description Pauses or unpauses screen sharing in a joined group call @group_call_id Group call identifier @is_paused True if screen sharing is paused
toggleGroupCallScreenSharingIsPaused group_call_id:int32 is_paused:Bool = Ok;
//@description Ends screen sharing in a joined group call @group_call_id Group call identifier
endGroupCallScreenSharing group_call_id:int32 = Ok;
//@description Sets group call title. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier @title New group call title; 1-64 characters
setGroupCallTitle group_call_id:int32 title:string = Ok;
@ -4664,7 +4799,7 @@ inviteGroupCallParticipants group_call_id:int32 user_ids:vector<int32> = Ok;
//@description Returns invite link to a voice chat in a public chat
//@group_call_id Group call identifier
//@can_self_unmute Pass true if the invite_link should contain an invite hash, passing which to joinGroupCall would allow the invited user to unmute themself. Requires groupCall.can_be_managed group call flag
//@can_self_unmute Pass true if the invite_link should contain an invite hash, passing which to joinGroupCall would allow the invited user to unmute themselves. Requires groupCall.can_be_managed group call flag
getGroupCallInviteLink group_call_id:int32 can_self_unmute:Bool = HttpUrl;
//@description Starts recording of an active group call. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier @title Group call recording title; 0-64 characters
@ -4673,11 +4808,17 @@ startGroupCallRecording group_call_id:int32 title:string = Ok;
//@description Ends recording of an active group call. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier
endGroupCallRecording group_call_id:int32 = Ok;
//@description Informs TDLib that a participant of an active group call speaking state has changed @group_call_id Group call identifier
//@source Group call participant's synchronization source identifier, or 0 for the current user @is_speaking True, if the user is speaking
setGroupCallParticipantIsSpeaking group_call_id:int32 source:int32 is_speaking:Bool = Ok;
//@description Toggles whether current user's video is paused @group_call_id Group call identifier @is_my_video_paused Pass true if the current user's video is paused
toggleGroupCallIsMyVideoPaused group_call_id:int32 is_my_video_paused:Bool = Ok;
//@description Toggles whether a participant of an active group call is muted, unmuted, or allowed to unmute themself
//@description Toggles whether current user's video is enabled @group_call_id Group call identifier @is_my_video_enabled Pass true if the current user's video is enabled
toggleGroupCallIsMyVideoEnabled group_call_id:int32 is_my_video_enabled:Bool = Ok;
//@description Informs TDLib that speaking state of a participant of an active group has changed @group_call_id Group call identifier
//@audio_source Group call participant's synchronization audio source identifier, or 0 for the current user @is_speaking True, if the user is speaking
setGroupCallParticipantIsSpeaking group_call_id:int32 audio_source:int32 is_speaking:Bool = Ok;
//@description Toggles whether a participant of an active group call is muted, unmuted, or allowed to unmute themselves
//@group_call_id Group call identifier @participant_id Participant identifier @is_muted Pass true if the user must be muted and false otherwise
toggleGroupCallParticipantIsMuted group_call_id:int32 participant_id:MessageSender is_muted:Bool = Ok;
@ -4888,8 +5029,19 @@ resendChangePhoneNumberCode = AuthenticationCodeInfo;
//@description Checks the authentication code sent to confirm a new phone number of the user @code Verification code received by SMS, phone call or flash call
checkChangePhoneNumberCode code:string = Ok;
//@description Sets the list of commands supported by the bot; for bots only @commands List of the bot's commands
setCommands commands:vector<botCommand> = Ok;
//@description Sets the list of commands supported by the bot for the given user scope and language; for bots only @scope The scope to which the commands are relevant
//@language_code A two-letter ISO 639-1 country code. If empty, the commands will be applied to all users from the given scope, for which language there are no dedicated commands
//@commands List of the bot's commands
setCommands scope:BotCommandScope language_code:string commands:vector<botCommand> = Ok;
//@description Deletes commands supported by the bot for the given user scope and language; for bots only @scope The scope to which the commands are relevant
//@language_code A two-letter ISO 639-1 country code or an empty string
deleteCommands scope:BotCommandScope language_code:string = Ok;
//@description Returns the list of commands supported by the bot for the given user scope and language; for bots only @scope The scope to which the commands are relevant
//@language_code A two-letter ISO 639-1 country code or an empty string
getCommands scope:BotCommandScope language_code:string = BotCommands;
//@description Returns all active sessions of the current user
@ -4983,8 +5135,8 @@ getBackgroundUrl name:string type:BackgroundType = HttpUrl;
searchBackground name:string = Background;
//@description Changes the background selected by the user; adds background to the list of installed backgrounds
//@background The input background to use, null for filled backgrounds
//@type Background type; null for default background. The method will return error 404 if type is null
//@background The input background to use. Pass null to create a new filled backgrounds. Pass null to remove the current background
//@type Background type. Pass null to use default type of the remote background. Pass null to remove the current background
//@for_dark_theme True, if the background is chosen for dark theme
setBackground background:InputBackground type:BackgroundType for_dark_theme:Bool = Background;
@ -5075,7 +5227,7 @@ reportChat chat_id:int53 message_ids:vector<int53> reason:ChatReportReason text:
//@chat_id Chat identifier @file_id Identifier of the photo to report. Only full photos from chatPhoto can be reported @reason The reason for reporting the chat photo @text Additional report details; 0-1024 characters
reportChatPhoto chat_id:int53 file_id:int32 reason:ChatReportReason text:string = Ok;
//@description Returns an HTTP URL with the chat statistics. Currently this method of getting the statistics are disabled and can be deleted in the future @chat_id Chat identifier @parameters Parameters from "tg://statsrefresh?params=******" link @is_dark Pass true if a URL with the dark theme must be returned
//@description Returns an HTTP URL with the chat statistics. Currently this method of getting the statistics are disabled and can be deleted in the future @chat_id Chat identifier @parameters Parameters for the request @is_dark Pass true if a URL with the dark theme must be returned
getChatStatisticsUrl chat_id:int53 parameters:string is_dark:Bool = HttpUrl;
//@description Returns detailed statistics about a chat. Currently this method can be used only for supergroups and channels. Can be used only if SupergroupFullInfo.can_get_statistics == true @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the application
@ -5134,7 +5286,7 @@ resetNetworkStatistics = Ok;
//@description Returns auto-download settings presets for the current user
getAutoDownloadSettingsPresets = AutoDownloadSettingsPresets;
//@description Sets auto-download settings @settings New user auto-download settings @type Type of the network for which the new settings are applied
//@description Sets auto-download settings @settings New user auto-download settings @type Type of the network for which the new settings are relevant
setAutoDownloadSettings settings:autoDownloadSettings type:NetworkType = Ok;
@ -5183,7 +5335,7 @@ resendEmailAddressVerificationCode = EmailAddressAuthenticationCodeInfo;
checkEmailAddressVerificationCode code:string = Ok;
//@description Returns a Telegram Passport authorization form for sharing data with a service @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 Authorization form nonce provided by the service
//@description Returns a Telegram Passport authorization form for sharing data with a service @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
getPassportAuthorizationForm bot_user_id:int32 scope:string public_key:string nonce:string = PassportAuthorizationForm;
//@description Returns already available Telegram Passport elements suitable for completing a Telegram Passport authorization form. Result can be received only once for each authorization form @autorization_form_id Authorization form identifier @password Password of the current user
@ -5194,8 +5346,7 @@ getPassportAuthorizationFormAvailableElements autorization_form_id:int32 passwor
sendPassportAuthorizationForm autorization_form_id:int32 types:vector<PassportElementType> = Ok;
//@description Sends phone number confirmation code. Should be called when user presses "https://t.me/confirmphone?phone=*******&hash=**********" or "tg://confirmphone?phone=*******&hash=**********" link @hash Value of the "hash" parameter from the link
//@phone_number Value of the "phone" parameter from the link @settings Settings for the authentication of the user's phone number
//@description Sends phone number confirmation code to handle links of the type internalLinkTypePhoneNumberConfirmation @hash Hash value from the link @phone_number Phone number value from the link @settings Settings for the authentication of the user's phone number
sendPhoneNumberConfirmationCode hash:string phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo;
//@description Resends phone number confirmation code
@ -5209,17 +5360,23 @@ checkPhoneNumberConfirmationCode code:string = Ok;
setBotUpdatesStatus pending_update_count:int32 error_message:string = Ok;
//@description Uploads a PNG image with a sticker; for bots only; returns the uploaded file
//@user_id Sticker file owner @png_sticker PNG image with the sticker; must be up to 512 KB in size and fit in 512x512 square
uploadStickerFile user_id:int32 png_sticker:InputFile = File;
//@description Uploads a PNG image with a sticker; returns the uploaded file @user_id Sticker file owner; ignored for regular users @sticker Sticker file to upload
uploadStickerFile user_id:int32 sticker:InputSticker = File;
//@description Creates a new sticker set; for bots only. Returns the newly created sticker set
//@user_id Sticker set owner
//@description Returns a suggested name for a new sticker set with a given title @title Sticker set title; 1-64 characters
getSuggestedStickerSetName title:string = Text;
//@description Checks whether a name can be used for a new sticker set @name Name to be checked
checkStickerSetName name:string = CheckStickerSetNameResult;
//@description Creates a new sticker set. Returns the newly created sticker set
//@user_id Sticker set owner; ignored for regular users
//@title Sticker set title; 1-64 characters
//@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_<bot username>"* (*<bot_username>* is case insensitive); 1-64 characters
//@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_<bot username>"* (*<bot_username>* is case insensitive) for bots; 1-64 characters
//@is_masks True, if stickers are masks. Animated stickers can't be masks
//@stickers List of stickers to be added to the set; must be non-empty. All stickers must be of the same type
createNewStickerSet user_id:int32 title:string name:string is_masks:Bool stickers:vector<InputSticker> = StickerSet;
//@stickers List of stickers to be added to the set; must be non-empty. All stickers must be of the same type. For animated stickers, uploadStickerFile must be used before the sticker is shown
//@source Source of the sticker set; may be empty if unknown
createNewStickerSet user_id:int32 title:string name:string is_masks:Bool stickers:vector<InputSticker> source:string = StickerSet;
//@description Adds a new sticker to a set; for bots only. Returns the sticker set
//@user_id Sticker set owner @name Sticker set name @sticker Sticker to add to the set
@ -5266,8 +5423,8 @@ getCountryCode = Text;
//@description Returns information about a phone number by its prefix. Can be called before authorization @phone_number_prefix The phone number prefix
getPhoneNumberInfo phone_number_prefix:string = PhoneNumberInfo;
//@description Returns the default text for invitation messages to be used as a placeholder when the current user invites friends to Telegram
getInviteText = Text;
//@description Returns the link for downloading official Telegram application to be used when the current user invites friends to Telegram
getApplicationDownloadLink = HttpUrl;
//@description Returns information about a tg:// deep link. Use "tg://need_update_for_some_feature" or "tg:some_unsupported_feature" for testing. Returns a 404 error for unknown links. Can be called before authorization @link The link
getDeepLinkInfo link:string = DeepLinkInfo;

View File

@ -19,6 +19,9 @@ ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
inputPeerPhotoFileLocationLegacy#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation;
inputStickerSetThumbLegacy#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation;
---functions---
test.useError = Error;
@ -77,8 +80,8 @@ inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
inputTakeoutFileLocation#29be5899 = InputFileLocation;
inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation;
inputPeerPhotoFileLocation#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation;
inputStickerSetThumb#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation;
inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer photo_id:long = InputFileLocation;
inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation;
inputGroupCallStream#bba51639 call:InputGroupCall time_ms:long scale:int = InputFileLocation;
peerUser#9db1bc6d user_id:int = Peer;
@ -100,7 +103,7 @@ userEmpty#200250ba id:int = User;
user#938458c1 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 id:int 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<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#cc656077 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
userStatusEmpty#9d05049 = UserStatus;
userStatusOnline#edb93949 expires:int = UserStatus;
@ -126,7 +129,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?
chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#4790ee05 flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
message#bce383d2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;
@ -182,10 +185,10 @@ photoEmpty#2331b22d id:long = Photo;
photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> video_sizes:flags.1?Vector<VideoSize> dc_id:int = Photo;
photoSizeEmpty#e17e23c type:string = PhotoSize;
photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
photoSize#75c78e60 type:string w:int h:int size:int = PhotoSize;
photoCachedSize#21e1ad6 type:string w:int h:int bytes:bytes = PhotoSize;
photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize;
photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector<int> = PhotoSize;
photoSizeProgressive#fa3efb95 type:string w:int h:int sizes:Vector<int> = PhotoSize;
photoPathSize#d8214d41 type:string bytes:bytes = PhotoSize;
geoPointEmpty#1117dd5f = GeoPoint;
@ -210,7 +213,7 @@ peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bo
peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings;
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
inputReportReasonSpam#58dbcab8 = ReportReason;
inputReportReasonViolence#1e22c78d = ReportReason;
@ -343,7 +346,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
updateTheme#8216fba3 theme:Theme = Update;
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
updateLoginToken#564fe691 = Update;
updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
updateMessagePollVote#37f69f0b poll_id:long user_id:int options:Vector<bytes> qts:int = Update;
updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
updateDialogFilters#3504914f = Update;
@ -362,6 +365,7 @@ updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update;
updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update;
updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update;
updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update;
updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -392,7 +396,7 @@ config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:fla
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
help.appUpdate#1da7158f flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string = help.AppUpdate;
help.appUpdate#ccbbce30 flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string sticker:flags.3?Document = help.AppUpdate;
help.noAppUpdate#c45a6536 = help.AppUpdate;
help.inviteText#18cb9f78 message:string = help.InviteText;
@ -565,8 +569,8 @@ keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = Keybo
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup;
replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> placeholder:flags.3?string = ReplyMarkup;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
@ -1056,14 +1060,14 @@ chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags
inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper;
inputWallPaperSlug#72091c80 slug:string = InputWallPaper;
inputWallPaperNoFile#8427bbac = InputWallPaper;
inputWallPaperNoFile#967a462e id:long = InputWallPaper;
account.wallPapersNotModified#1c199183 = account.WallPapers;
account.wallPapers#702b65a9 hash:int wallpapers:Vector<WallPaper> = account.WallPapers;
codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings;
wallPaperSettings#5086cf8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings;
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;
@ -1078,8 +1082,6 @@ emojiURL#a575739d url:string = EmojiURL;
emojiLanguage#b3fb5361 lang_code:string = EmojiLanguage;
fileLocationToBeDeprecated#bc7fc6cd volume_id:long local_id:int = FileLocation;
folder#ff544e65 flags:# autofill_new_broadcasts:flags.0?true autofill_public_groups:flags.1?true autofill_new_correspondents:flags.2?true id:int title:string photo:flags.3?ChatPhoto = Folder;
inputFolderPeer#fbd2c296 peer:InputPeer folder_id:int = InputFolderPeer;
@ -1159,7 +1161,7 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;
videoSize#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster;
@ -1193,11 +1195,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall;
groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall;
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant;
groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant;
phone.groupCall#9e727aad call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string chats:Vector<Chat> users:Vector<User> = phone.GroupCall;
@ -1234,6 +1236,20 @@ phone.joinAsPeers#afe5623f peers:Vector<Peer> chats:Vector<Chat> users:Vector<Us
phone.exportedGroupCallInvite#204bd158 link:string = phone.ExportedGroupCallInvite;
groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector<int> = GroupCallParticipantVideoSourceGroup;
groupCallParticipantVideo#78e41663 flags:# paused:flags.0?true endpoint:string source_groups:Vector<GroupCallParticipantVideoSourceGroup> = GroupCallParticipantVideo;
stickers.suggestedShortName#85fea03f short_name:string = stickers.SuggestedShortName;
botCommandScopeDefault#2f6cb2ab = BotCommandScope;
botCommandScopeUsers#3c4f04d8 = BotCommandScope;
botCommandScopeChats#6fe1a881 = BotCommandScope;
botCommandScopeChatAdmins#b9aa606a = BotCommandScope;
botCommandScopePeer#db9d897d peer:InputPeer = BotCommandScope;
botCommandScopePeerAdmins#3fd863d1 peer:InputPeer = BotCommandScope;
botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandScope;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1580,7 +1596,9 @@ channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector<BotCommand> = Bool;
bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool;
bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector<BotCommand>;
payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
@ -1590,11 +1608,13 @@ 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;
stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
stickers.checkShortName#284b3639 short_name:string = Bool;
stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName;
phone.getCallConfig#55451fa9 = DataJSON;
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
@ -1606,22 +1626,24 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates;
phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
phone.discardGroupCall#7a777135 call:InputGroupCall = Updates;
phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates;
phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall;
phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector<InputPeer> sources:Vector<int> offset:string limit:int = phone.GroupParticipants;
phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool;
phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector<int> = Vector<int>;
phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates;
phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGroupCall participant:InputPeer volume:flags.1?int raise_hand:flags.2?Bool = Updates;
phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates;
phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates;
phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers;
phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite;
phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates;
phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates;
phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool;
phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates;
phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates;
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;

View File

@ -7,8 +7,8 @@
#pragma once
#include "td/mtproto/AuthData.h"
#include "td/mtproto/MtprotoQuery.h"
#include "td/mtproto/PacketStorer.h"
#include "td/mtproto/Query.h"
#include "td/mtproto/utils.h"
#include "td/mtproto/mtproto_api.h"

View File

@ -27,7 +27,7 @@ HandshakeActor::HandshakeActor(unique_ptr<AuthKeyHandshake> handshake, unique_pt
}
void HandshakeActor::close() {
finish(Status::Error("Cancelled"));
finish(Status::Error("Canceled"));
stop();
}
@ -56,7 +56,7 @@ void HandshakeActor::return_connection(Status status) {
return;
}
if (status.is_error() && !raw_connection->extra().debug_str.empty()) {
status = Status::Error(status.code(), PSLICE() << status.message() << " : " << raw_connection->extra().debug_str);
status = status.move_as_error_suffix(PSLICE() << " : " << raw_connection->extra().debug_str);
}
Scheduler::unsubscribe(raw_connection->get_poll_info().get_pollable_fd_ref());
if (raw_connection_promise_) {

View File

@ -40,7 +40,7 @@ class HandshakeActor : public Actor {
finish(Status::OK());
}
void hangup() override {
finish(Status::Error(1, "Cancelled"));
finish(Status::Error(1, "Canceled"));
stop();
}
void timeout_expired() override {

View File

@ -45,7 +45,7 @@ ActorOwn<> create_ping_actor(string debug, unique_ptr<RawConnection> raw_connect
}
void hangup() override {
finish(Status::Error("Cancelled"));
finish(Status::Error("Canceled"));
stop();
}

View File

@ -6,8 +6,8 @@
//
#pragma once
#include "td/mtproto/MtprotoQuery.h"
#include "td/mtproto/PacketInfo.h"
#include "td/mtproto/Query.h"
#include "td/mtproto/RawConnection.h"
#include "td/utils/buffer.h"

View File

@ -227,11 +227,7 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a
auto *header = reinterpret_cast<HeaderT *>(message.begin());
*header_ptr = header;
auto to_decrypt = MutableSlice(header->encrypt_begin(), message.uend());
to_decrypt.truncate(to_decrypt.size() & ~15);
if (to_decrypt.size() % 16 != 0) {
return Status::Error(PSLICE() << "Invalid mtproto message: size of encrypted part is not multiple of 16 [size = "
<< to_decrypt.size() << "]");
}
to_decrypt.remove_suffix(to_decrypt.size() & 15);
if (header->auth_key_id != auth_key.id()) {
return Status::Error(PSLICE() << "Invalid mtproto message: auth_key_id mismatch [found = "

View File

@ -18,12 +18,10 @@
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/misc.h"
#include "td/telegram/SecretChatActor.h"
#include "td/telegram/Td.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/PromiseFuture.h"
@ -355,7 +353,9 @@ void AnimationsManager::create_animation(FileId file_id, string minithumbnail, P
a->mime_type = std::move(mime_type);
a->duration = max(duration, 0);
a->dimensions = dimensions;
a->minithumbnail = std::move(minithumbnail);
if (!td_->auth_manager_->is_bot()) {
a->minithumbnail = std::move(minithumbnail);
}
a->thumbnail = std::move(thumbnail);
a->animated_thumbnail = std::move(animated_thumbnail);
a->has_stickers = has_stickers;
@ -421,8 +421,7 @@ tl_object_ptr<telegram_api::InputMedia> AnimationsManager::get_input_media(
SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
const string &caption, BufferSlice thumbnail,
int32 layer) const {
const string &caption, BufferSlice thumbnail) const {
auto *animation = get_animation(animation_file_id);
if (animation == nullptr) {
return SecretInputMedia{};
@ -446,13 +445,8 @@ SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file
attributes.push_back(make_tl_object<secret_api::documentAttributeFilename>(animation->file_name));
}
if (animation->duration != 0 && animation->mime_type == "video/mp4") {
if (layer >= SecretChatActor::VIDEO_NOTES_LAYER) {
attributes.push_back(make_tl_object<secret_api::documentAttributeVideo66>(
0, false, animation->duration, animation->dimensions.width, animation->dimensions.height));
} else {
attributes.push_back(make_tl_object<secret_api::documentAttributeVideo>(
animation->duration, animation->dimensions.width, animation->dimensions.height));
}
attributes.push_back(make_tl_object<secret_api::documentAttributeVideo66>(
0, false, animation->duration, animation->dimensions.width, animation->dimensions.height));
}
if (animation->dimensions.width != 0 && animation->dimensions.height != 0) {
attributes.push_back(make_tl_object<secret_api::documentAttributeImageSize>(animation->dimensions.width,

View File

@ -49,7 +49,7 @@ class AnimationsManager : public Actor {
SecretInputMedia get_secret_input_media(FileId animation_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
const string &caption, BufferSlice thumbnail, int32 layer) const;
const string &caption, BufferSlice thumbnail) const;
FileId get_animation_thumbnail_file_id(FileId file_id) const;

View File

@ -6,10 +6,10 @@
//
#include "td/telegram/AudiosManager.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/Td.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
@ -190,7 +190,9 @@ void AudiosManager::create_audio(FileId file_id, string minithumbnail, PhotoSize
a->duration = max(duration, 0);
a->title = std::move(title);
a->performer = std::move(performer);
a->minithumbnail = std::move(minithumbnail);
if (!td_->auth_manager_->is_bot()) {
a->minithumbnail = std::move(minithumbnail);
}
a->thumbnail = std::move(thumbnail);
on_get_audio(std::move(a), replace);
}

View File

@ -59,6 +59,7 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par
td, PromiseCreator::lambda([this](Result<Unit> result) { update_state(State::Ok); }));
}
} else if (auth_str == "logout") {
LOG(WARNING) << "Continue to log out";
update_state(State::LoggingOut);
} else if (auth_str == "destroy") {
update_state(State::DestroyingKeys);
@ -345,10 +346,11 @@ void AuthManager::log_out(uint64 query_id) {
if (state_ != State::Ok) {
// TODO: could skip full logout if still no authorization
// TODO: send auth.cancelCode if state_ == State::WaitCode
LOG(WARNING) << "Destroying auth keys by user request";
destroy_auth_keys();
on_query_ok();
} else {
LOG(INFO) << "Logging out";
LOG(WARNING) << "Logging out by user request";
G()->td_db()->get_binlog_pmc()->set("auth", "logout");
update_state(State::LoggingOut);
send_log_out_query();
@ -628,12 +630,13 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) {
on_query_ok();
}
}
void AuthManager::on_authorization_lost() {
void AuthManager::on_authorization_lost(const string &source) {
LOG(WARNING) << "Lost authorization because of " << source;
destroy_auth_keys();
}
void AuthManager::destroy_auth_keys() {
if (state_ == State::Closing) {
if (state_ == State::Closing || state_ == State::DestroyingKeys) {
return;
}
update_state(State::DestroyingKeys);

View File

@ -48,7 +48,7 @@ class AuthManager : public NetActor {
void on_update_login_token();
void on_authorization_lost();
void on_authorization_lost(const string &source);
void on_closing(bool destroy_flag);
// can return nullptr if state isn't initialized yet

View File

@ -42,6 +42,10 @@ class BackgroundId {
return id != 0;
}
bool is_local() const {
return 0 < id && id <= 0x7FFFFFFF;
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(id, storer);

View File

@ -28,6 +28,7 @@
#include "td/db/SqliteKeyValueAsync.h"
#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
@ -107,10 +108,9 @@ class InstallBackgroundQuery : public Td::ResultHandler {
explicit InstallBackgroundQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(BackgroundId background_id, int64 access_hash, const BackgroundType &type) {
send_query(G()->net_query_creator().create(telegram_api::account_installWallPaper(
telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), access_hash),
get_input_wallpaper_settings(type))));
void send(telegram_api::object_ptr<telegram_api::InputWallPaper> input_wallpaper, const BackgroundType &type) {
send_query(G()->net_query_creator().create(
telegram_api::account_installWallPaper(std::move(input_wallpaper), type.get_input_wallpaper_settings())));
}
void on_result(uint64 id, BufferSlice packet) override {
@ -146,9 +146,8 @@ class UploadBackgroundQuery : public Td::ResultHandler {
file_id_ = file_id;
type_ = type;
for_dark_theme_ = for_dark_theme;
string mime_type = type.type == BackgroundType::Type::Pattern ? "image/png" : "image/jpeg";
send_query(G()->net_query_creator().create(
telegram_api::account_uploadWallPaper(std::move(input_file), mime_type, get_input_wallpaper_settings(type))));
send_query(G()->net_query_creator().create(telegram_api::account_uploadWallPaper(
std::move(input_file), type_.get_mime_type(), type.get_input_wallpaper_settings())));
}
void on_result(uint64 id, BufferSlice packet) override {
@ -177,17 +176,16 @@ class UploadBackgroundQuery : public Td::ResultHandler {
}
};
class SaveBackgroundQuery : public Td::ResultHandler {
class UnsaveBackgroundQuery : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit SaveBackgroundQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
explicit UnsaveBackgroundQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(BackgroundId background_id, int64 access_hash, const BackgroundType &type, bool unsave) {
void send(telegram_api::object_ptr<telegram_api::InputWallPaper> input_wallpaper) {
send_query(G()->net_query_creator().create(telegram_api::account_saveWallPaper(
telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), access_hash), unsave,
get_input_wallpaper_settings(type))));
std::move(input_wallpaper), true, telegram_api::make_object<telegram_api::wallPaperSettings>())));
}
void on_result(uint64 id, BufferSlice packet) override {
@ -272,6 +270,7 @@ void BackgroundManager::Background::store(StorerT &storer) const {
STORE_FLAG(is_default);
STORE_FLAG(is_dark);
STORE_FLAG(has_file_id);
STORE_FLAG(has_new_local_id);
END_STORE_FLAGS();
td::store(id, storer);
td::store(access_hash, storer);
@ -290,6 +289,7 @@ void BackgroundManager::Background::parse(ParserT &parser) {
PARSE_FLAG(is_default);
PARSE_FLAG(is_dark);
PARSE_FLAG(has_file_id);
PARSE_FLAG(has_new_local_id);
END_PARSE_FLAGS();
td::parse(id, parser);
td::parse(access_hash, parser);
@ -320,25 +320,89 @@ class BackgroundManager::BackgroundLogEvent {
}
};
class BackgroundManager::BackgroundsLogEvent {
public:
vector<Background> backgrounds_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(backgrounds_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(backgrounds_, parser);
}
};
void BackgroundManager::start_up() {
max_local_background_id_ = BackgroundId(to_integer<int64>(G()->td_db()->get_binlog_pmc()->get("max_bg_id")));
// first parse all log events and fix max_local_background_id_ value
bool has_selected_background[2] = {false, false};
BackgroundLogEvent selected_background_log_event[2];
for (int i = 0; i < 2; i++) {
bool for_dark_theme = i != 0;
auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_background_database_key(for_dark_theme));
if (!log_event_string.empty()) {
BackgroundLogEvent log_event;
log_event_parse(log_event, log_event_string).ensure();
CHECK(log_event.background_.id.is_valid());
bool needs_file_id = log_event.background_.type.type != BackgroundType::Type::Fill;
if (log_event.background_.file_id.is_valid() != needs_file_id) {
LOG(ERROR) << "Failed to load " << log_event.background_.id << " of " << log_event.background_.type;
G()->td_db()->get_binlog_pmc()->erase(get_background_database_key(for_dark_theme));
continue;
has_selected_background[i] = true;
log_event_parse(selected_background_log_event[i], log_event_string).ensure();
const Background &background = selected_background_log_event[i].background_;
if (background.has_new_local_id && background.id.is_local() && !background.type.has_file() &&
background.id.get() > max_local_background_id_.get()) {
set_max_local_background_id(background.id);
}
set_background_id_[for_dark_theme] = log_event.background_.id;
set_background_type_[for_dark_theme] = log_event.set_type_;
}
}
add_background(log_event.background_);
for (int i = 0; i < 2; i++) {
bool for_dark_theme = i != 0;
auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_local_backgrounds_database_key(for_dark_theme));
if (!log_event_string.empty()) {
BackgroundsLogEvent log_event;
log_event_parse(log_event, log_event_string).ensure();
for (const auto &background : log_event.backgrounds_) {
CHECK(background.has_new_local_id);
CHECK(background.id.is_valid());
CHECK(background.id.is_local());
CHECK(!background.type.has_file());
CHECK(!background.file_id.is_valid());
if (background.id.get() > max_local_background_id_.get()) {
set_max_local_background_id(background.id);
}
add_background(background);
local_background_ids_[for_dark_theme].push_back(background.id);
}
}
}
// then add selected backgrounds fixing their ID
for (int i = 0; i < 2; i++) {
bool for_dark_theme = i != 0;
if (has_selected_background[i]) {
Background &background = selected_background_log_event[i].background_;
bool need_resave = false;
if (!background.has_new_local_id && !background.type.has_file()) {
background.has_new_local_id = true;
background.id = get_next_local_background_id();
need_resave = true;
}
CHECK(background.id.is_valid());
if (background.file_id.is_valid() != background.type.has_file()) {
LOG(ERROR) << "Failed to load " << background.id << " of " << background.type;
need_resave = true;
} else {
set_background_id_[for_dark_theme] = background.id;
set_background_type_[for_dark_theme] = selected_background_log_event[i].set_type_;
add_background(background);
}
if (need_resave) {
save_background_id(for_dark_theme);
}
}
send_update_selected_background(for_dark_theme);
@ -363,10 +427,10 @@ void BackgroundManager::get_backgrounds(Promise<Unit> &&promise) {
Result<string> BackgroundManager::get_background_url(const string &name,
td_api::object_ptr<td_api::BackgroundType> background_type) const {
TRY_RESULT(type, get_background_type(background_type.get()));
TRY_RESULT(type, BackgroundType::get_background_type(background_type.get()));
auto url = PSTRING() << G()->shared_config().get_option_string("t_me_url", "https://t.me/") << "bg/";
auto link = type.get_link();
if (type.is_server()) {
if (type.has_file()) {
url += name;
if (!link.empty()) {
url += '?';
@ -395,86 +459,58 @@ void BackgroundManager::reload_background(BackgroundId background_id, int64 acce
}
static bool is_background_name_local(Slice name) {
return name.size() <= 6 || name.find('?') <= 13u;
return name.size() <= 13u || name.find('?') <= 13u || !is_base64url_characters(name.substr(0, name.find('?')));
}
BackgroundId BackgroundManager::search_background(const string &name, Promise<Unit> &&promise) {
auto it = name_to_background_id_.find(name);
std::pair<BackgroundId, BackgroundType> BackgroundManager::search_background(const string &name,
Promise<Unit> &&promise) {
auto params_pos = name.find('?');
string slug = params_pos >= name.size() ? name : name.substr(0, params_pos);
auto it = name_to_background_id_.find(slug);
if (it != name_to_background_id_.end()) {
CHECK(!is_background_name_local(slug));
const auto *background = get_background(it->second);
CHECK(background != nullptr);
promise.set_value(Unit());
return it->second;
BackgroundType type = background->type;
type.apply_parameters_from_link(name);
return {it->second, std::move(type)};
}
if (name.empty()) {
if (slug.empty()) {
promise.set_error(Status::Error(400, "Background name must be non-empty"));
return BackgroundId();
return {};
}
if (is_background_name_local(name)) {
Slice fill_colors = name;
Slice parameters;
auto parameters_pos = fill_colors.find('?');
if (parameters_pos != Slice::npos) {
parameters = fill_colors.substr(parameters_pos + 1);
fill_colors = fill_colors.substr(0, parameters_pos);
if (is_background_name_local(slug)) {
auto r_type = BackgroundType::get_local_background_type(name);
if (r_type.is_error()) {
promise.set_error(r_type.move_as_error());
return {};
}
CHECK(fill_colors.size() <= 13u);
bool have_hyphen = false;
size_t hyphen_pos = 0;
for (size_t i = 0; i < fill_colors.size(); i++) {
auto c = fill_colors[i];
if (!is_hex_digit(c)) {
if (c != '-' || have_hyphen || i > 6 || i + 7 < fill_colors.size()) {
promise.set_error(Status::Error(400, "WALLPAPER_INVALID"));
return BackgroundId();
}
have_hyphen = true;
hyphen_pos = i;
}
}
BackgroundFill fill;
if (have_hyphen) {
int32 top_color = static_cast<int32>(hex_to_integer<uint32>(fill_colors.substr(0, hyphen_pos)));
int32 bottom_color = static_cast<int32>(hex_to_integer<uint32>(fill_colors.substr(hyphen_pos + 1)));
int32 rotation_angle = 0;
Slice prefix("rotation=");
if (begins_with(parameters, prefix)) {
rotation_angle = to_integer<int32>(parameters.substr(prefix.size()));
if (!BackgroundFill::is_valid_rotation_angle(rotation_angle)) {
rotation_angle = 0;
}
}
fill = BackgroundFill(top_color, bottom_color, rotation_angle);
} else {
int32 color = static_cast<int32>(hex_to_integer<uint32>(fill_colors));
fill = BackgroundFill(color);
}
auto background_id = add_fill_background(fill);
auto background_id = add_local_background(r_type.ok());
promise.set_value(Unit());
return background_id;
return {background_id, r_type.ok()};
}
if (G()->parameters().use_file_db && loaded_from_database_backgrounds_.count(name) == 0) {
auto &queries = being_loaded_from_database_backgrounds_[name];
if (G()->parameters().use_file_db && loaded_from_database_backgrounds_.count(slug) == 0) {
auto &queries = being_loaded_from_database_backgrounds_[slug];
queries.push_back(std::move(promise));
if (queries.size() == 1) {
LOG(INFO) << "Trying to load background " << name << " from database";
LOG(INFO) << "Trying to load background " << slug << " from database";
G()->td_db()->get_sqlite_pmc()->get(
get_background_name_database_key(name), PromiseCreator::lambda([name](string value) {
get_background_name_database_key(slug), PromiseCreator::lambda([slug](string value) {
send_closure(G()->background_manager(), &BackgroundManager::on_load_background_from_database,
std::move(name), std::move(value));
std::move(slug), std::move(value));
}));
}
return BackgroundId();
return {};
}
reload_background_from_server(BackgroundId(), name, telegram_api::make_object<telegram_api::inputWallPaperSlug>(name),
reload_background_from_server(BackgroundId(), slug, telegram_api::make_object<telegram_api::inputWallPaperSlug>(slug),
std::move(promise));
return BackgroundId();
return {};
}
void BackgroundManager::on_load_background_from_database(string name, string value) {
@ -495,7 +531,7 @@ void BackgroundManager::on_load_background_from_database(string name, string val
LOG(INFO) << "Successfully loaded background " << name << " of size " << value.size() << " from database";
Background background;
auto status = log_event_parse(background, value);
if (status.is_error() || background.type.type == BackgroundType::Type::Fill || !background.file_id.is_valid() ||
if (status.is_error() || !background.type.has_file() || !background.file_id.is_valid() ||
!background.id.is_valid()) {
LOG(ERROR) << "Can't load background " << name << ": " << status << ' ' << format::as_hex_dump<4>(Slice(value));
} else {
@ -539,56 +575,75 @@ Result<FileId> BackgroundManager::prepare_input_file(const tl_object_ptr<td_api:
return std::move(file_id);
}
BackgroundId BackgroundManager::add_fill_background(const BackgroundFill &fill) {
return add_fill_background(fill, false, (fill.top_color & 0x808080) == 0 && (fill.bottom_color & 0x808080) == 0);
void BackgroundManager::set_max_local_background_id(BackgroundId background_id) {
CHECK(background_id.is_local());
CHECK(background_id.get() > max_local_background_id_.get());
max_local_background_id_ = background_id;
G()->td_db()->get_binlog_pmc()->set("max_bg_id", to_string(max_local_background_id_.get()));
}
BackgroundId BackgroundManager::add_fill_background(const BackgroundFill &fill, bool is_default, bool is_dark) {
BackgroundId background_id(fill.get_id());
BackgroundId BackgroundManager::get_next_local_background_id() {
set_max_local_background_id(BackgroundId(max_local_background_id_.get() + 1));
return max_local_background_id_;
}
BackgroundId BackgroundManager::add_local_background(const BackgroundType &type) {
Background background;
background.id = background_id;
background.id = get_next_local_background_id();
background.is_creator = true;
background.is_default = is_default;
background.is_dark = is_dark;
background.type = BackgroundType(fill);
background.name = background.type.get_link();
background.is_default = false;
background.is_dark = type.is_dark();
background.type = type;
background.name = type.get_link();
add_background(background);
return background_id;
return background.id;
}
BackgroundId BackgroundManager::set_background(const td_api::InputBackground *input_background,
const td_api::BackgroundType *background_type, bool for_dark_theme,
Promise<Unit> &&promise) {
if (background_type == nullptr) {
set_background_id(BackgroundId(), BackgroundType(), for_dark_theme);
promise.set_value(Unit());
return BackgroundId();
BackgroundType type;
if (background_type != nullptr) {
auto r_type = BackgroundType::get_background_type(background_type);
if (r_type.is_error()) {
promise.set_error(r_type.move_as_error());
return BackgroundId();
}
type = r_type.move_as_ok();
} else {
CHECK(!type.has_file());
}
auto r_type = get_background_type(background_type);
if (r_type.is_error()) {
promise.set_error(r_type.move_as_error());
return BackgroundId();
}
auto type = r_type.move_as_ok();
if (type.type == BackgroundType::Type::Fill) {
auto background_id = add_fill_background(type.fill);
set_background_id(background_id, type, for_dark_theme);
promise.set_value(Unit());
return background_id;
}
CHECK(type.is_server());
if (input_background == nullptr) {
promise.set_error(Status::Error(400, "Input background must be non-empty"));
return BackgroundId();
if (background_type == nullptr) {
set_background_id(BackgroundId(), BackgroundType(), for_dark_theme);
promise.set_value(Unit());
return BackgroundId();
}
if (type.has_file()) {
promise.set_error(Status::Error(400, "Input background must be non-empty for the background type"));
return BackgroundId();
}
auto background_id = add_local_background(type);
set_background_id(background_id, type, for_dark_theme);
local_background_ids_[for_dark_theme].insert(local_background_ids_[for_dark_theme].begin(), background_id);
save_local_backgrounds(for_dark_theme);
promise.set_value(Unit());
return background_id;
}
switch (input_background->get_id()) {
case td_api::inputBackgroundLocal::ID: {
if (!type.has_file()) {
promise.set_error(Status::Error(400, "Can't specify local file for the background type"));
return BackgroundId();
}
CHECK(background_type != nullptr);
auto background_local = static_cast<const td_api::inputBackgroundLocal *>(input_background);
auto r_file_id = prepare_input_file(background_local->background_);
if (r_file_id.is_error()) {
@ -608,7 +663,8 @@ BackgroundId BackgroundManager::set_background(const td_api::InputBackground *in
}
case td_api::inputBackgroundRemote::ID: {
auto background_remote = static_cast<const td_api::inputBackgroundRemote *>(input_background);
return set_background(BackgroundId(background_remote->background_id_), type, for_dark_theme, std::move(promise));
return set_background(BackgroundId(background_remote->background_id_), std::move(type), for_dark_theme,
std::move(promise));
}
default:
UNREACHABLE();
@ -616,15 +672,17 @@ BackgroundId BackgroundManager::set_background(const td_api::InputBackground *in
return BackgroundId();
}
BackgroundId BackgroundManager::set_background(BackgroundId background_id, const BackgroundType &type,
bool for_dark_theme, Promise<Unit> &&promise) {
BackgroundId BackgroundManager::set_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
Promise<Unit> &&promise) {
LOG(INFO) << "Set " << background_id << " with " << type;
auto *background = get_background(background_id);
const auto *background = get_background(background_id);
if (background == nullptr) {
promise.set_error(Status::Error(400, "Background to set not found"));
return BackgroundId();
}
if (background->type.type != type.type) {
if (!type.has_file()) {
type = background->type;
} else if (!background->type.has_equal_type(type)) {
promise.set_error(Status::Error(400, "Background type mismatch"));
return BackgroundId();
}
@ -634,13 +692,21 @@ BackgroundId BackgroundManager::set_background(BackgroundId background_id, const
}
LOG(INFO) << "Install " << background_id << " with " << type;
if (!type.has_file()) {
set_background_id(background_id, type, for_dark_theme);
promise.set_value(Unit());
return background_id;
}
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), background_id, type, for_dark_theme,
promise = std::move(promise)](Result<Unit> &&result) mutable {
send_closure(actor_id, &BackgroundManager::on_installed_background, background_id, type, for_dark_theme,
std::move(result), std::move(promise));
});
td_->create_handler<InstallBackgroundQuery>(std::move(query_promise))
->send(background_id, background->access_hash, type);
->send(telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), background->access_hash),
type);
return BackgroundId();
}
@ -661,7 +727,11 @@ string BackgroundManager::get_background_database_key(bool for_dark_theme) {
return for_dark_theme ? "bgd" : "bg";
}
void BackgroundManager::save_background_id(bool for_dark_theme) const {
string BackgroundManager::get_local_backgrounds_database_key(bool for_dark_theme) {
return for_dark_theme ? "bgsd" : "bgs";
}
void BackgroundManager::save_background_id(bool for_dark_theme) {
string key = get_background_database_key(for_dark_theme);
auto background_id = set_background_id_[for_dark_theme];
if (background_id.is_valid()) {
@ -686,6 +756,26 @@ void BackgroundManager::set_background_id(BackgroundId background_id, const Back
send_update_selected_background(for_dark_theme);
}
void BackgroundManager::save_local_backgrounds(bool for_dark_theme) {
string key = get_local_backgrounds_database_key(for_dark_theme);
auto &background_ids = local_background_ids_[for_dark_theme];
const size_t MAX_LOCAL_BACKGROUNDS = 100;
while (background_ids.size() > MAX_LOCAL_BACKGROUNDS) {
background_ids.pop_back();
}
if (!background_ids.empty()) {
BackgroundsLogEvent log_event;
log_event.backgrounds_ = transform(background_ids, [&](BackgroundId background_id) {
const Background *background = get_background(background_id);
CHECK(background != nullptr);
return *background;
});
G()->td_db()->get_binlog_pmc()->set(key, log_event_store(log_event).as_slice().str());
} else {
G()->td_db()->get_binlog_pmc()->erase(key);
}
}
void BackgroundManager::upload_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
Promise<Unit> &&promise) {
auto upload_file_id = td_->file_manager_->dup_file_id(file_id);
@ -763,7 +853,7 @@ void BackgroundManager::on_uploaded_background_file(FileId file_id, const Backgr
return promise.set_error(Status::Error(500, "Receive wrong uploaded background"));
}
auto background = get_background(background_id);
const auto *background = get_background(background_id);
if (!(background != nullptr)) return promise.set_error(Status::Error(500, "Error"));
if (!background->file_id.is_valid()) {
td_->file_manager_->cancel_upload(file_id);
@ -775,7 +865,7 @@ void BackgroundManager::on_uploaded_background_file(FileId file_id, const Backgr
}
void BackgroundManager::remove_background(BackgroundId background_id, Promise<Unit> &&promise) {
auto background = get_background(background_id);
const auto *background = get_background(background_id);
if (background == nullptr) {
return promise.set_error(Status::Error(400, "Background not found"));
}
@ -786,12 +876,17 @@ void BackgroundManager::remove_background(BackgroundId background_id, Promise<Un
std::move(promise));
});
if (!background->type.is_server()) {
return query_promise.set_value(Unit());
if (!background->type.has_file()) {
if (!background->id.is_local()) {
return td_->create_handler<UnsaveBackgroundQuery>(std::move(query_promise))
->send(telegram_api::make_object<telegram_api::inputWallPaperNoFile>(background_id.get()));
} else {
return query_promise.set_value(Unit());
}
}
td_->create_handler<SaveBackgroundQuery>(std::move(query_promise))
->send(background_id, background->access_hash, background->type, true);
td_->create_handler<UnsaveBackgroundQuery>(std::move(query_promise))
->send(telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), background->access_hash));
}
void BackgroundManager::on_removed_background(BackgroundId background_id, Result<Unit> &&result,
@ -806,6 +901,14 @@ void BackgroundManager::on_removed_background(BackgroundId background_id, Result
if (background_id == set_background_id_[1]) {
set_background_id(BackgroundId(), BackgroundType(), true);
}
if (background_id.is_local()) {
if (td::remove(local_background_ids_[0], background_id)) {
save_local_backgrounds(false);
}
if (td::remove(local_background_ids_[1], background_id)) {
save_local_backgrounds(true);
}
}
promise.set_value(Unit());
}
@ -825,6 +928,15 @@ void BackgroundManager::on_reset_background(Result<Unit> &&result, Promise<Unit>
installed_background_ids_.clear();
set_background_id(BackgroundId(), BackgroundType(), false);
set_background_id(BackgroundId(), BackgroundType(), true);
if (!local_background_ids_[0].empty()) {
local_background_ids_[0].clear();
save_local_backgrounds(false);
}
if (!local_background_ids_[1].empty()) {
local_background_ids_[1].clear();
save_local_backgrounds(true);
}
promise.set_value(Unit());
}
@ -933,37 +1045,38 @@ BackgroundId BackgroundManager::on_get_background(BackgroundId expected_backgrou
if (wallpaper_ptr->get_id() == telegram_api::wallPaperNoFile::ID) {
auto wallpaper = move_tl_object_as<telegram_api::wallPaperNoFile>(wallpaper_ptr);
auto settings = std::move(wallpaper->settings_);
if (settings == nullptr) {
if (wallpaper->settings_ == nullptr) {
LOG(ERROR) << "Receive wallPaperNoFile without settings: " << to_string(wallpaper);
return BackgroundId();
}
bool has_color = (settings->flags_ & telegram_api::wallPaperSettings::BACKGROUND_COLOR_MASK) != 0;
auto color = has_color ? settings->background_color_ : 0;
auto is_default = (wallpaper->flags_ & telegram_api::wallPaperNoFile::DEFAULT_MASK) != 0;
auto is_dark = (wallpaper->flags_ & telegram_api::wallPaperNoFile::DARK_MASK) != 0;
BackgroundFill fill = BackgroundFill(color);
if ((settings->flags_ & telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK) != 0) {
fill = BackgroundFill(color, settings->second_background_color_, settings->rotation_);
auto background_id = BackgroundId(wallpaper->id_);
if (!background_id.is_valid() || background_id.is_local()) {
LOG(ERROR) << "Receive " << to_string(wallpaper);
return BackgroundId();
}
return add_fill_background(fill, is_default, is_dark);
Background background;
background.id = background_id;
background.is_creator = false;
background.is_default = (wallpaper->flags_ & telegram_api::wallPaperNoFile::DEFAULT_MASK) != 0;
background.is_dark = (wallpaper->flags_ & telegram_api::wallPaperNoFile::DARK_MASK) != 0;
background.type = BackgroundType(true, false, std::move(wallpaper->settings_));
background.name = background.type.get_link();
add_background(background);
return background_id;
}
auto wallpaper = move_tl_object_as<telegram_api::wallPaper>(wallpaper_ptr);
auto id = BackgroundId(wallpaper->id_);
if (!id.is_valid()) {
auto background_id = BackgroundId(wallpaper->id_);
if (!background_id.is_valid() || background_id.is_local() || is_background_name_local(wallpaper->slug_)) {
LOG(ERROR) << "Receive " << to_string(wallpaper);
return BackgroundId();
}
if (expected_background_id.is_valid() && id != expected_background_id) {
if (expected_background_id.is_valid() && background_id != expected_background_id) {
LOG(ERROR) << "Expected " << expected_background_id << ", but receive " << to_string(wallpaper);
}
if (is_background_name_local(wallpaper->slug_) || BackgroundFill::is_valid_id(wallpaper->id_)) {
LOG(ERROR) << "Receive " << to_string(wallpaper);
return BackgroundId();
}
int32 document_id = wallpaper->document_->get_id();
if (document_id == telegram_api::documentEmpty::ID) {
@ -985,28 +1098,29 @@ BackgroundId BackgroundManager::on_get_background(BackgroundId expected_backgrou
CHECK(document.type == Document::Type::General); // guaranteed by is_background parameter to on_get_document
Background background;
background.id = id;
background.id = background_id;
background.access_hash = wallpaper->access_hash_;
background.is_creator = (flags & telegram_api::wallPaper::CREATOR_MASK) != 0;
background.is_default = (flags & telegram_api::wallPaper::DEFAULT_MASK) != 0;
background.is_dark = (flags & telegram_api::wallPaper::DARK_MASK) != 0;
background.type = get_background_type(is_pattern, std::move(wallpaper->settings_));
background.type = BackgroundType(false, is_pattern, std::move(wallpaper->settings_));
background.name = std::move(wallpaper->slug_);
background.file_id = document.file_id;
add_background(background);
if (!expected_background_name.empty() && background.name != expected_background_name) {
LOG(ERROR) << "Expected background " << expected_background_name << ", but receive " << background.name;
name_to_background_id_.emplace(expected_background_name, id);
name_to_background_id_.emplace(expected_background_name, background_id);
}
if (G()->parameters().use_file_db && !is_background_name_local(background.name)) {
LOG(INFO) << "Save " << id << " to database with name " << background.name;
if (G()->parameters().use_file_db) {
LOG(INFO) << "Save " << background_id << " to database with name " << background.name;
CHECK(!is_background_name_local(background.name));
G()->td_db()->get_sqlite_pmc()->set(get_background_name_database_key(background.name),
log_event_store(background).as_slice().str(), Auto());
}
return id;
return background_id;
}
void BackgroundManager::on_get_backgrounds(Result<telegram_api::object_ptr<telegram_api::account_WallPapers>> result) {
@ -1048,23 +1162,26 @@ void BackgroundManager::on_get_backgrounds(Result<telegram_api::object_ptr<teleg
}
td_api::object_ptr<td_api::background> BackgroundManager::get_background_object(BackgroundId background_id,
bool for_dark_theme) const {
auto background = get_background(background_id);
bool for_dark_theme,
const BackgroundType *type) const {
const auto *background = get_background(background_id);
if (background == nullptr) {
return nullptr;
}
auto type = &background->type;
// first check another set_background_id to get correct type if both backgrounds are the same
if (background_id == set_background_id_[1 - static_cast<int>(for_dark_theme)]) {
type = &set_background_type_[1 - static_cast<int>(for_dark_theme)];
}
if (background_id == set_background_id_[for_dark_theme]) {
type = &set_background_type_[for_dark_theme];
if (type == nullptr) {
type = &background->type;
// first check another set_background_id to get correct type if both backgrounds are the same
if (background_id == set_background_id_[1 - static_cast<int>(for_dark_theme)]) {
type = &set_background_type_[1 - static_cast<int>(for_dark_theme)];
}
if (background_id == set_background_id_[for_dark_theme]) {
type = &set_background_type_[for_dark_theme];
}
}
return td_api::make_object<td_api::background>(
background->id.get(), background->is_default, background->is_dark, background->name,
td_->documents_manager_->get_document_object(background->file_id, PhotoFormat::Png),
get_background_type_object(*type));
type->get_background_type_object());
}
td_api::object_ptr<td_api::backgrounds> BackgroundManager::get_backgrounds_object(bool for_dark_theme) const {
@ -1075,6 +1192,11 @@ td_api::object_ptr<td_api::backgrounds> BackgroundManager::get_backgrounds_objec
if (background_id.is_valid() && !td::contains(installed_background_ids_, background_id)) {
backgrounds.push_back(get_background_object(background_id, for_dark_theme));
}
for (auto local_background_id : local_background_ids_[for_dark_theme]) {
if (local_background_id != background_id) {
backgrounds.push_back(get_background_object(local_background_id, for_dark_theme));
}
}
std::stable_sort(backgrounds.begin(), backgrounds.end(),
[background_id, for_dark_theme](const td_api::object_ptr<td_api::background> &lhs,
const td_api::object_ptr<td_api::background> &rhs) {
@ -1083,10 +1205,9 @@ td_api::object_ptr<td_api::backgrounds> BackgroundManager::get_backgrounds_objec
if (background->id_ == background_id.get()) {
return 0;
}
if (background->is_dark_ == for_dark_theme) {
return 1;
}
return 2;
int theme_score = background->is_dark_ == for_dark_theme ? 0 : 1;
int local_score = BackgroundId(background->id_).is_local() ? 0 : 2;
return 1 + local_score + theme_score;
};
return get_order(lhs) < get_order(rhs);
});

View File

@ -44,7 +44,7 @@ class BackgroundManager : public Actor {
void reload_background(BackgroundId background_id, int64 access_hash, Promise<Unit> &&promise);
BackgroundId search_background(const string &name, Promise<Unit> &&promise);
std::pair<BackgroundId, BackgroundType> search_background(const string &name, Promise<Unit> &&promise);
BackgroundId set_background(const td_api::InputBackground *input_background,
const td_api::BackgroundType *background_type, bool for_dark_theme,
@ -54,7 +54,8 @@ class BackgroundManager : public Actor {
void reset_backgrounds(Promise<Unit> &&promise);
td_api::object_ptr<td_api::background> get_background_object(BackgroundId background_id, bool for_dark_theme) const;
td_api::object_ptr<td_api::background> get_background_object(BackgroundId background_id, bool for_dark_theme,
const BackgroundType *type = nullptr) const;
td_api::object_ptr<td_api::backgrounds> get_backgrounds_object(bool for_dark_theme) const;
@ -78,6 +79,7 @@ class BackgroundManager : public Actor {
bool is_creator = false;
bool is_default = false;
bool is_dark = false;
bool has_new_local_id = true;
BackgroundType type;
FileSourceId file_source_id;
@ -89,6 +91,7 @@ class BackgroundManager : public Actor {
};
class BackgroundLogEvent;
class BackgroundsLogEvent;
class UploadBackgroundFileCallback;
@ -100,7 +103,11 @@ class BackgroundManager : public Actor {
static string get_background_database_key(bool for_dark_theme);
void save_background_id(bool for_dark_theme) const;
static string get_local_backgrounds_database_key(bool for_dark_theme);
void save_background_id(bool for_dark_theme);
void save_local_backgrounds(bool for_dark_theme);
void reload_background_from_server(BackgroundId background_id, const string &background_name,
telegram_api::object_ptr<telegram_api::InputWallPaper> &&input_wallpaper,
@ -110,9 +117,11 @@ class BackgroundManager : public Actor {
void send_update_selected_background(bool for_dark_theme) const;
BackgroundId add_fill_background(const BackgroundFill &fill);
void set_max_local_background_id(BackgroundId background_id);
BackgroundId add_fill_background(const BackgroundFill &fill, bool is_default, bool is_dark);
BackgroundId get_next_local_background_id();
BackgroundId add_local_background(const BackgroundType &type);
void add_background(const Background &background);
@ -128,7 +137,7 @@ class BackgroundManager : public Actor {
Result<FileId> prepare_input_file(const tl_object_ptr<td_api::InputFile> &input_file);
BackgroundId set_background(BackgroundId background_id, const BackgroundType &type, bool for_dark_theme,
BackgroundId set_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
Promise<Unit> &&promise);
void on_installed_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
@ -177,6 +186,9 @@ class BackgroundManager : public Actor {
};
std::unordered_map<FileId, UploadedFileInfo, FileIdHash> being_uploaded_files_;
BackgroundId max_local_background_id_;
vector<BackgroundId> local_background_ids_[2];
Td *td_;
ActorShared<> parent_;
};

View File

@ -6,7 +6,9 @@
//
#include "td/telegram/BackgroundType.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
@ -15,7 +17,7 @@ namespace td {
static string get_color_hex_string(int32 color) {
string result;
for (int i = 20; i >= 0; i -= 4) {
result += "0123456789abcdef"[(color >> i) & 0xf];
result += "0123456789abcdef"[(color >> i) & 0xF];
}
return result;
}
@ -24,7 +26,58 @@ static bool is_valid_color(int32 color) {
return 0 <= color && color <= 0xFFFFFF;
}
static Result<BackgroundFill> get_background_fill(const td_api::BackgroundFill *fill) {
static bool is_valid_rotation_angle(int32 rotation_angle) {
return 0 <= rotation_angle && rotation_angle < 360 && rotation_angle % 45 == 0;
}
BackgroundFill::BackgroundFill(const telegram_api::wallPaperSettings *settings) {
if (settings == nullptr) {
return;
}
auto flags = settings->flags_;
if ((flags & telegram_api::wallPaperSettings::BACKGROUND_COLOR_MASK) != 0) {
top_color_ = settings->background_color_;
if (!is_valid_color(top_color_)) {
LOG(ERROR) << "Receive " << to_string(*settings);
top_color_ = 0;
}
}
if ((flags & telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK) != 0 ||
(flags & telegram_api::wallPaperSettings::THIRD_BACKGROUND_COLOR_MASK) != 0) {
bottom_color_ = settings->second_background_color_;
if (!is_valid_color(bottom_color_)) {
LOG(ERROR) << "Receive " << to_string(*settings);
bottom_color_ = 0;
}
third_color_ = settings->third_background_color_;
if (!is_valid_color(third_color_)) {
LOG(ERROR) << "Receive " << to_string(*settings);
third_color_ = 0;
}
if ((flags & telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK) != 0) {
fourth_color_ = settings->fourth_background_color_;
if (!is_valid_color(fourth_color_)) {
LOG(ERROR) << "Receive " << to_string(*settings);
fourth_color_ = 0;
}
}
} else if ((flags & telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK) != 0) {
bottom_color_ = settings->second_background_color_;
if (!is_valid_color(bottom_color_)) {
LOG(ERROR) << "Receive " << to_string(*settings);
bottom_color_ = 0;
}
rotation_angle_ = settings->rotation_;
if (!is_valid_rotation_angle(rotation_angle_)) {
LOG(ERROR) << "Receive " << to_string(*settings);
rotation_angle_ = 0;
}
}
}
Result<BackgroundFill> BackgroundFill::get_background_fill(const td_api::BackgroundFill *fill) {
if (fill == nullptr) {
return Status::Error(400, "Background fill info must be non-empty");
}
@ -44,28 +97,105 @@ static Result<BackgroundFill> get_background_fill(const td_api::BackgroundFill *
if (!is_valid_color(gradient->bottom_color_)) {
return Status::Error(400, "Invalid bottom gradient color value");
}
if (!BackgroundFill::is_valid_rotation_angle(gradient->rotation_angle_)) {
if (!is_valid_rotation_angle(gradient->rotation_angle_)) {
return Status::Error(400, "Invalid rotation angle value");
}
return BackgroundFill(gradient->top_color_, gradient->bottom_color_, gradient->rotation_angle_);
}
case td_api::backgroundFillFreeformGradient::ID: {
auto freeform = static_cast<const td_api::backgroundFillFreeformGradient *>(fill);
if (freeform->colors_.size() != 3 && freeform->colors_.size() != 4) {
return Status::Error(400, "Wrong number of gradient colors");
}
for (auto &color : freeform->colors_) {
if (!is_valid_color(color)) {
return Status::Error(400, "Invalid freeform gradient color value");
}
}
return BackgroundFill(freeform->colors_[0], freeform->colors_[1], freeform->colors_[2],
freeform->colors_.size() == 3 ? -1 : freeform->colors_[3]);
}
default:
UNREACHABLE();
return {};
}
}
static string get_background_fill_color_hex_string(const BackgroundFill &fill, bool is_first) {
switch (fill.get_type()) {
case BackgroundFill::Type::Solid:
return get_color_hex_string(fill.top_color);
case BackgroundFill::Type::Gradient: {
string colors = PSTRING() << get_color_hex_string(fill.top_color) << '-'
<< get_color_hex_string(fill.bottom_color);
if (fill.rotation_angle != 0) {
colors += (PSTRING() << (is_first ? '?' : '&') << "rotation=" << fill.rotation_angle);
Result<BackgroundFill> BackgroundFill::get_background_fill(Slice name) {
name = name.substr(0, name.find('#'));
Slice parameters;
auto parameters_pos = name.find('?');
if (parameters_pos != Slice::npos) {
parameters = name.substr(parameters_pos + 1);
name = name.substr(0, parameters_pos);
}
auto get_color = [](Slice color_string) -> Result<int32> {
auto r_color = hex_to_integer_safe<uint32>(color_string);
if (r_color.is_error() || color_string.size() > 6) {
return Status::Error(400, "WALLPAPER_INVALID");
}
return static_cast<int32>(r_color.ok());
};
size_t hyphen_pos = name.find('-');
if (name.find('~') < name.size()) {
vector<Slice> color_strings = full_split(name, '~');
CHECK(color_strings.size() >= 2);
if (color_strings.size() == 2) {
hyphen_pos = color_strings[0].size();
} else {
if (color_strings.size() > 4) {
return Status::Error(400, "WALLPAPER_INVALID");
}
return colors;
TRY_RESULT(first_color, get_color(color_strings[0]));
TRY_RESULT(second_color, get_color(color_strings[1]));
TRY_RESULT(third_color, get_color(color_strings[2]));
int32 fourth_color = -1;
if (color_strings.size() == 4) {
TRY_RESULT_ASSIGN(fourth_color, get_color(color_strings[3]));
}
return BackgroundFill(first_color, second_color, third_color, fourth_color);
}
}
if (hyphen_pos < name.size()) {
TRY_RESULT(top_color, get_color(name.substr(0, hyphen_pos)));
TRY_RESULT(bottom_color, get_color(name.substr(hyphen_pos + 1)));
int32 rotation_angle = 0;
Slice prefix("rotation=");
if (begins_with(parameters, prefix)) {
rotation_angle = to_integer<int32>(parameters.substr(prefix.size()));
if (!is_valid_rotation_angle(rotation_angle)) {
rotation_angle = 0;
}
}
return BackgroundFill(top_color, bottom_color, rotation_angle);
}
TRY_RESULT(color, get_color(name));
return BackgroundFill(color);
}
string BackgroundFill::get_link(bool is_first) const {
switch (get_type()) {
case BackgroundFill::Type::Solid:
return get_color_hex_string(top_color_);
case BackgroundFill::Type::Gradient:
return PSTRING() << get_color_hex_string(top_color_) << '-' << get_color_hex_string(bottom_color_)
<< (is_first ? '?' : '&') << "rotation=" << rotation_angle_;
case BackgroundFill::Type::FreeformGradient: {
SliceBuilder sb;
sb << get_color_hex_string(top_color_) << '~' << get_color_hex_string(bottom_color_) << '~'
<< get_color_hex_string(third_color_);
if (fourth_color_ != -1) {
sb << '~' << get_color_hex_string(fourth_color_);
}
return sb.as_cslice().str();
}
default:
UNREACHABLE();
@ -74,64 +204,100 @@ static string get_background_fill_color_hex_string(const BackgroundFill &fill, b
}
static bool is_valid_intensity(int32 intensity) {
return 0 <= intensity && intensity <= 100;
return -100 <= intensity && intensity <= 100;
}
int64 BackgroundFill::get_id() const {
CHECK(is_valid_color(top_color));
CHECK(is_valid_color(bottom_color));
CHECK(is_valid_rotation_angle(rotation_angle));
bool BackgroundFill::is_dark() const {
switch (get_type()) {
case Type::Solid:
return static_cast<int64>(top_color) + 1;
return (top_color_ & 0x808080) == 0;
case Type::Gradient:
return (rotation_angle / 45) * 0x1000001000001 + (static_cast<int64>(top_color) << 24) + bottom_color +
(1 << 24) + 1;
return (top_color_ & 0x808080) == 0 && (bottom_color_ & 0x808080) == 0;
case Type::FreeformGradient:
return (top_color_ & 0x808080) == 0 && (bottom_color_ & 0x808080) == 0 && (third_color_ & 0x808080) == 0 &&
(fourth_color_ == -1 || (fourth_color_ & 0x808080) == 0);
default:
UNREACHABLE();
return 0;
}
}
bool BackgroundFill::is_valid_id(int64 id) {
return 0 < id && id < 0x8000008000008;
bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs) {
return lhs.top_color_ == rhs.top_color_ && lhs.bottom_color_ == rhs.bottom_color_ &&
lhs.rotation_angle_ == rhs.rotation_angle_ && lhs.third_color_ == rhs.third_color_ &&
lhs.fourth_color_ == rhs.fourth_color_;
}
bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs) {
return lhs.top_color == rhs.top_color && lhs.bottom_color == rhs.bottom_color &&
lhs.rotation_angle == rhs.rotation_angle;
string BackgroundType::get_mime_type() const {
CHECK(has_file());
return type_ == Type::Pattern ? "image/png" : "image/jpeg";
}
void BackgroundType::apply_parameters_from_link(Slice name) {
const auto query = parse_url_query(name);
is_blurred_ = false;
is_moving_ = false;
auto modes = full_split(query.get_arg("mode"), ' ');
for (auto &mode : modes) {
if (type_ != Type::Pattern && to_lower(mode) == "blur") {
is_blurred_ = true;
}
if (to_lower(mode) == "motion") {
is_moving_ = true;
}
}
if (type_ == Type::Pattern) {
intensity_ = -101;
auto intensity_arg = query.get_arg("intensity");
if (!intensity_arg.empty()) {
intensity_ = to_integer<int32>(intensity_arg);
}
if (!is_valid_intensity(intensity_)) {
intensity_ = 50;
}
auto bg_color = query.get_arg("bg_color");
if (!bg_color.empty()) {
auto r_fill = BackgroundFill::get_background_fill(
PSLICE() << url_encode(bg_color) << "?rotation=" << url_encode(query.get_arg("rotation")));
if (r_fill.is_ok()) {
fill_ = r_fill.move_as_ok();
}
}
}
}
string BackgroundType::get_link() const {
string mode;
if (is_blurred) {
if (is_blurred_) {
mode = "blur";
}
if (is_moving) {
if (is_moving_) {
if (!mode.empty()) {
mode += '+';
}
mode += "motion";
}
switch (type) {
case BackgroundType::Type::Wallpaper: {
switch (type_) {
case Type::Wallpaper: {
if (!mode.empty()) {
return PSTRING() << "mode=" << mode;
}
return string();
}
case BackgroundType::Type::Pattern: {
string link = PSTRING() << "intensity=" << intensity
<< "&bg_color=" << get_background_fill_color_hex_string(fill, false);
case Type::Pattern: {
string link = PSTRING() << "intensity=" << intensity_ << "&bg_color=" << fill_.get_link(false);
if (!mode.empty()) {
link += "&mode=";
link += mode;
}
return link;
}
case BackgroundType::Type::Fill:
return get_background_fill_color_hex_string(fill, true);
case Type::Fill:
return fill_.get_link(true);
default:
UNREACHABLE();
return string();
@ -139,152 +305,142 @@ string BackgroundType::get_link() const {
}
bool operator==(const BackgroundType &lhs, const BackgroundType &rhs) {
return lhs.type == rhs.type && lhs.is_blurred == rhs.is_blurred && lhs.is_moving == rhs.is_moving &&
lhs.intensity == rhs.intensity && lhs.fill == rhs.fill;
}
static StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType::Type &type) {
switch (type) {
case BackgroundType::Type::Wallpaper:
return string_builder << "Wallpaper";
case BackgroundType::Type::Pattern:
return string_builder << "Pattern";
case BackgroundType::Type::Fill:
return string_builder << "Fill";
default:
UNREACHABLE();
return string_builder;
}
return lhs.type_ == rhs.type_ && lhs.is_blurred_ == rhs.is_blurred_ && lhs.is_moving_ == rhs.is_moving_ &&
lhs.intensity_ == rhs.intensity_ && lhs.fill_ == rhs.fill_;
}
StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &type) {
return string_builder << "type " << type.type << '[' << type.get_link() << ']';
string_builder << "type ";
switch (type.type_) {
case BackgroundType::Type::Wallpaper:
string_builder << "Wallpaper";
break;
case BackgroundType::Type::Pattern:
string_builder << "Pattern";
break;
case BackgroundType::Type::Fill:
string_builder << "Fill";
break;
default:
UNREACHABLE();
break;
}
return string_builder << '[' << type.get_link() << ']';
}
Result<BackgroundType> get_background_type(const td_api::BackgroundType *type) {
if (type == nullptr) {
Result<BackgroundType> BackgroundType::get_background_type(const td_api::BackgroundType *background_type) {
if (background_type == nullptr) {
return Status::Error(400, "Type must be non-empty");
}
BackgroundType result;
switch (type->get_id()) {
switch (background_type->get_id()) {
case td_api::backgroundTypeWallpaper::ID: {
auto wallpaper = static_cast<const td_api::backgroundTypeWallpaper *>(type);
result = BackgroundType(wallpaper->is_blurred_, wallpaper->is_moving_);
break;
auto wallpaper_type = static_cast<const td_api::backgroundTypeWallpaper *>(background_type);
return BackgroundType(wallpaper_type->is_blurred_, wallpaper_type->is_moving_);
}
case td_api::backgroundTypePattern::ID: {
auto pattern = static_cast<const td_api::backgroundTypePattern *>(type);
TRY_RESULT(background_fill, get_background_fill(pattern->fill_.get()));
if (!is_valid_intensity(pattern->intensity_)) {
auto pattern_type = static_cast<const td_api::backgroundTypePattern *>(background_type);
TRY_RESULT(background_fill, BackgroundFill::get_background_fill(pattern_type->fill_.get()));
if (!is_valid_intensity(pattern_type->intensity_)) {
return Status::Error(400, "Wrong intensity value");
}
result = BackgroundType(pattern->is_moving_, std::move(background_fill), pattern->intensity_);
break;
return BackgroundType(pattern_type->is_moving_, std::move(background_fill), pattern_type->intensity_);
}
case td_api::backgroundTypeFill::ID: {
auto fill = static_cast<const td_api::backgroundTypeFill *>(type);
TRY_RESULT(background_fill, get_background_fill(fill->fill_.get()));
result = BackgroundType(std::move(background_fill));
break;
auto fill_type = static_cast<const td_api::backgroundTypeFill *>(background_type);
TRY_RESULT(background_fill, BackgroundFill::get_background_fill(fill_type->fill_.get()));
return BackgroundType(std::move(background_fill));
}
default:
UNREACHABLE();
return BackgroundType();
}
return result;
}
BackgroundType get_background_type(bool is_pattern,
telegram_api::object_ptr<telegram_api::wallPaperSettings> settings) {
bool is_blurred = false;
bool is_moving = false;
BackgroundFill fill;
int32 intensity = 0;
if (settings) {
auto flags = settings->flags_;
is_blurred = (flags & telegram_api::wallPaperSettings::BLUR_MASK) != 0;
is_moving = (flags & telegram_api::wallPaperSettings::MOTION_MASK) != 0;
Result<BackgroundType> BackgroundType::get_local_background_type(Slice name) {
TRY_RESULT(fill, BackgroundFill::get_background_fill(name));
return BackgroundType(fill);
}
int32 color = 0;
if ((flags & telegram_api::wallPaperSettings::BACKGROUND_COLOR_MASK) != 0) {
color = settings->background_color_;
if (!is_valid_color(color)) {
LOG(ERROR) << "Receive " << to_string(settings);
color = 0;
BackgroundType::BackgroundType(bool is_fill, bool is_pattern,
telegram_api::object_ptr<telegram_api::wallPaperSettings> settings) {
if (is_fill) {
type_ = Type::Fill;
CHECK(settings != nullptr);
fill_ = BackgroundFill(settings.get());
} else if (is_pattern) {
type_ = Type::Pattern;
if (settings) {
fill_ = BackgroundFill(settings.get());
is_moving_ = (settings->flags_ & telegram_api::wallPaperSettings::MOTION_MASK) != 0;
if ((settings->flags_ & telegram_api::wallPaperSettings::INTENSITY_MASK) != 0) {
intensity_ = settings->intensity_;
if (!is_valid_intensity(intensity_)) {
LOG(ERROR) << "Receive " << to_string(settings);
intensity_ = 50;
}
}
}
if ((flags & telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK) != 0) {
int32 second_color = settings->second_background_color_;
if (!is_valid_color(second_color)) {
LOG(ERROR) << "Receive " << to_string(settings);
second_color = 0;
}
int32 rotation_angle = settings->rotation_;
if (!BackgroundFill::is_valid_rotation_angle(rotation_angle)) {
LOG(ERROR) << "Receive " << to_string(settings);
rotation_angle = 0;
}
fill = BackgroundFill(color, second_color, rotation_angle);
} else {
fill = BackgroundFill(color);
}
if ((flags & telegram_api::wallPaperSettings::INTENSITY_MASK) != 0) {
intensity = settings->intensity_;
if (!is_valid_intensity(intensity)) {
LOG(ERROR) << "Receive " << to_string(settings);
intensity = 0;
}
}
}
if (is_pattern) {
return BackgroundType(is_moving, fill, intensity);
} else {
return BackgroundType(is_blurred, is_moving);
type_ = Type::Wallpaper;
if (settings) {
is_blurred_ = (settings->flags_ & telegram_api::wallPaperSettings::BLUR_MASK) != 0;
is_moving_ = (settings->flags_ & telegram_api::wallPaperSettings::MOTION_MASK) != 0;
}
}
}
static td_api::object_ptr<td_api::BackgroundFill> get_background_fill_object(const BackgroundFill &fill) {
switch (fill.get_type()) {
td_api::object_ptr<td_api::BackgroundFill> BackgroundFill::get_background_fill_object() const {
switch (get_type()) {
case BackgroundFill::Type::Solid:
return td_api::make_object<td_api::backgroundFillSolid>(fill.top_color);
return td_api::make_object<td_api::backgroundFillSolid>(top_color_);
case BackgroundFill::Type::Gradient:
return td_api::make_object<td_api::backgroundFillGradient>(fill.top_color, fill.bottom_color,
fill.rotation_angle);
return td_api::make_object<td_api::backgroundFillGradient>(top_color_, bottom_color_, rotation_angle_);
case BackgroundFill::Type::FreeformGradient: {
vector<int32> colors{top_color_, bottom_color_, third_color_, fourth_color_};
if (colors.back() == -1) {
colors.pop_back();
}
return td_api::make_object<td_api::backgroundFillFreeformGradient>(std::move(colors));
}
default:
UNREACHABLE();
return nullptr;
}
}
td_api::object_ptr<td_api::BackgroundType> get_background_type_object(const BackgroundType &type) {
switch (type.type) {
case BackgroundType::Type::Wallpaper:
return td_api::make_object<td_api::backgroundTypeWallpaper>(type.is_blurred, type.is_moving);
case BackgroundType::Type::Pattern:
return td_api::make_object<td_api::backgroundTypePattern>(get_background_fill_object(type.fill), type.intensity,
type.is_moving);
case BackgroundType::Type::Fill:
return td_api::make_object<td_api::backgroundTypeFill>(get_background_fill_object(type.fill));
td_api::object_ptr<td_api::BackgroundType> BackgroundType::get_background_type_object() const {
switch (type_) {
case Type::Wallpaper:
return td_api::make_object<td_api::backgroundTypeWallpaper>(is_blurred_, is_moving_);
case Type::Pattern:
return td_api::make_object<td_api::backgroundTypePattern>(fill_.get_background_fill_object(), intensity_,
is_moving_);
case Type::Fill:
return td_api::make_object<td_api::backgroundTypeFill>(fill_.get_background_fill_object());
default:
UNREACHABLE();
return nullptr;
}
}
telegram_api::object_ptr<telegram_api::wallPaperSettings> get_input_wallpaper_settings(const BackgroundType &type) {
CHECK(type.is_server());
telegram_api::object_ptr<telegram_api::wallPaperSettings> BackgroundType::get_input_wallpaper_settings() const {
CHECK(has_file());
int32 flags = 0;
if (type.is_blurred) {
if (is_blurred_) {
flags |= telegram_api::wallPaperSettings::BLUR_MASK;
}
if (type.is_moving) {
if (is_moving_) {
flags |= telegram_api::wallPaperSettings::MOTION_MASK;
}
switch (type.fill.get_type()) {
switch (fill_.get_type()) {
case BackgroundFill::Type::FreeformGradient:
if (fill_.fourth_color_ != -1) {
flags |= telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK;
}
flags |= telegram_api::wallPaperSettings::THIRD_BACKGROUND_COLOR_MASK;
// fallthrough
case BackgroundFill::Type::Gradient:
flags |= telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK;
// fallthrough
@ -294,12 +450,12 @@ telegram_api::object_ptr<telegram_api::wallPaperSettings> get_input_wallpaper_se
default:
UNREACHABLE();
}
if (type.intensity != 0) {
if (intensity_ != 0) {
flags |= telegram_api::wallPaperSettings::INTENSITY_MASK;
}
return telegram_api::make_object<telegram_api::wallPaperSettings>(flags, false /*ignored*/, false /*ignored*/,
type.fill.top_color, type.fill.bottom_color,
type.intensity, type.fill.rotation_angle);
return telegram_api::make_object<telegram_api::wallPaperSettings>(
flags, false /*ignored*/, false /*ignored*/, fill_.top_color_, fill_.bottom_color_, fill_.third_color_,
fill_.fourth_color_, intensity_, fill_.rotation_angle_);
}
} // namespace td

View File

@ -10,77 +10,121 @@
#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
namespace td {
struct BackgroundFill {
int32 top_color = 0;
int32 bottom_color = 0;
int32 rotation_angle = 0;
class BackgroundFill {
int32 top_color_ = 0;
int32 bottom_color_ = 0;
int32 rotation_angle_ = 0;
int32 third_color_ = -1;
int32 fourth_color_ = -1;
BackgroundFill() = default;
explicit BackgroundFill(int32 solid_color) : top_color(solid_color), bottom_color(solid_color) {
explicit BackgroundFill(int32 solid_color) : top_color_(solid_color), bottom_color_(solid_color) {
}
BackgroundFill(int32 top_color, int32 bottom_color, int32 rotation_angle)
: top_color(top_color), bottom_color(bottom_color), rotation_angle(rotation_angle) {
: top_color_(top_color), bottom_color_(bottom_color), rotation_angle_(rotation_angle) {
}
BackgroundFill(int32 first_color, int32 second_color, int32 third_color, int32 fourth_color)
: top_color_(first_color), bottom_color_(second_color), third_color_(third_color), fourth_color_(fourth_color) {
}
enum class Type : int32 { Solid, Gradient };
explicit BackgroundFill(const telegram_api::wallPaperSettings *settings);
static Result<BackgroundFill> get_background_fill(const td_api::BackgroundFill *fill);
string get_link(bool is_first) const;
td_api::object_ptr<td_api::BackgroundFill> get_background_fill_object() const;
enum class Type : int32 { Solid, Gradient, FreeformGradient };
Type get_type() const {
if (top_color == bottom_color) {
if (third_color_ != -1) {
return Type::FreeformGradient;
}
if (top_color_ == bottom_color_) {
return Type::Solid;
}
return Type::Gradient;
}
int64 get_id() const;
friend bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs);
static bool is_valid_id(int64 id);
friend class BackgroundType;
static bool is_valid_rotation_angle(int32 rotation_angle) {
return 0 <= rotation_angle && rotation_angle < 360 && rotation_angle % 45 == 0;
}
static Result<BackgroundFill> get_background_fill(Slice name);
bool is_dark() const;
};
bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs);
struct BackgroundType {
class BackgroundType {
enum class Type : int32 { Wallpaper, Pattern, Fill };
Type type = Type::Fill;
bool is_blurred = false;
bool is_moving = false;
int32 intensity = 0;
BackgroundFill fill;
Type type_ = Type::Fill;
bool is_blurred_ = false;
bool is_moving_ = false;
int32 intensity_ = 0;
BackgroundFill fill_;
friend bool operator==(const BackgroundType &lhs, const BackgroundType &rhs);
friend StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &type);
BackgroundType() = default;
BackgroundType(bool is_blurred, bool is_moving)
: type(Type::Wallpaper), is_blurred(is_blurred), is_moving(is_moving) {
: type_(Type::Wallpaper), is_blurred_(is_blurred), is_moving_(is_moving) {
}
BackgroundType(bool is_moving, const BackgroundFill &fill, int32 intensity)
: type(Type::Pattern), is_moving(is_moving), intensity(intensity), fill(fill) {
: type_(Type::Pattern), is_moving_(is_moving), intensity_(intensity), fill_(fill) {
}
explicit BackgroundType(BackgroundFill fill) : type(Type::Fill), fill(fill) {
explicit BackgroundType(BackgroundFill fill) : type_(Type::Fill), fill_(fill) {
}
bool is_server() const {
return type == Type::Wallpaper || type == Type::Pattern;
public:
BackgroundType() = default;
BackgroundType(bool is_fill, bool is_pattern, telegram_api::object_ptr<telegram_api::wallPaperSettings> settings);
static Result<BackgroundType> get_background_type(const td_api::BackgroundType *background_type);
static Result<BackgroundType> get_local_background_type(Slice name);
bool has_file() const {
return type_ == Type::Wallpaper || type_ == Type::Pattern;
}
string get_mime_type() const;
void apply_parameters_from_link(Slice name);
string get_link() const;
bool has_equal_type(const BackgroundType &other) const {
return type_ == other.type_;
}
td_api::object_ptr<td_api::BackgroundType> get_background_type_object() const;
telegram_api::object_ptr<telegram_api::wallPaperSettings> get_input_wallpaper_settings() const;
bool is_dark() const {
CHECK(type_ == Type::Fill);
return fill_.is_dark();
}
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
bool operator==(const BackgroundType &lhs, const BackgroundType &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &type);
Result<BackgroundType> get_background_type(const td_api::BackgroundType *type);
BackgroundType get_background_type(bool is_pattern, telegram_api::object_ptr<telegram_api::wallPaperSettings> settings);
td_api::object_ptr<td_api::BackgroundType> get_background_type_object(const BackgroundType &type);
telegram_api::object_ptr<telegram_api::wallPaperSettings> get_input_wallpaper_settings(const BackgroundType &type);
} // namespace td

View File

@ -13,55 +13,71 @@
namespace td {
template <class StorerT>
void store(const BackgroundType &type, StorerT &storer) {
bool has_fill = type.fill.top_color != 0 || type.fill.bottom_color != 0;
bool has_intensity = type.intensity != 0;
auto fill_type = type.fill.get_type();
void BackgroundType::store(StorerT &storer) const {
using td::store;
bool has_fill = fill_.top_color_ != 0 || fill_.bottom_color_ != 0;
bool has_intensity = intensity_ != 0;
auto fill_type = fill_.get_type();
bool is_gradient = fill_type == BackgroundFill::Type::Gradient;
bool is_freeform_gradient = fill_type == BackgroundFill::Type::FreeformGradient;
BEGIN_STORE_FLAGS();
STORE_FLAG(type.is_blurred);
STORE_FLAG(type.is_moving);
STORE_FLAG(is_blurred_);
STORE_FLAG(is_moving_);
STORE_FLAG(has_fill);
STORE_FLAG(has_intensity);
STORE_FLAG(is_gradient);
STORE_FLAG(is_freeform_gradient);
END_STORE_FLAGS();
store(type.type, storer);
if (has_fill) {
store(type.fill.top_color, storer);
store(type_, storer);
if (is_freeform_gradient) {
store(fill_.top_color_, storer);
store(fill_.bottom_color_, storer);
store(fill_.third_color_, storer);
store(fill_.fourth_color_, storer);
} else if (has_fill) {
store(fill_.top_color_, storer);
if (is_gradient) {
store(type.fill.bottom_color, storer);
store(type.fill.rotation_angle, storer);
store(fill_.bottom_color_, storer);
store(fill_.rotation_angle_, storer);
}
}
if (has_intensity) {
store(type.intensity, storer);
store(intensity_, storer);
}
}
template <class ParserT>
void parse(BackgroundType &type, ParserT &parser) {
void BackgroundType::parse(ParserT &parser) {
using td::parse;
bool has_fill;
bool has_intensity;
bool is_gradient;
bool is_freeform_gradient;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(type.is_blurred);
PARSE_FLAG(type.is_moving);
PARSE_FLAG(is_blurred_);
PARSE_FLAG(is_moving_);
PARSE_FLAG(has_fill);
PARSE_FLAG(has_intensity);
PARSE_FLAG(is_gradient);
PARSE_FLAG(is_freeform_gradient);
END_PARSE_FLAGS();
parse(type.type, parser);
if (has_fill) {
parse(type.fill.top_color, parser);
parse(type_, parser);
if (is_freeform_gradient) {
parse(fill_.top_color_, parser);
parse(fill_.bottom_color_, parser);
parse(fill_.third_color_, parser);
parse(fill_.fourth_color_, parser);
} else if (has_fill) {
parse(fill_.top_color_, parser);
if (is_gradient) {
parse(type.fill.bottom_color, parser);
parse(type.fill.rotation_angle, parser);
parse(fill_.bottom_color_, parser);
parse(fill_.rotation_angle_, parser);
} else {
type.fill.bottom_color = type.fill.top_color;
fill_.bottom_color_ = fill_.top_color_;
}
}
if (has_intensity) {
parse(type.intensity, parser);
parse(intensity_, parser);
}
}

228
td/telegram/BotCommand.cpp Normal file
View File

@ -0,0 +1,228 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/BotCommand.h"
#include "td/telegram/BotCommandScope.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/misc.h"
#include "td/telegram/Td.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"
#include "td/utils/utf8.h"
namespace td {
class SetBotCommandsQuery : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit SetBotCommandsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(BotCommandScope scope, const string &language_code, vector<BotCommand> &&commands) {
send_query(G()->net_query_creator().create(telegram_api::bots_setBotCommands(
scope.get_input_bot_command_scope(td), language_code,
transform(commands, [](const BotCommand &command) { return command.get_input_bot_command(); }))));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::bots_setBotCommands>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
if (!result_ptr.ok()) {
LOG(ERROR) << "Set bot commands request failed";
}
promise_.set_value(Unit());
}
void on_error(uint64 id, Status status) override {
promise_.set_error(std::move(status));
}
};
class ResetBotCommandsQuery : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit ResetBotCommandsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(BotCommandScope scope, const string &language_code) {
send_query(G()->net_query_creator().create(
telegram_api::bots_resetBotCommands(scope.get_input_bot_command_scope(td), language_code)));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::bots_resetBotCommands>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
promise_.set_value(Unit());
}
void on_error(uint64 id, Status status) override {
promise_.set_error(std::move(status));
}
};
class GetBotCommandsQuery : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::botCommands>> promise_;
public:
explicit GetBotCommandsQuery(Promise<td_api::object_ptr<td_api::botCommands>> &&promise)
: promise_(std::move(promise)) {
}
void send(BotCommandScope scope, const string &language_code) {
send_query(G()->net_query_creator().create(
telegram_api::bots_getBotCommands(scope.get_input_bot_command_scope(td), language_code)));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::bots_getBotCommands>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
BotCommands commands(td->contacts_manager_->get_my_id(), result_ptr.move_as_ok());
promise_.set_value(commands.get_bot_commands_object(td));
}
void on_error(uint64 id, Status status) override {
promise_.set_error(std::move(status));
}
};
BotCommand::BotCommand(telegram_api::object_ptr<telegram_api::botCommand> &&bot_command) {
CHECK(bot_command != nullptr);
command_ = std::move(bot_command->command_);
description_ = std::move(bot_command->description_);
}
td_api::object_ptr<td_api::botCommand> BotCommand::get_bot_command_object() const {
return td_api::make_object<td_api::botCommand>(command_, description_);
}
telegram_api::object_ptr<telegram_api::botCommand> BotCommand::get_input_bot_command() const {
return telegram_api::make_object<telegram_api::botCommand>(command_, description_);
}
bool operator==(const BotCommand &lhs, const BotCommand &rhs) {
return lhs.command_ == rhs.command_ && lhs.description_ == rhs.description_;
}
BotCommands::BotCommands(UserId bot_user_id, vector<telegram_api::object_ptr<telegram_api::botCommand>> &&bot_commands)
: bot_user_id_(bot_user_id) {
commands_ = transform(std::move(bot_commands), [](telegram_api::object_ptr<telegram_api::botCommand> &&bot_command) {
return BotCommand(std::move(bot_command));
});
}
td_api::object_ptr<td_api::botCommands> BotCommands::get_bot_commands_object(Td *td) const {
auto commands = transform(commands_, [](const auto &command) { return command.get_bot_command_object(); });
return td_api::make_object<td_api::botCommands>(
td->contacts_manager_->get_user_id_object(bot_user_id_, "get_bot_commands_object"), std::move(commands));
}
bool operator==(const BotCommands &lhs, const BotCommands &rhs) {
return lhs.bot_user_id_ == rhs.bot_user_id_ && lhs.commands_ == rhs.commands_;
}
static bool is_valid_language_code(const string &language_code) {
if (language_code.empty()) {
return true;
}
if (language_code.size() != 2) {
return false;
}
return 'a' <= language_code[0] && language_code[0] <= 'z' && 'a' <= language_code[1] && language_code[1] <= 'z';
}
void set_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
vector<td_api::object_ptr<td_api::botCommand>> &&commands, Promise<Unit> &&promise) {
TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
if (!is_valid_language_code(language_code)) {
return promise.set_error(Status::Error(400, "Invalid language code specified"));
}
vector<BotCommand> new_commands;
for (auto &command : commands) {
if (command == nullptr) {
return promise.set_error(Status::Error(400, "Command must be non-empty"));
}
if (!clean_input_string(command->command_)) {
return promise.set_error(Status::Error(400, "Command must be encoded in UTF-8"));
}
if (!clean_input_string(command->description_)) {
return promise.set_error(Status::Error(400, "Command description must be encoded in UTF-8"));
}
const size_t MAX_COMMAND_TEXT_LENGTH = 32;
command->command_ = trim(command->command_);
if (command->command_[0] == '/') {
command->command_ = command->command_.substr(1);
}
if (command->command_.empty()) {
return promise.set_error(Status::Error(400, "Command must be non-empty"));
}
if (utf8_length(command->command_) > MAX_COMMAND_TEXT_LENGTH) {
return promise.set_error(
Status::Error(400, PSLICE() << "Command length must not exceed " << MAX_COMMAND_TEXT_LENGTH));
}
const size_t MIN_COMMAND_DESCRIPTION_LENGTH = 3;
const size_t MAX_COMMAND_DESCRIPTION_LENGTH = 256;
command->description_ = trim(command->description_);
auto description_length = utf8_length(command->description_);
if (description_length < MIN_COMMAND_DESCRIPTION_LENGTH) {
return promise.set_error(Status::Error(
400, PSLICE() << "Command description length must be at least " << MIN_COMMAND_DESCRIPTION_LENGTH));
}
if (description_length > MAX_COMMAND_DESCRIPTION_LENGTH) {
return promise.set_error(Status::Error(
400, PSLICE() << "Command description length must not exceed " << MAX_COMMAND_DESCRIPTION_LENGTH));
}
new_commands.emplace_back(std::move(command->command_), std::move(command->description_));
}
td->create_handler<SetBotCommandsQuery>(std::move(promise))->send(scope, language_code, std::move(new_commands));
}
void delete_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
Promise<Unit> &&promise) {
TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
if (!is_valid_language_code(language_code)) {
return promise.set_error(Status::Error(400, "Invalid language code specified"));
}
td->create_handler<ResetBotCommandsQuery>(std::move(promise))->send(scope, language_code);
}
void get_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
Promise<td_api::object_ptr<td_api::botCommands>> &&promise) {
TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
if (!is_valid_language_code(language_code)) {
return promise.set_error(Status::Error(400, "Invalid language code specified"));
}
td->create_handler<GetBotCommandsQuery>(std::move(promise))->send(scope, language_code);
}
} // namespace td

101
td/telegram/BotCommand.h Normal file
View File

@ -0,0 +1,101 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
class Td;
class BotCommand {
string command_;
string description_;
friend bool operator==(const BotCommand &lhs, const BotCommand &rhs);
public:
BotCommand() = default;
BotCommand(string command, string description) : command_(std::move(command)), description_(std::move(description)) {
}
explicit BotCommand(telegram_api::object_ptr<telegram_api::botCommand> &&bot_command);
td_api::object_ptr<td_api::botCommand> get_bot_command_object() const;
telegram_api::object_ptr<telegram_api::botCommand> get_input_bot_command() const;
template <class StorerT>
void store(StorerT &storer) const {
td::store(command_, storer);
td::store(description_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(command_, parser);
td::parse(description_, parser);
}
};
bool operator==(const BotCommand &lhs, const BotCommand &rhs);
inline bool operator!=(const BotCommand &lhs, const BotCommand &rhs) {
return !(lhs == rhs);
}
class BotCommands {
UserId bot_user_id_;
vector<BotCommand> commands_;
friend bool operator==(const BotCommands &lhs, const BotCommands &rhs);
public:
BotCommands() = default;
BotCommands(UserId bot_user_id, vector<telegram_api::object_ptr<telegram_api::botCommand>> &&bot_commands);
td_api::object_ptr<td_api::botCommands> get_bot_commands_object(Td *td) const;
UserId get_bot_user_id() const {
return bot_user_id_;
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(bot_user_id_, storer);
td::store(commands_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(bot_user_id_, parser);
td::parse(commands_, parser);
}
};
bool operator==(const BotCommands &lhs, const BotCommands &rhs);
inline bool operator!=(const BotCommands &lhs, const BotCommands &rhs) {
return !(lhs == rhs);
}
void set_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
vector<td_api::object_ptr<td_api::botCommand>> &&commands, Promise<Unit> &&promise);
void delete_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
Promise<Unit> &&promise);
void get_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
Promise<td_api::object_ptr<td_api::botCommands>> &&promise);
} // namespace td

View File

@ -0,0 +1,130 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/BotCommandScope.h"
#include "td/telegram/AccessRights.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/Td.h"
namespace td {
BotCommandScope::BotCommandScope(Type type, DialogId dialog_id, UserId user_id)
: type_(type), dialog_id_(dialog_id), user_id_(user_id) {
}
Result<BotCommandScope> BotCommandScope::get_bot_command_scope(Td *td,
td_api::object_ptr<td_api::BotCommandScope> scope_ptr) {
if (scope_ptr == nullptr) {
return BotCommandScope(Type::Default);
}
CHECK(td->auth_manager_->is_bot());
Type type;
DialogId dialog_id;
UserId user_id;
switch (scope_ptr->get_id()) {
case td_api::botCommandScopeDefault::ID:
return BotCommandScope(Type::Default);
case td_api::botCommandScopeAllPrivateChats::ID:
return BotCommandScope(Type::AllUsers);
case td_api::botCommandScopeAllGroupChats::ID:
return BotCommandScope(Type::AllChats);
case td_api::botCommandScopeAllChatAdministrators::ID:
return BotCommandScope(Type::AllChatAdministrators);
case td_api::botCommandScopeChat::ID: {
auto scope = td_api::move_object_as<td_api::botCommandScopeChat>(scope_ptr);
type = Type::Dialog;
dialog_id = DialogId(scope->chat_id_);
break;
}
case td_api::botCommandScopeChatAdministrators::ID: {
auto scope = td_api::move_object_as<td_api::botCommandScopeChatAdministrators>(scope_ptr);
type = Type::DialogAdministrators;
dialog_id = DialogId(scope->chat_id_);
break;
}
case td_api::botCommandScopeChatMember::ID: {
auto scope = td_api::move_object_as<td_api::botCommandScopeChatMember>(scope_ptr);
type = Type::DialogParticipant;
dialog_id = DialogId(scope->chat_id_);
user_id = UserId(scope->user_id_);
if (!user_id.is_valid()) {
return Status::Error(400, "User not found");
}
if (!td->contacts_manager_->have_input_user(user_id)) {
return Status::Error(400, "Can't access the user");
}
break;
}
default:
UNREACHABLE();
return BotCommandScope(Type::Default);
}
if (!td->messages_manager_->have_dialog_force(dialog_id, "get_bot_command_scope")) {
return Status::Error(400, "Chat not found");
}
if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
return Status::Error(400, "Can't access the chat");
}
switch (dialog_id.get_type()) {
case DialogType::User:
if (type != Type::Dialog) {
return Status::Error(400, "Can't use specified scope in private chats");
}
break;
case DialogType::Chat:
// ok
break;
case DialogType::Channel:
if (td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) !=
ContactsManager::ChannelType::Megagroup) {
return Status::Error(400, "Can't change commands in channel chats");
}
break;
case DialogType::SecretChat:
default:
return Status::Error(400, "Can't access the chat");
}
return BotCommandScope(type, dialog_id, user_id);
}
telegram_api::object_ptr<telegram_api::BotCommandScope> BotCommandScope::get_input_bot_command_scope(
const Td *td) const {
auto input_peer =
dialog_id_.is_valid() ? td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read) : nullptr;
auto input_user = user_id_.is_valid() ? td->contacts_manager_->get_input_user(user_id_) : nullptr;
switch (type_) {
case Type::Default:
return telegram_api::make_object<telegram_api::botCommandScopeDefault>();
case Type::AllUsers:
return telegram_api::make_object<telegram_api::botCommandScopeUsers>();
case Type::AllChats:
return telegram_api::make_object<telegram_api::botCommandScopeChats>();
case Type::AllChatAdministrators:
return telegram_api::make_object<telegram_api::botCommandScopeChatAdmins>();
case Type::Dialog:
CHECK(input_peer != nullptr);
return telegram_api::make_object<telegram_api::botCommandScopePeer>(std::move(input_peer));
case Type::DialogAdministrators:
CHECK(input_peer != nullptr);
return telegram_api::make_object<telegram_api::botCommandScopePeerAdmins>(std::move(input_peer));
case Type::DialogParticipant:
CHECK(input_peer != nullptr);
CHECK(input_user != nullptr);
return telegram_api::make_object<telegram_api::botCommandScopePeerUser>(std::move(input_peer),
std::move(input_user));
default:
UNREACHABLE();
return nullptr;
}
}
} // namespace td

View File

@ -0,0 +1,43 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/DialogId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/common.h"
#include "td/utils/Status.h"
namespace td {
class Td;
class BotCommandScope {
enum class Type : int32 {
Default,
AllUsers,
AllChats,
AllChatAdministrators,
Dialog,
DialogAdministrators,
DialogParticipant
};
Type type_ = Type::Default;
DialogId dialog_id_;
UserId user_id_;
explicit BotCommandScope(Type type, DialogId dialog_id = DialogId(), UserId user_id = UserId());
public:
static Result<BotCommandScope> get_bot_command_scope(Td *td, td_api::object_ptr<td_api::BotCommandScope> scope_ptr);
telegram_api::object_ptr<telegram_api::BotCommandScope> get_input_bot_command_scope(const Td *td) const;
};
} // namespace td

View File

@ -22,6 +22,7 @@
#include "td/utils/MpscPollableQueue.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/port/thread.h"
#include "td/utils/Slice.h"
#include <algorithm>
#include <atomic>

View File

@ -253,7 +253,7 @@ class ClientManager final {
* None of the TDLib methods can be called from the callback.
* By default the callback is not set.
*
* \param[in] max_verbosity_level Maximum verbosity level of messages for which the callback will be called.
* \param[in] max_verbosity_level The maximum verbosity level of messages for which the callback will be called.
* \param[in] callback Callback that will be called when a message is added to the internal TDLib log.
* Pass nullptr to remove the callback.
*/

View File

@ -121,7 +121,7 @@ public:
/// Sets the callback that will be called when a message is added to the internal TDLib log.
/// None of the TDLib methods can be called from the callback.
/// </summary>
/// <param name="max_verbosity_level">Maximum verbosity level of messages for which the callback will be called.</param>
/// <param name="max_verbosity_level">The maximum verbosity level of messages for which the callback will be called.</param>
/// <param name="callback">Callback that will be called when a message is added to the internal TDLib log.
/// Pass null to remove the callback.</param>
static void SetLogMessageCallback(std::int32_t max_verbosity_level, LogMessageCallback^ callback) {

View File

@ -10,8 +10,8 @@
#include "td/telegram/ConfigShared.h"
#include "td/telegram/Global.h"
#include "td/telegram/JsonValue.h"
#include "td/telegram/LinkManager.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/net/AuthDataShared.h"
#include "td/telegram/net/ConnectionCreator.h"
#include "td/telegram/net/DcId.h"
@ -32,12 +32,13 @@
#include "td/mtproto/RSA.h"
#include "td/mtproto/TransportType.h"
#include "td/net/HttpQuery.h"
#if !TD_EMSCRIPTEN //FIXME
#include "td/net/SslStream.h"
#include "td/net/Wget.h"
#endif
#include "td/net/HttpQuery.h"
#include "td/actor/actor.h"
#include "td/utils/algorithm.h"
@ -46,7 +47,6 @@
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
@ -890,11 +890,6 @@ void ConfigManager::start_up() {
expire_time_ = expire_time;
set_timeout_in(expire_time_.in());
}
autologin_update_time_ = Time::now() - 365 * 86400;
autologin_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("autologin_domains"), '\xFF');
url_auth_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("url_auth_domains"), '\xFF');
}
ActorShared<> ConfigManager::create_reference() {
@ -971,65 +966,6 @@ void ConfigManager::get_app_config(Promise<td_api::object_ptr<td_api::JsonValue>
}
}
void ConfigManager::get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
auto default_result = td_api::make_object<td_api::loginUrlInfoOpen>(link, false);
if (G()->close_flag()) {
return promise.set_value(std::move(default_result));
}
auto r_url = parse_url(link);
if (r_url.is_error()) {
return promise.set_value(std::move(default_result));
}
if (!td::contains(autologin_domains_, r_url.ok().host_)) {
if (td::contains(url_auth_domains_, r_url.ok().host_)) {
send_closure(G()->messages_manager(), &MessagesManager::get_link_login_url_info, link, std::move(promise));
return;
}
return promise.set_value(std::move(default_result));
}
if (autologin_update_time_ < Time::now() - 10000) {
auto query_promise = PromiseCreator::lambda([link = std::move(link), promise = std::move(promise)](
Result<td_api::object_ptr<td_api::JsonValue>> &&result) mutable {
if (result.is_error()) {
return promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(link, false));
}
send_closure(G()->config_manager(), &ConfigManager::get_external_link_info, std::move(link), std::move(promise));
});
return get_app_config(std::move(query_promise));
}
if (autologin_token_.empty()) {
return promise.set_value(std::move(default_result));
}
auto url = r_url.move_as_ok();
url.protocol_ = HttpUrl::Protocol::Https;
Slice path = url.query_;
path.truncate(url.query_.find_first_of("?#"));
Slice parameters_hash = Slice(url.query_).substr(path.size());
Slice parameters = parameters_hash;
parameters.truncate(parameters.find('#'));
Slice hash = parameters_hash.substr(parameters.size());
string added_parameter;
if (parameters.empty()) {
added_parameter = '?';
} else if (parameters.size() == 1) {
CHECK(parameters == "?");
} else {
added_parameter = '&';
}
added_parameter += "autologin_token=";
added_parameter += autologin_token_;
url.query_ = PSTRING() << path << parameters << added_parameter << hash;
promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url.get_url(), false));
}
void ConfigManager::get_content_settings(Promise<Unit> &&promise) {
if (G()->close_flag()) {
return promise.set_error(Status::Error(500, "Request aborted"));
@ -1139,6 +1075,10 @@ void ConfigManager::do_set_archive_and_mute(bool archive_and_mute) {
G()->shared_config().set_option_boolean("archive_and_mute_new_chats_from_unknown_users", archive_and_mute);
}
void ConfigManager::hide_suggested_action(SuggestedAction suggested_action) {
remove_suggested_action(suggested_actions_, suggested_action);
}
void ConfigManager::dismiss_suggested_action(SuggestedAction suggested_action, Promise<Unit> &&promise) {
auto action_str = suggested_action.get_suggested_action_str();
if (action_str.empty()) {
@ -1528,13 +1468,9 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
const bool archive_and_mute =
G()->shared_config().get_option_boolean("archive_and_mute_new_chats_from_unknown_users");
autologin_token_.clear();
auto old_autologin_domains = std::move(autologin_domains_);
autologin_domains_.clear();
autologin_update_time_ = Time::now();
auto old_url_auth_domains = std::move(url_auth_domains_);
url_auth_domains_.clear();
string autologin_token;
vector<string> autologin_domains;
vector<string> url_auth_domains;
vector<tl_object_ptr<telegram_api::jsonObjectValue>> new_values;
string ignored_restriction_reasons;
@ -1704,7 +1640,7 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
}
if (key == "autologin_token") {
if (value->get_id() == telegram_api::jsonString::ID) {
autologin_token_ = url_encode(static_cast<telegram_api::jsonString *>(value)->value_);
autologin_token = url_encode(static_cast<telegram_api::jsonString *>(value)->value_);
} else {
LOG(ERROR) << "Receive unexpected autologin_token " << to_string(*value);
}
@ -1716,7 +1652,7 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
for (auto &domain : domains) {
CHECK(domain != nullptr);
if (domain->get_id() == telegram_api::jsonString::ID) {
autologin_domains_.push_back(std::move(static_cast<telegram_api::jsonString *>(domain.get())->value_));
autologin_domains.push_back(std::move(static_cast<telegram_api::jsonString *>(domain.get())->value_));
} else {
LOG(ERROR) << "Receive unexpected autologin domain " << to_string(domain);
}
@ -1732,7 +1668,7 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
for (auto &domain : domains) {
CHECK(domain != nullptr);
if (domain->get_id() == telegram_api::jsonString::ID) {
url_auth_domains_.push_back(std::move(static_cast<telegram_api::jsonString *>(domain.get())->value_));
url_auth_domains.push_back(std::move(static_cast<telegram_api::jsonString *>(domain.get())->value_));
} else {
LOG(ERROR) << "Receive unexpected url auth domain " << to_string(domain);
}
@ -1750,12 +1686,8 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
}
config = make_tl_object<telegram_api::jsonObject>(std::move(new_values));
if (autologin_domains_ != old_autologin_domains) {
G()->td_db()->get_binlog_pmc()->set("autologin_domains", implode(autologin_domains_, '\xFF'));
}
if (url_auth_domains_ != old_url_auth_domains) {
G()->td_db()->get_binlog_pmc()->set("url_auth_domains", implode(url_auth_domains_, '\xFF'));
}
send_closure(G()->link_manager(), &LinkManager::update_autologin_domains, std::move(autologin_token),
std::move(autologin_domains), std::move(url_auth_domains));
ConfigShared &shared_config = G()->shared_config();

View File

@ -93,8 +93,6 @@ class ConfigManager : public NetQueryCallback {
void get_app_config(Promise<td_api::object_ptr<td_api::JsonValue>> &&promise);
void get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_content_settings(Promise<Unit> &&promise);
void set_content_settings(bool ignore_sensitive_content_restrictions, Promise<Unit> &&promise);
@ -103,6 +101,8 @@ class ConfigManager : public NetQueryCallback {
void set_archive_and_mute(bool archive_and_mute, Promise<Unit> &&promise);
void hide_suggested_action(SuggestedAction suggested_action);
void dismiss_suggested_action(SuggestedAction suggested_action, Promise<Unit> &&promise);
void on_dc_options_update(DcOptions dc_options);
@ -116,11 +116,6 @@ class ConfigManager : public NetQueryCallback {
int ref_cnt_{1};
Timestamp expire_time_;
string autologin_token_;
vector<string> autologin_domains_;
double autologin_update_time_ = 0.0;
vector<string> url_auth_domains_;
FloodControlStrict lazy_request_flood_control_;
vector<Promise<td_api::object_ptr<td_api::JsonValue>>> get_app_config_queries_;

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
#include "td/telegram/telegram_api.h"
#include "td/telegram/AccessRights.h"
#include "td/telegram/BotCommand.h"
#include "td/telegram/ChannelId.h"
#include "td/telegram/ChatId.h"
#include "td/telegram/Contact.h"
@ -55,29 +56,8 @@ namespace td {
struct BinlogEvent;
class DialogInviteLink;
class DialogLocation;
class Td;
struct BotData {
string username;
bool can_join_groups;
bool can_read_all_group_messages;
bool is_inline;
bool need_location;
};
enum class ChannelType : uint8 { Broadcast, Megagroup, Unknown };
enum class CheckDialogUsernameResult : uint8 { Ok, Invalid, Occupied, PublicDialogsTooMuch, PublicGroupsUnavailable };
struct CanTransferOwnershipResult {
enum class Type : uint8 { Ok, PasswordNeeded, PasswordTooFresh, SessionTooFresh };
Type type = Type::Ok;
int32 retry_after = 0;
};
class ContactsManager : public Actor {
public:
ContactsManager(Td *td, ActorShared<> parent);
@ -185,7 +165,6 @@ class ContactsManager : public Actor {
void on_get_channel_full_failed(ChannelId channel_id);
void on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about);
void on_set_bot_commands_success(vector<std::pair<string, string>> &&commands);
void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, string &&username);
void on_update_user_phone_number(UserId user_id, string &&phone_number);
@ -288,7 +267,7 @@ class ContactsManager : public Actor {
void invalidate_user_full(UserId user_id);
void on_channel_unban_timeout(ChannelId channel_id);
enum class CheckDialogUsernameResult : uint8 { Ok, Invalid, Occupied, PublicDialogsTooMuch, PublicGroupsUnavailable };
void check_dialog_username(DialogId dialog_id, const string &username, Promise<CheckDialogUsernameResult> &&promise);
@ -354,8 +333,6 @@ class ContactsManager : public Actor {
void set_username(const string &username, Promise<Unit> &&promise);
void set_commands(vector<td_api::object_ptr<td_api::botCommand>> &&commands, Promise<Unit> &&promise);
void set_chat_description(ChatId chat_id, const string &description, Promise<Unit> &&promise);
void set_channel_username(ChannelId channel_id, const string &username, Promise<Unit> &&promise);
@ -393,6 +370,11 @@ class ContactsManager : public Actor {
void load_statistics_graph(DialogId dialog_id, const string &token, int64 x,
Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise);
struct CanTransferOwnershipResult {
enum class Type : uint8 { Ok, PasswordNeeded, PasswordTooFresh, SessionTooFresh };
Type type = Type::Ok;
int32 retry_after = 0;
};
void can_transfer_ownership(Promise<CanTransferOwnershipResult> &&promise);
static td_api::object_ptr<td_api::CanTransferOwnershipResult> get_can_transfer_ownership_result_object(
@ -450,6 +432,14 @@ class ContactsManager : public Actor {
bool is_user_support(UserId user_id) const;
bool is_user_bot(UserId user_id) const;
struct BotData {
string username;
bool can_join_groups;
bool can_read_all_group_messages;
bool is_inline;
bool need_location;
};
Result<BotData> get_bot_data(UserId user_id) const TD_WARN_UNUSED_RESULT;
bool is_user_online(UserId user_id, int32 tolerance = 0) const;
@ -508,6 +498,8 @@ class ContactsManager : public Actor {
bool get_secret_chat(SecretChatId secret_chat_id, bool force, Promise<Unit> &&promise);
bool get_secret_chat_full(SecretChatId secret_chat_id, Promise<Unit> &&promise);
enum class ChannelType : uint8 { Broadcast, Megagroup, Unknown };
ChannelType get_channel_type(ChannelId channel_id) const;
int32 get_channel_date(ChannelId channel_id) const;
DialogParticipantStatus get_channel_status(ChannelId channel_id) const;
@ -535,13 +527,13 @@ class ContactsManager : public Actor {
bool force, Promise<Unit> &&promise);
void search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, DialogParticipantsFilter filter,
bool without_bot_info, Promise<DialogParticipants> &&promise);
Promise<DialogParticipants> &&promise);
vector<DialogAdministrator> get_dialog_administrators(DialogId dialog_id, int left_tries, Promise<Unit> &&promise);
void get_channel_participants(ChannelId channel_id, tl_object_ptr<td_api::SupergroupMembersFilter> &&filter,
string additional_query, int32 offset, int32 limit, int32 additional_limit,
bool without_bot_info, Promise<DialogParticipants> &&promise);
Promise<DialogParticipants> &&promise);
int32 get_user_id_object(UserId user_id, const char *source) const;
@ -682,6 +674,9 @@ class ContactsManager : public Actor {
Photo photo;
string about;
string description;
vector<BotCommand> commands;
int32 common_chat_count = 0;
@ -766,6 +761,8 @@ class ContactsManager : public Actor {
DialogInviteLink invite_link;
vector<BotCommands> bot_commands;
bool can_set_username = false;
bool is_changed = true; // have new changes that need to be sent to the client and database
@ -845,6 +842,8 @@ class ContactsManager : public Actor {
DialogInviteLink invite_link;
vector<BotCommands> bot_commands;
uint32 speculative_version = 1;
uint32 repair_request_version = 0;
@ -919,19 +918,6 @@ class ContactsManager : public Actor {
void parse(ParserT &parser);
};
struct BotInfo {
int32 version = -1;
string description;
vector<std::pair<string, string>> commands;
bool is_changed = true;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
struct InviteLinkInfo {
// known dialog
DialogId dialog_id;
@ -985,6 +971,8 @@ class ContactsManager : public Actor {
static constexpr size_t MAX_BIO_LENGTH = 70; // server side limit
static constexpr int32 MAX_GET_CHANNEL_PARTICIPANTS = 200; // server side limit
static constexpr int32 CHANNEL_PARTICIPANT_CACHE_TIME = 1800; // some reasonable limit
static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0;
static constexpr int32 USER_FLAG_HAS_FIRST_NAME = 1 << 1;
static constexpr int32 USER_FLAG_HAS_LAST_NAME = 1 << 2;
@ -1124,12 +1112,6 @@ class ContactsManager : public Actor {
void send_get_user_full_query(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
Promise<Unit> &&promise, const char *source);
const BotInfo *get_bot_info(UserId user_id) const;
BotInfo *get_bot_info(UserId user_id);
BotInfo *get_bot_info_force(UserId user_id, bool send_update = true);
BotInfo *add_bot_info(UserId user_id);
const Chat *get_chat(ChatId chat_id) const;
Chat *get_chat(ChatId chat_id);
Chat *get_chat_force(ChatId chat_id);
@ -1179,9 +1161,6 @@ class ContactsManager : public Actor {
static bool is_valid_username(const string &username);
bool on_update_bot_info(tl_object_ptr<telegram_api::botInfo> &&new_bot_info, bool send_update = true);
bool is_bot_info_expired(UserId user_id, int32 bot_info_version);
void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name, string &&username);
void on_update_user_phone_number(User *u, UserId user_id, string &&phone_number);
void on_update_user_photo(User *u, UserId user_id, tl_object_ptr<telegram_api::UserProfilePhoto> &&photo,
@ -1205,6 +1184,7 @@ class ContactsManager : public Actor {
void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked);
void on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count);
void on_update_user_full_commands(UserFull *user_full, UserId user_id, vector<BotCommand> &&commands);
void on_update_user_full_need_phone_number_privacy_exception(UserFull *user_full, UserId user_id,
bool need_phone_number_privacy_exception);
@ -1329,11 +1309,6 @@ class ContactsManager : public Actor {
static string get_user_full_database_value(const UserFull *user_full);
void on_load_user_full_from_database(UserId user_id, string value);
void save_bot_info(const BotInfo *bot_info, UserId user_id);
static string get_bot_info_database_key(UserId user_id);
static string get_bot_info_database_value(const BotInfo *bot_info);
void on_load_bot_info_from_database(UserId user_id, string value, bool send_update);
void save_chat_full(const ChatFull *chat_full, ChatId chat_id);
static string get_chat_full_database_key(ChatId chat_id);
static string get_chat_full_database_value(const ChatFull *chat_full);
@ -1354,8 +1329,6 @@ class ContactsManager : public Actor {
void update_chat_full(ChatFull *chat_full, ChatId chat_id, bool from_database = false);
void update_channel_full(ChannelFull *channel_full, ChannelId channel_id, bool from_database = false);
void update_bot_info(BotInfo *bot_info, UserId user_id, bool send_update, bool from_database);
bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id);
bool is_user_contact(const User *u, UserId user_id, bool is_mutual) const;
@ -1423,6 +1396,9 @@ class ContactsManager : public Actor {
void add_channel_participants(ChannelId channel_id, const vector<UserId> &user_ids, Promise<Unit> &&promise);
vector<BotCommands> get_bot_commands(vector<tl_object_ptr<telegram_api::botInfo>> &&bot_infos,
const vector<DialogParticipant> *participants);
const DialogParticipant *get_chat_participant(ChatId chat_id, UserId user_id) const;
static const DialogParticipant *get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id);
@ -1457,8 +1433,6 @@ class ContactsManager : public Actor {
td_api::object_ptr<td_api::UserStatus> get_user_status_object(UserId user_id, const User *u) const;
td_api::object_ptr<td_api::botInfo> get_bot_info_object(UserId user_id) const;
tl_object_ptr<td_api::user> get_user_object(UserId user_id, const User *u) const;
tl_object_ptr<td_api::userFullInfo> get_user_full_info_object(UserId user_id, const UserFull *user_full) const;
@ -1501,21 +1475,28 @@ class ContactsManager : public Actor {
void delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise<Unit> &&promise);
void on_get_channel_participant(ChannelId channel_id, int64 random_id, Result<DialogParticipant> r_dialog_participant,
Promise<Unit> &&promise);
void search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantsFilter filter,
Promise<DialogParticipants> &&promise);
void do_search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantsFilter filter,
Promise<DialogParticipants> &&promise);
void do_get_channel_participants(ChannelId channel_id, ChannelParticipantsFilter &&filter, string additional_query,
int32 offset, int32 limit, int32 additional_limit,
Promise<DialogParticipants> &&promise);
void on_get_channel_participants(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit,
string additional_query, int32 additional_limit,
tl_object_ptr<telegram_api::channels_channelParticipants> &&channel_participants,
Promise<DialogParticipants> &&promise);
bool have_channel_participant_cache(ChannelId channel_id) const;
void add_channel_participant_to_cache(ChannelId channel_id, const DialogParticipant &dialog_participant,
bool allow_replace);
const DialogParticipant *get_channel_participant_from_cache(ChannelId channel_id,
DialogId participant_dialog_id) const;
void change_channel_participant_status_impl(ChannelId channel_id, DialogId participant_dialog_id,
DialogParticipantStatus status, DialogParticipantStatus old_status,
Promise<Unit> &&promise);
@ -1558,14 +1539,20 @@ class ContactsManager : public Actor {
static void on_invite_link_info_expire_timeout_callback(void *contacts_manager_ptr, int64 dialog_id_long);
static void on_channel_participant_cache_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long);
void on_user_online_timeout(UserId user_id);
void on_channel_unban_timeout(ChannelId channel_id);
void on_user_nearby_timeout(UserId user_id);
void on_slow_mode_delay_timeout(ChannelId channel_id);
void on_invite_link_info_expire_timeout(DialogId dialog_id);
void on_channel_participant_cache_timeout(ChannelId channel_id);
void tear_down() override;
Td *td_;
@ -1576,7 +1563,6 @@ class ContactsManager : public Actor {
std::unordered_map<UserId, unique_ptr<User>, UserIdHash> users_;
std::unordered_map<UserId, unique_ptr<UserFull>, UserIdHash> users_full_;
std::unordered_map<UserId, unique_ptr<BotInfo>, UserIdHash> bot_infos_;
std::unordered_map<UserId, UserPhotos, UserIdHash> user_photos_;
mutable std::unordered_set<UserId, UserIdHash> unknown_users_;
std::unordered_map<UserId, tl_object_ptr<telegram_api::UserProfilePhoto>, UserIdHash> pending_user_photos_;
@ -1624,7 +1610,6 @@ class ContactsManager : public Actor {
std::unordered_map<UserId, vector<Promise<Unit>>, UserIdHash> load_user_from_database_queries_;
std::unordered_set<UserId, UserIdHash> loaded_from_database_users_;
std::unordered_set<UserId, UserIdHash> unavailable_user_fulls_;
std::unordered_set<UserId, UserIdHash> unavailable_bot_infos_;
std::unordered_map<ChatId, vector<Promise<Unit>>, ChatIdHash> load_chat_from_database_queries_;
std::unordered_set<ChatId, ChatIdHash> loaded_from_database_chats_;
@ -1670,6 +1655,17 @@ class ContactsManager : public Actor {
std::unordered_map<ChannelId, vector<DialogParticipant>, ChannelIdHash> cached_channel_participants_;
// bot-administrators only
struct ChannelParticipantInfo {
DialogParticipant participant_;
mutable int32 last_access_date_ = 0;
};
struct ChannelParticipants {
std::unordered_map<DialogId, ChannelParticipantInfo, DialogIdHash> participants_;
};
std::unordered_map<ChannelId, ChannelParticipants, ChannelIdHash> channel_participants_;
bool are_contacts_loaded_ = false;
int32 next_contacts_sync_date_ = 0;
Hints contacts_hints_; // search contacts by first name, last name and username
@ -1713,6 +1709,7 @@ class ContactsManager : public Actor {
MultiTimeout user_nearby_timeout_{"UserNearbyTimeout"};
MultiTimeout slow_mode_delay_timeout_{"SlowModeDelayTimeout"};
MultiTimeout invite_link_info_expire_timeout_{"InviteLinkInfoExpireTimeout"};
MultiTimeout channel_participant_cache_timeout_{"ChannelParticipantCacheTimeout"};
};
} // namespace td

View File

@ -262,7 +262,7 @@ tl_object_ptr<td_api::ChatAction> DialogAction::get_chat_action_object() const {
}
}
bool DialogAction::is_cancelled_by_message_of_type(MessageContentType message_content_type) const {
bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_content_type) const {
if (message_content_type == MessageContentType::None) {
return true;
}

View File

@ -56,7 +56,7 @@ class DialogAction {
td_api::object_ptr<td_api::ChatAction> get_chat_action_object() const;
bool is_cancelled_by_message_of_type(MessageContentType message_content_type) const;
bool is_canceled_by_message_of_type(MessageContentType message_content_type) const;
static DialogAction get_uploading_action(MessageContentType message_content_type, int32 progress);

View File

@ -7,18 +7,12 @@
#include "td/telegram/DialogInviteLink.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/LinkManager.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
namespace td {
const CSlice DialogInviteLink::INVITE_LINK_URLS[12] = {
"t.me/joinchat/", "telegram.me/joinchat/", "telegram.dog/joinchat/",
"t.me/+", "telegram.me/+", "telegram.dog/+",
"t.me/ ", "telegram.me/ ", "telegram.dog/ ",
"t.me/%20", "telegram.me/%20", "telegram.dog/%20"};
DialogInviteLink::DialogInviteLink(tl_object_ptr<telegram_api::chatInviteExported> exported_invite) {
if (exported_invite == nullptr) {
return;
@ -76,29 +70,7 @@ DialogInviteLink::DialogInviteLink(tl_object_ptr<telegram_api::chatInviteExporte
}
bool DialogInviteLink::is_valid_invite_link(Slice invite_link) {
return !get_dialog_invite_link_hash(invite_link).empty();
}
Slice DialogInviteLink::get_dialog_invite_link_hash(Slice invite_link) {
auto lower_cased_invite_link_str = to_lower(invite_link);
Slice lower_cased_invite_link = lower_cased_invite_link_str;
size_t offset = 0;
if (begins_with(lower_cased_invite_link, "https://")) {
offset = 8;
} else if (begins_with(lower_cased_invite_link, "http://")) {
offset = 7;
}
lower_cased_invite_link.remove_prefix(offset);
for (auto &url : INVITE_LINK_URLS) {
if (begins_with(lower_cased_invite_link, url)) {
Slice hash = invite_link.substr(url.size() + offset);
hash.truncate(hash.find('#'));
hash.truncate(hash.find('?'));
return hash;
}
}
return Slice();
return !LinkManager::get_dialog_invite_link_hash(invite_link).empty();
}
td_api::object_ptr<td_api::chatInviteLink> DialogInviteLink::get_chat_invite_link_object(

View File

@ -34,8 +34,6 @@ class DialogInviteLink {
friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link);
static const CSlice INVITE_LINK_URLS[12];
public:
DialogInviteLink() = default;
@ -43,8 +41,6 @@ class DialogInviteLink {
static bool is_valid_invite_link(Slice invite_link);
static Slice get_dialog_invite_link_hash(Slice invite_link);
td_api::object_ptr<td_api::chatInviteLink> get_chat_invite_link_object(const ContactsManager *contacts_manager) const;
bool is_valid() const {

View File

@ -17,16 +17,15 @@
#include "td/telegram/misc.h"
#include "td/telegram/net/DcId.h"
#include "td/telegram/Photo.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/StickersManager.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/VideoNotesManager.h"
#include "td/telegram/VideosManager.h"
#include "td/telegram/VoiceNotesManager.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/HttpUrl.h"
@ -517,7 +516,9 @@ void DocumentsManager::create_document(FileId file_id, string minithumbnail, Pho
d->file_id = file_id;
d->file_name = std::move(file_name);
d->mime_type = std::move(mime_type);
d->minithumbnail = std::move(minithumbnail);
if (!td_->auth_manager_->is_bot()) {
d->minithumbnail = std::move(minithumbnail);
}
d->thumbnail = std::move(thumbnail);
on_get_document(std::move(d), replace);
}

View File

@ -360,16 +360,25 @@ void FileReferenceManager::reload_photo(PhotoSizeSource source, Promise<Unit> pr
switch (source.get_type()) {
case PhotoSizeSource::Type::DialogPhotoBig:
case PhotoSizeSource::Type::DialogPhotoSmall:
case PhotoSizeSource::Type::DialogPhotoBigLegacy:
case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
send_closure(G()->contacts_manager(), &ContactsManager::reload_dialog_info, source.dialog_photo().dialog_id,
std::move(promise));
break;
case PhotoSizeSource::Type::StickerSetThumbnail:
case PhotoSizeSource::Type::StickerSetThumbnailLegacy:
case PhotoSizeSource::Type::StickerSetThumbnailVersion:
send_closure(G()->stickers_manager(), &StickersManager::reload_sticker_set,
StickerSetId(source.sticker_set_thumbnail().sticker_set_id),
source.sticker_set_thumbnail().sticker_set_access_hash, std::move(promise));
break;
default:
case PhotoSizeSource::Type::Legacy:
case PhotoSizeSource::Type::FullLegacy:
case PhotoSizeSource::Type::Thumbnail:
promise.set_error(Status::Error("Unexpected PhotoSizeSource type"));
break;
default:
UNREACHABLE();
}
}

View File

@ -163,6 +163,7 @@ void Global::save_server_time() {
void Global::do_save_server_time_difference() {
if (shared_config_ != nullptr && shared_config_->get_option_boolean("disable_time_adjustment_protection")) {
td_db()->get_binlog_pmc()->erase("server_time_difference");
return;
}

View File

@ -40,6 +40,7 @@ class FileManager;
class FileReferenceManager;
class GroupCallManager;
class LanguagePackManager;
class LinkManager;
class MessagesManager;
class MtprotoHeader;
class NetQueryDispatcher;
@ -231,6 +232,13 @@ class Global : public ActorContext {
language_pack_manager_ = language_pack_manager;
}
ActorId<LinkManager> link_manager() const {
return link_manager_;
}
void set_link_manager(ActorId<LinkManager> link_manager) {
link_manager_ = link_manager;
}
ActorId<MessagesManager> messages_manager() const {
return messages_manager_;
}
@ -403,6 +411,7 @@ class Global : public ActorContext {
ActorId<FileReferenceManager> file_reference_manager_;
ActorId<GroupCallManager> group_call_manager_;
ActorId<LanguagePackManager> language_pack_manager_;
ActorId<LinkManager> link_manager_;
ActorId<MessagesManager> messages_manager_;
ActorId<NotificationManager> notification_manager_;
ActorId<PasswordManager> password_manager_;

File diff suppressed because it is too large Load Diff

View File

@ -68,12 +68,24 @@ class GroupCallManager : public Actor {
void start_scheduled_group_call(GroupCallId group_call_id, Promise<Unit> &&promise);
void join_group_call(GroupCallId group_call_id, DialogId as_dialog_id,
td_api::object_ptr<td_api::groupCallPayload> &&payload, int32 audio_source, bool is_muted,
const string &invite_hash, Promise<td_api::object_ptr<td_api::GroupCallJoinResponse>> &&promise);
void join_group_call(GroupCallId group_call_id, DialogId as_dialog_id, int32 audio_source, string &&payload,
bool is_muted, bool is_my_video_enabled, const string &invite_hash, Promise<string> &&promise);
void start_group_call_screen_sharing(GroupCallId group_call_id, string &&payload, Promise<string> &&promise);
void end_group_call_screen_sharing(GroupCallId group_call_id, Promise<Unit> &&promise);
void set_group_call_title(GroupCallId group_call_id, string title, Promise<Unit> &&promise);
void toggle_group_call_is_my_video_paused(GroupCallId group_call_id, bool is_my_video_paused,
Promise<Unit> &&promise);
void toggle_group_call_is_my_video_enabled(GroupCallId group_call_id, bool is_my_video_enabled,
Promise<Unit> &&promise);
void toggle_group_call_is_my_presentation_paused(GroupCallId group_call_id, bool is_my_presentation_paused,
Promise<Unit> &&promise);
void toggle_group_call_start_subscribed(GroupCallId group_call_id, bool start_subscribed, Promise<Unit> &&promise);
void toggle_group_call_mute_new_participants(GroupCallId group_call_id, bool mute_new_participants,
@ -107,6 +119,8 @@ class GroupCallManager : public Actor {
void on_update_dialog_about(DialogId dialog_id, const string &about, bool from_server);
void on_update_group_call_connection(string &&connection_params);
void on_update_group_call(tl_object_ptr<telegram_api::GroupCall> group_call_ptr, DialogId dialog_id);
void on_user_speaking_in_group_call(GroupCallId group_call_id, DialogId dialog_id, int32 date,
@ -123,6 +137,9 @@ class GroupCallManager : public Actor {
void process_join_group_call_response(InputGroupCallId input_group_call_id, uint64 generation,
tl_object_ptr<telegram_api::Updates> &&updates, Promise<Unit> &&promise);
void process_join_group_call_presentation_response(InputGroupCallId input_group_call_id, uint64 generation,
tl_object_ptr<telegram_api::Updates> &&updates, Status status);
private:
struct GroupCall;
struct GroupCallParticipants;
@ -191,6 +208,12 @@ class GroupCallManager : public Actor {
static bool get_group_call_start_subscribed(const GroupCall *group_call);
static bool get_group_call_is_my_video_paused(const GroupCall *group_call);
static bool get_group_call_is_my_video_enabled(const GroupCall *group_call);
static bool get_group_call_is_my_presentation_paused(const GroupCall *group_call);
static bool get_group_call_mute_new_participants(const GroupCall *group_call);
static int32 get_group_call_record_start_date(const GroupCall *group_call);
@ -222,6 +245,9 @@ class GroupCallManager : public Actor {
void update_group_call_participants_can_be_muted(InputGroupCallId input_group_call_id, bool can_manage,
GroupCallParticipants *participants);
void update_group_call_participants_order(InputGroupCallId input_group_call_id, bool can_self_unmute,
GroupCallParticipants *participants, const char *source);
int process_group_call_participant(InputGroupCallId group_call_id, GroupCallParticipant &&participant);
void on_add_group_call_participant(InputGroupCallId input_group_call_id, DialogId participant_dialog_id);
@ -234,6 +260,8 @@ class GroupCallManager : public Actor {
int32 cancel_join_group_call_request(InputGroupCallId input_group_call_id);
int32 cancel_join_group_call_presentation_request(InputGroupCallId input_group_call_id);
bool on_join_group_call_response(InputGroupCallId input_group_call_id, string json_response);
void finish_join_group_call(InputGroupCallId input_group_call_id, uint64 generation, Status error);
@ -256,6 +284,24 @@ class GroupCallManager : public Actor {
void on_toggle_group_call_start_subscription(InputGroupCallId input_group_call_id, bool start_subscribed,
Result<Unit> &&result);
void send_toggle_group_call_is_my_video_paused_query(InputGroupCallId input_group_call_id, DialogId as_dialog_id,
bool is_my_video_paused);
void on_toggle_group_call_is_my_video_paused(InputGroupCallId input_group_call_id, bool is_my_video_paused,
Result<Unit> &&result);
void send_toggle_group_call_is_my_video_enabled_query(InputGroupCallId input_group_call_id, DialogId as_dialog_id,
bool is_my_video_enabled);
void on_toggle_group_call_is_my_video_enabled(InputGroupCallId input_group_call_id, bool is_my_video_enabled,
Result<Unit> &&result);
void send_toggle_group_call_is_my_presentation_paused_query(InputGroupCallId input_group_call_id,
DialogId as_dialog_id, bool is_my_presentation_paused);
void on_toggle_group_call_is_my_presentation_paused(InputGroupCallId input_group_call_id,
bool is_my_presentation_paused, Result<Unit> &&result);
void send_toggle_group_call_mute_new_participants_query(InputGroupCallId input_group_call_id,
bool mute_new_participants);
@ -294,9 +340,6 @@ class GroupCallManager : public Actor {
DialogId set_group_call_participant_is_speaking_by_source(InputGroupCallId input_group_call_id, int32 audio_source,
bool is_speaking, int32 date);
static Result<td_api::object_ptr<td_api::GroupCallJoinResponse>> get_group_call_join_response_object(
string json_response);
bool try_clear_group_call_participants(InputGroupCallId input_group_call_id);
bool set_group_call_participant_count(GroupCall *group_call, int32 count, const char *source,
@ -333,6 +376,8 @@ class GroupCallManager : public Actor {
std::unordered_map<InputGroupCallId, unique_ptr<GroupCall>, InputGroupCallIdHash> group_calls_;
string pending_group_call_join_params_;
std::unordered_map<InputGroupCallId, unique_ptr<GroupCallParticipants>, InputGroupCallIdHash>
group_call_participants_;
std::unordered_map<DialogId, vector<InputGroupCallId>, DialogIdHash> participant_id_to_group_call_id_;
@ -343,6 +388,8 @@ class GroupCallManager : public Actor {
load_group_call_queries_;
std::unordered_map<InputGroupCallId, unique_ptr<PendingJoinRequest>, InputGroupCallIdHash> pending_join_requests_;
std::unordered_map<InputGroupCallId, unique_ptr<PendingJoinRequest>, InputGroupCallIdHash>
pending_join_presentation_requests_;
uint64 join_group_request_generation_ = 0;
uint64 toggle_recording_generation_ = 0;

View File

@ -25,6 +25,7 @@ GroupCallParticipant::GroupCallParticipant(const tl_object_ptr<telegram_api::gro
server_is_muted_by_themselves = participant->can_self_unmute_;
server_is_muted_by_admin = participant->muted_ && !participant->can_self_unmute_;
server_is_muted_locally = participant->muted_by_you_;
can_enable_video = participant->video_joined_;
is_self = participant->self_;
if ((participant->flags_ & telegram_api::groupCallParticipant::VOLUME_MASK) != 0) {
volume_level = participant->volume_;
@ -55,6 +56,13 @@ GroupCallParticipant::GroupCallParticipant(const tl_object_ptr<telegram_api::gro
is_just_joined = participant->just_joined_;
is_min = participant->min_;
version = call_version;
if (participant->video_ != nullptr) {
video_payload = get_group_call_video_payload(participant->video_.get());
}
if (participant->presentation_ != nullptr) {
presentation_payload = get_group_call_video_payload(participant->presentation_.get());
}
}
bool GroupCallParticipant::is_versioned_update(const tl_object_ptr<telegram_api::groupCallParticipant> &participant) {
@ -247,16 +255,19 @@ td_api::object_ptr<td_api::groupCallParticipant> GroupCallParticipant::get_group
}
return td_api::make_object<td_api::groupCallParticipant>(
td->messages_manager_->get_message_sender_object(dialog_id), audio_source, about, is_self, is_speaking,
td->messages_manager_->get_message_sender_object(dialog_id), audio_source, can_enable_video,
get_group_call_participant_video_info_object(video_payload),
get_group_call_participant_video_info_object(presentation_payload), about, is_self, is_speaking,
get_is_hand_raised(), can_be_muted_for_all_users, can_be_unmuted_for_all_users, can_be_muted_only_for_self,
can_be_unmuted_only_for_self, get_is_muted_for_all_users(), get_is_muted_locally(), get_is_muted_by_themselves(),
get_volume_level(), order.get_group_call_participant_order_object());
}
bool operator==(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) {
return lhs.dialog_id == rhs.dialog_id && lhs.audio_source == rhs.audio_source && lhs.about == rhs.about &&
lhs.is_self == rhs.is_self && lhs.is_speaking == rhs.is_speaking &&
lhs.get_is_hand_raised() == rhs.get_is_hand_raised() &&
return lhs.dialog_id == rhs.dialog_id && lhs.audio_source == rhs.audio_source &&
lhs.can_enable_video == rhs.can_enable_video && lhs.video_payload == rhs.video_payload &&
lhs.presentation_payload == rhs.presentation_payload && lhs.about == rhs.about && lhs.is_self == rhs.is_self &&
lhs.is_speaking == rhs.is_speaking && lhs.get_is_hand_raised() == rhs.get_is_hand_raised() &&
lhs.can_be_muted_for_all_users == rhs.can_be_muted_for_all_users &&
lhs.can_be_unmuted_for_all_users == rhs.can_be_unmuted_for_all_users &&
lhs.can_be_muted_only_for_self == rhs.can_be_muted_only_for_self &&

View File

@ -8,6 +8,7 @@
#include "td/telegram/DialogId.h"
#include "td/telegram/GroupCallParticipantOrder.h"
#include "td/telegram/GroupCallVideoPayload.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
@ -21,11 +22,14 @@ class Td;
struct GroupCallParticipant {
DialogId dialog_id;
string about;
GroupCallVideoPayload video_payload;
GroupCallVideoPayload presentation_payload;
int32 audio_source = 0;
int32 joined_date = 0;
int32 active_date = 0;
int32 volume_level = 10000;
int64 raise_hand_rating = 0;
bool can_enable_video = false;
bool is_volume_level_local = false;
bool server_is_muted_by_themselves = false;
bool server_is_muted_by_admin = false;

View File

@ -0,0 +1,48 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/GroupCallVideoPayload.h"
#include "td/utils/algorithm.h"
namespace td {
static bool operator==(const GroupCallVideoSourceGroup &lhs, const GroupCallVideoSourceGroup &rhs) {
return lhs.semantics == rhs.semantics && lhs.source_ids == rhs.source_ids;
}
bool operator==(const GroupCallVideoPayload &lhs, const GroupCallVideoPayload &rhs) {
return lhs.source_groups == rhs.source_groups && lhs.endpoint == rhs.endpoint && lhs.is_paused == rhs.is_paused;
}
static td_api::object_ptr<td_api::groupCallVideoSourceGroup> get_group_call_video_source_group_object(
const GroupCallVideoSourceGroup &group) {
return td_api::make_object<td_api::groupCallVideoSourceGroup>(group.semantics, vector<int32>(group.source_ids));
}
td_api::object_ptr<td_api::groupCallParticipantVideoInfo> get_group_call_participant_video_info_object(
const GroupCallVideoPayload &payload) {
if (payload.endpoint.empty() || payload.source_groups.empty()) {
return nullptr;
}
return td_api::make_object<td_api::groupCallParticipantVideoInfo>(
transform(payload.source_groups, get_group_call_video_source_group_object), payload.endpoint, payload.is_paused);
}
GroupCallVideoPayload get_group_call_video_payload(const telegram_api::groupCallParticipantVideo *video) {
GroupCallVideoPayload result;
result.endpoint = video->endpoint_;
result.source_groups = transform(video->source_groups_, [](auto &&source_group) {
GroupCallVideoSourceGroup result;
result.semantics = source_group->semantics_;
result.source_ids = source_group->sources_;
return result;
});
result.is_paused = video->paused_;
return result;
}
} // namespace td

View File

@ -0,0 +1,34 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
namespace td {
struct GroupCallVideoSourceGroup {
string semantics;
vector<int32> source_ids;
};
struct GroupCallVideoPayload {
vector<GroupCallVideoSourceGroup> source_groups;
string endpoint;
bool is_paused = false;
};
bool operator==(const GroupCallVideoPayload &lhs, const GroupCallVideoPayload &rhs);
td_api::object_ptr<td_api::groupCallParticipantVideoInfo> get_group_call_participant_video_info_object(
const GroupCallVideoPayload &payload);
GroupCallVideoPayload get_group_call_video_payload(const telegram_api::groupCallParticipantVideo *video);
} // namespace td

View File

@ -105,8 +105,8 @@ class GetInlineBotResultsQuery : public Td::ResultHandler {
}
void on_error(uint64 id, Status status) override {
if (status.code() == NetQuery::Cancelled) {
status = Status::Error(406, "Request cancelled");
if (status.code() == NetQuery::Canceled) {
status = Status::Error(406, "Request canceled");
} else if (status.message() == "BOT_RESPONSE_TIMEOUT") {
status = Status::Error(502, "The bot is not responding");
}
@ -833,7 +833,7 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial
LOG(INFO) << "Drop inline query " << pending_inline_query_->query_hash;
on_get_inline_query_results(pending_inline_query_->dialog_id, pending_inline_query_->bot_user_id,
pending_inline_query_->query_hash, nullptr);
pending_inline_query_->promise.set_error(Status::Error(406, "Request cancelled"));
pending_inline_query_->promise.set_error(Status::Error(406, "Request canceled"));
}
pending_inline_query_ = make_unique<PendingInlineQuery>(PendingInlineQuery{
@ -1269,7 +1269,8 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI
break;
}
if (dialog_type == DialogType::Channel &&
td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast) {
td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) ==
ContactsManager::ChannelType::Broadcast) {
continue;
}
if (dialog_type == DialogType::SecretChat) {

1252
td/telegram/LinkManager.cpp Normal file

File diff suppressed because it is too large Load Diff

126
td/telegram/LinkManager.h Normal file
View File

@ -0,0 +1,126 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/FullMessageId.h"
#include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/td_api.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include <utility>
namespace td {
class Td;
class LinkManager : public Actor {
public:
LinkManager(Td *td, ActorShared<> parent);
LinkManager(const LinkManager &) = delete;
LinkManager &operator=(const LinkManager &) = delete;
LinkManager(LinkManager &&) = delete;
LinkManager &operator=(LinkManager &&) = delete;
~LinkManager() override;
class InternalLink {
public:
InternalLink() = default;
InternalLink(const InternalLink &) = delete;
InternalLink &operator=(const InternalLink &) = delete;
InternalLink(InternalLink &&) = delete;
InternalLink &operator=(InternalLink &&) = delete;
virtual ~InternalLink() = default;
virtual td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const = 0;
};
// checks whether the link is a valid tg, ton or HTTP(S) URL and returns it in a canonical form
static Result<string> check_link(Slice link);
// checks whether the link is a supported tg or t.me link and parses it
static unique_ptr<InternalLink> parse_internal_link(Slice link);
void update_autologin_domains(string autologin_token, vector<string> autologin_domains,
vector<string> url_auth_domains);
void get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_login_url_info(FullMessageId full_message_id, int32 button_id,
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_login_url(FullMessageId full_message_id, int32 button_id, bool allow_write_access,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
void get_link_login_url(const string &url, bool allow_write_access,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
static string get_dialog_invite_link_hash(Slice invite_link);
static Result<MessageLinkInfo> get_message_link_info(Slice url);
private:
void start_up() final;
void tear_down() final;
class InternalLinkActiveSessions;
class InternalLinkAuthenticationCode;
class InternalLinkBackground;
class InternalLinkBotStart;
class InternalLinkBotStartInGroup;
class InternalLinkChangePhoneNumber;
class InternalLinkConfirmPhone;
class InternalLinkDialogInvite;
class InternalLinkFilterSettings;
class InternalLinkGame;
class InternalLinkLanguage;
class InternalLinkMessage;
class InternalLinkMessageDraft;
class InternalLinkPassportDataRequest;
class InternalLinkProxy;
class InternalLinkPublicDialog;
class InternalLinkQrCodeAuthentication;
class InternalLinkSettings;
class InternalLinkStickerSet;
class InternalLinkTheme;
class InternalLinkThemeSettings;
class InternalLinkUnknownDeepLink;
class InternalLinkVoiceChat;
struct LinkInfo {
bool is_internal_ = false;
bool is_tg_ = false;
string query_;
};
// returns information about the link
static LinkInfo get_link_info(Slice link);
static unique_ptr<InternalLink> parse_tg_link_query(Slice query);
static unique_ptr<InternalLink> parse_t_me_link_query(Slice query);
static unique_ptr<InternalLink> get_internal_link_passport(const vector<std::pair<string, string>> &args);
static unique_ptr<InternalLink> get_internal_link_message_draft(Slice url, Slice text);
Td *td_;
ActorShared<> parent_;
string autologin_token_;
vector<string> autologin_domains_;
double autologin_update_time_ = 0.0;
vector<string> url_auth_domains_;
};
} // namespace td

View File

@ -12,8 +12,6 @@
#include "td/telegram/td_api.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
#include <mutex>

View File

@ -21,7 +21,7 @@
#include "td/telegram/Td.h"
#include "td/telegram/UpdatesManager.h"
#include "td/db/binlog/BinlogEvent.h"
#include "td/db/binlog/Binlog.h"
#include "td/db/SqliteStatement.h"
#include "td/net/GetHostByNameActor.h"
@ -32,9 +32,9 @@
#include "td/utils/algorithm.h"
#include "td/utils/ExitGuard.h"
#include "td/utils/FileLog.h"
#include "td/utils/NullLog.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/NullLog.h"
#include "td/utils/port/detail/NativeFd.h"
#include "td/utils/TsLog.h"

View File

@ -50,7 +50,6 @@
#include "td/telegram/PollId.hpp"
#include "td/telegram/PollManager.h"
#include "td/telegram/secret_api.hpp"
#include "td/telegram/SecretChatActor.h"
#include "td/telegram/SecureValue.h"
#include "td/telegram/SecureValue.hpp"
#include "td/telegram/StickersManager.h"
@ -85,7 +84,6 @@
#include "td/utils/tl_helpers.h"
#include "td/utils/utf8.h"
#include <limits>
#include <utility>
namespace td {
@ -2061,12 +2059,12 @@ bool can_have_input_media(const Td *td, const MessageContent *content) {
SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer) {
BufferSlice thumbnail) {
switch (content->get_type()) {
case MessageContentType::Animation: {
auto m = static_cast<const MessageAnimation *>(content);
return td->animations_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
std::move(thumbnail), layer);
std::move(thumbnail));
}
case MessageContentType::Audio: {
auto m = static_cast<const MessageAudio *>(content);
@ -2112,8 +2110,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
}
case MessageContentType::VideoNote: {
auto m = static_cast<const MessageVideoNote *>(content);
return td->video_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail),
layer);
return td->video_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail));
}
case MessageContentType::VoiceNote: {
auto m = static_cast<const MessageVoiceNote *>(content);
@ -2438,13 +2435,6 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) {
Status can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward, const Td *td) {
auto dialog_type = dialog_id.get_type();
int32 secret_chat_layer = std::numeric_limits<int32>::max();
if (dialog_type == DialogType::SecretChat) {
auto secret_chat_id = dialog_id.get_secret_chat_id();
secret_chat_layer = td->contacts_manager_->get_secret_chat_layer(secret_chat_id);
}
auto content_type = content->get_type();
RestrictedRights permissions = [&] {
switch (dialog_type) {
case DialogType::User:
@ -2462,6 +2452,7 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten
}
}();
auto content_type = content->get_type();
switch (content_type) {
case MessageContentType::Animation:
if (!permissions.can_send_animations()) {
@ -2492,8 +2483,8 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten
}
break;
case MessageContentType::Game:
if (dialog_type == DialogType::Channel &&
td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast) {
if (dialog_type == DialogType::Channel && td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) ==
ContactsManager::ChannelType::Broadcast) {
// return Status::Error(400, "Games can't be sent to channel chats");
}
if (dialog_type == DialogType::SecretChat) {
@ -2531,7 +2522,8 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten
return Status::Error(400, "Not enough rights to send polls to the chat");
}
if (dialog_type == DialogType::Channel &&
td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast &&
td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) ==
ContactsManager::ChannelType::Broadcast &&
!td->poll_manager_->get_poll_is_anonymous(static_cast<const MessagePoll *>(content)->poll_id)) {
return Status::Error(400, "Non-anonymous polls can't be sent to channel chats");
}
@ -2567,10 +2559,6 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten
if (!permissions.can_send_media()) {
return Status::Error(400, "Not enough rights to send video notes to the chat");
}
if (secret_chat_layer < SecretChatActor::VIDEO_NOTES_LAYER) {
return Status::Error(400, PSLICE()
<< "Video notes can't be sent to secret chat with layer " << secret_chat_layer);
}
break;
case MessageContentType::VoiceNote:
if (!permissions.can_send_media()) {
@ -3091,10 +3079,9 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
new_file_view.remote_location().get_file_reference() ||
old_file_view.main_remote_location().get_access_hash() !=
new_file_view.remote_location().get_access_hash()) {
auto volume_id = -new_file_view.remote_location().get_id();
FileId file_id = td->file_manager_->register_remote(
FullRemoteFileLocation({FileType::Photo, 'i'}, new_file_view.remote_location().get_id(),
new_file_view.remote_location().get_access_hash(), 0, volume_id, DcId::invalid(),
new_file_view.remote_location().get_access_hash(), DcId::invalid(),
new_file_view.remote_location().get_file_reference().str()),
FileLocationSource::FromServer, dialog_id, old_photo->photos.back().size, 0, "");
LOG_STATUS(td->file_manager_->merge(file_id, old_file_id));
@ -3592,16 +3579,6 @@ void unregister_message_content(Td *td, const MessageContent *content, FullMessa
template <class ToT, class FromT>
static tl_object_ptr<ToT> secret_to_telegram(FromT &from);
// fileLocationUnavailable volume_id:long local_id:int secret:long = FileLocation;
static auto secret_to_telegram(secret_api::fileLocationUnavailable &file_location) {
return make_tl_object<telegram_api::fileLocationToBeDeprecated>(file_location.volume_id_, file_location.local_id_);
}
// fileLocation dc_id:int volume_id:long local_id:int secret:long = FileLocation;
static auto secret_to_telegram(secret_api::fileLocation &file_location) {
return make_tl_object<telegram_api::fileLocationToBeDeprecated>(file_location.volume_id_, file_location.local_id_);
}
// photoSizeEmpty type:string = PhotoSize;
static auto secret_to_telegram(secret_api::photoSizeEmpty &empty) {
if (!clean_input_string(empty.type_)) {
@ -3615,9 +3592,7 @@ static auto secret_to_telegram(secret_api::photoSize &photo_size) {
if (!clean_input_string(photo_size.type_)) {
photo_size.type_.clear();
}
return make_tl_object<telegram_api::photoSize>(
photo_size.type_, secret_to_telegram<telegram_api::fileLocationToBeDeprecated>(*photo_size.location_),
photo_size.w_, photo_size.h_, photo_size.size_);
return make_tl_object<telegram_api::photoSize>(photo_size.type_, photo_size.w_, photo_size.h_, photo_size.size_);
}
// photoCachedSize type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
@ -3625,9 +3600,8 @@ static auto secret_to_telegram(secret_api::photoCachedSize &photo_size) {
if (!clean_input_string(photo_size.type_)) {
photo_size.type_.clear();
}
return make_tl_object<telegram_api::photoCachedSize>(
photo_size.type_, secret_to_telegram<telegram_api::fileLocationToBeDeprecated>(*photo_size.location_),
photo_size.w_, photo_size.h_, photo_size.bytes_.clone());
return make_tl_object<telegram_api::photoCachedSize>(photo_size.type_, photo_size.w_, photo_size.h_,
photo_size.bytes_.clone());
}
// documentAttributeImageSize w:int h:int = DocumentAttribute;

View File

@ -107,7 +107,7 @@ bool can_have_input_media(const Td *td, const MessageContent *content);
SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer);
BufferSlice thumbnail);
tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content, Td *td,
tl_object_ptr<telegram_api::InputFile> input_file,

View File

@ -8,6 +8,7 @@
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Dependencies.h"
#include "td/telegram/LinkManager.h"
#include "td/telegram/misc.h"
#include "td/telegram/SecretChatActor.h"
@ -484,6 +485,89 @@ static vector<Slice> match_bank_card_numbers(Slice str) {
return result;
}
static bool is_url_unicode_symbol(uint32 c) {
if (0x2000 <= c && c <= 0x206f) { // General Punctuation
// Zero Width Non-Joiner/Joiner and various dashes
return c == 0x200c || c == 0x200d || (0x2010 <= c && c <= 0x2015);
}
return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
}
static bool is_url_path_symbol(uint32 c) {
switch (c) {
case '\n':
case '<':
case '>':
case '"':
case 0xab: // «
case 0xbb: // »
return false;
default:
return is_url_unicode_symbol(c);
}
}
static vector<Slice> match_tg_urls(Slice str) {
vector<Slice> result;
const unsigned char *begin = str.ubegin();
const unsigned char *end = str.uend();
const unsigned char *ptr = begin;
// '(tg|ton)://[a-z0-9_-]{1,253}([/?#][^\s\x{2000}-\x{200b}\x{200e}-\x{200f}\x{2016}-\x{206f}<>«»"]*)?'
Slice bad_path_end_chars(".:;,('?!`");
while (end - ptr > 5) {
ptr = static_cast<const unsigned char *>(std::memchr(ptr, ':', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
const unsigned char *url_begin = nullptr;
if (end - ptr >= 3 && ptr[1] == '/' && ptr[2] == '/') {
if (ptr - begin >= 2 && to_lower(ptr[-2]) == 't' && to_lower(ptr[-1]) == 'g') {
url_begin = ptr - 2;
} else if (ptr - begin >= 3 && to_lower(ptr[-3]) == 't' && to_lower(ptr[-2]) == 'o' && to_lower(ptr[-1]) == 'n') {
url_begin = ptr - 3;
}
}
if (url_begin == nullptr) {
++ptr;
continue;
}
ptr += 3;
auto domain_begin = ptr;
while (ptr != end && ptr - domain_begin != 253 && is_alpha_digit_or_underscore_or_minus(*ptr)) {
ptr++;
}
if (ptr == domain_begin) {
continue;
}
if (ptr != end && (*ptr == '/' || *ptr == '?' || *ptr == '#')) {
auto path_end_ptr = ptr + 1;
while (path_end_ptr != end) {
uint32 code = 0;
auto next_ptr = next_utf8_unsafe(path_end_ptr, &code, "match_tg_urls");
if (!is_url_path_symbol(code)) {
break;
}
path_end_ptr = next_ptr;
}
while (path_end_ptr > ptr + 1 && bad_path_end_chars.find(path_end_ptr[-1]) < bad_path_end_chars.size()) {
path_end_ptr--;
}
if (ptr[0] == '/' || path_end_ptr > ptr + 1) {
ptr = path_end_ptr;
}
}
result.emplace_back(url_begin, ptr);
}
return result;
}
static vector<Slice> match_urls(Slice str) {
vector<Slice> result;
const unsigned char *begin = str.ubegin();
@ -517,10 +601,7 @@ static vector<Slice> match_urls(Slice str) {
case 0xbb: // »
return false;
default:
if (0x2000 <= c && c <= 0x206f) { // General Punctuation
return c == 0x200c || c == 0x200d; // Zero Width Non-Joiner/Joiner
}
return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
return is_url_unicode_symbol(c);
}
};
@ -528,27 +609,7 @@ static vector<Slice> match_urls(Slice str) {
if (c < 0xc0) {
return c == '.' || is_alpha_digit_or_underscore_or_minus(c) || c == '~';
}
if (0x2000 <= c && c <= 0x206f) { // General Punctuation
return c == 0x200c || c == 0x200d; // Zero Width Non-Joiner/Joiner
}
return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
};
const auto &is_path_symbol = [](uint32 c) {
switch (c) {
case '\n':
case '<':
case '>':
case '"':
case 0xab: // «
case 0xbb: // »
return false;
default:
if (0x2000 <= c && c <= 0x206f) { // General Punctuation
return c == 0x200c || c == 0x200d; // Zero Width Non-Joiner/Joiner
}
return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
}
return is_url_unicode_symbol(c);
};
Slice bad_path_end_chars(".:;,('?!`");
@ -624,7 +685,7 @@ static vector<Slice> match_urls(Slice str) {
while (path_end_ptr != end) {
uint32 code = 0;
auto next_ptr = next_utf8_unsafe(path_end_ptr, &code, "match_urls 4");
if (!is_path_symbol(code)) {
if (!is_url_path_symbol(code)) {
break;
}
path_end_ptr = next_ptr;
@ -969,7 +1030,7 @@ static bool is_common_tld(Slice str) {
"இந்தியா", "հայ", "新加坡", "فلسطين", "政务", "xperia", "xxx", "xyz", "yachts", "yahoo", "yamaxun", "yandex",
"ye", "yodobashi", "yoga", "yokohama", "you", "youtube", "yt", "yun", "za", "zappos", "zara", "zero", "zip",
"zippo", "zm", "zone", "zuerich",
// comment for clang-format to prevent him from placing all strings on separate lines
// comment for clang-format to prevent it from placing all strings on separate lines
"zw"});
string str_lower = utf8_to_lower(str);
if (str_lower != str && utf8_substr(Slice(str_lower), 1) == utf8_substr(str, 1)) {
@ -978,7 +1039,7 @@ static bool is_common_tld(Slice str) {
return tlds.count(str_lower) > 0;
}
Slice fix_url(Slice str) {
static Slice fix_url(Slice str) {
auto full_url = str;
bool has_protocol = false;
@ -1156,6 +1217,10 @@ vector<Slice> find_bank_card_numbers(Slice str) {
return result;
}
vector<Slice> find_tg_urls(Slice str) {
return match_tg_urls(str);
}
vector<std::pair<Slice, bool>> find_urls(Slice str) {
vector<std::pair<Slice, bool>> result;
for (auto url : match_urls(str)) {
@ -1251,6 +1316,12 @@ static int32 is_user_entity(MessageEntity::Type type) {
return (get_entity_type_mask(type) & get_user_entities_mask()) != 0;
}
static int32 is_hidden_data_entity(MessageEntity::Type type) {
return (get_entity_type_mask(type) &
(get_entity_type_mask(MessageEntity::Type::TextUrl) | get_entity_type_mask(MessageEntity::Type::MentionName) |
get_pre_entities_mask())) != 0;
}
static constexpr size_t SPLITTABLE_ENTITY_TYPE_COUNT = 4;
static size_t get_splittable_entity_type_index(MessageEntity::Type type) {
@ -1376,34 +1447,30 @@ static void remove_entities_intersecting_blockquote(vector<MessageEntity> &entit
entities.erase(entities.begin() + left_entities, entities.end());
}
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool only_urls) {
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands) {
vector<MessageEntity> entities;
if (!only_urls) {
auto add_entities = [&entities, &text](MessageEntity::Type type, vector<Slice> (*find_entities_f)(Slice)) mutable {
auto new_entities = find_entities_f(text);
for (auto &entity : new_entities) {
auto offset = narrow_cast<int32>(entity.begin() - text.begin());
auto length = narrow_cast<int32>(entity.size());
entities.emplace_back(type, offset, length);
}
};
add_entities(MessageEntity::Type::Mention, find_mentions);
if (!skip_bot_commands) {
add_entities(MessageEntity::Type::BotCommand, find_bot_commands);
auto add_entities = [&entities, &text](MessageEntity::Type type, vector<Slice> (*find_entities_f)(Slice)) mutable {
auto new_entities = find_entities_f(text);
for (auto &entity : new_entities) {
auto offset = narrow_cast<int32>(entity.begin() - text.begin());
auto length = narrow_cast<int32>(entity.size());
entities.emplace_back(type, offset, length);
}
add_entities(MessageEntity::Type::Hashtag, find_hashtags);
add_entities(MessageEntity::Type::Cashtag, find_cashtags);
// TODO find_phone_numbers
add_entities(MessageEntity::Type::BankCardNumber, find_bank_card_numbers);
};
add_entities(MessageEntity::Type::Mention, find_mentions);
if (!skip_bot_commands) {
add_entities(MessageEntity::Type::BotCommand, find_bot_commands);
}
add_entities(MessageEntity::Type::Hashtag, find_hashtags);
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;
if (only_urls && type != MessageEntity::Type::Url) {
continue;
}
auto offset = narrow_cast<int32>(url.first.begin() - text.begin());
auto length = narrow_cast<int32>(url.first.size());
entities.emplace_back(type, offset, length);
@ -1501,7 +1568,8 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
break;
case MessageEntity::Type::Url: {
Slice url = utf8_utf16_substr(text, entity.offset, entity.length);
if (begins_with(url, "ton:") || begins_with(url, "tg:") || is_plain_domain(url)) {
string scheme = to_lower(url.substr(0, 4));
if (scheme == "ton:" || begins_with(scheme, "tg:") || scheme == "ftp:" || is_plain_domain(url)) {
continue;
}
return url.str();
@ -1524,11 +1592,13 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
break;
case MessageEntity::Type::PreCode:
break;
case MessageEntity::Type::TextUrl:
if (begins_with(entity.argument, "ton:") || begins_with(entity.argument, "tg:")) {
case MessageEntity::Type::TextUrl: {
Slice url = entity.argument;
if (begins_with(url, "ton:") || begins_with(url, "tg:") || begins_with(url, "ftp:")) {
continue;
}
return entity.argument;
return url.str();
}
case MessageEntity::Type::MentionName:
break;
case MessageEntity::Type::Cashtag:
@ -1676,7 +1746,7 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
if (user_id.is_valid()) {
entities.emplace_back(entity_offset, entity_length, user_id);
} else {
auto r_url = check_url(url);
auto r_url = LinkManager::check_link(url);
if (r_url.is_ok()) {
entities.emplace_back(MessageEntity::Type::TextUrl, entity_offset, entity_length, r_url.move_as_ok());
}
@ -1880,7 +1950,7 @@ static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &r
}
user_id = get_link_user_id(url);
if (!user_id.is_valid()) {
auto r_url = check_url(url);
auto r_url = LinkManager::check_link(url);
if (r_url.is_error()) {
skip_entity = true;
} else {
@ -1957,7 +2027,7 @@ static vector<Slice> find_text_url_entities_v3(Slice text) {
if (url_end < size) {
Slice url = text.substr(url_begin + 1, url_end - url_begin - 1);
if (check_url(url).is_ok()) {
if (LinkManager::check_link(url).is_ok()) {
result.push_back(text.substr(text_begin, text_end - text_begin + 1));
result.push_back(text.substr(url_begin, url_end - url_begin + 1));
}
@ -2029,7 +2099,7 @@ static FormattedText parse_text_url_entities_v3(Slice text, const vector<Message
Slice url = parsed_part_text.substr(url_begin_pos + 1, url_end_pos - url_begin_pos - 1);
auto url_utf16_length = text_length(url);
result.entities.emplace_back(MessageEntity::Type::TextUrl, result_text_utf16_length, text_url_utf16_length,
check_url(url).move_as_ok());
LinkManager::check_link(url).move_as_ok());
result.text.append(text_url.begin(), text_url.size());
result_text_utf16_length += text_url_utf16_length;
@ -2160,7 +2230,7 @@ static vector<MessageEntity> find_splittable_entities_v3(Slice text, const vecto
}
}
auto found_entities = find_entities(text, false, false);
auto found_entities = find_entities(text, false);
td::remove_if(found_entities, [](const auto &entity) {
return entity.type == MessageEntity::Type::EmailAddress || entity.type == MessageEntity::Type::Url;
});
@ -2832,7 +2902,7 @@ static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result)
if (user_id.is_valid()) {
entities.emplace_back(entity_offset, entity_length, user_id);
} else {
auto r_url = check_url(url);
auto r_url = LinkManager::check_link(url);
if (r_url.is_ok()) {
entities.emplace_back(MessageEntity::Type::TextUrl, entity_offset, entity_length, r_url.move_as_ok());
}
@ -3026,7 +3096,7 @@ Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contac
if (!clean_input_string(entity_text_url->url_)) {
return Status::Error(400, "MessageEntityTextUrl.url must be encoded in UTF-8");
}
auto r_url = check_url(entity_text_url->url_);
auto r_url = LinkManager::check_link(entity_text_url->url_);
if (r_url.is_error()) {
return Status::Error(400, PSTRING() << "Wrong message entity: " << r_url.error().message());
}
@ -3146,7 +3216,7 @@ vector<MessageEntity> get_message_entities(const ContactsManager *contacts_manag
}
case telegram_api::messageEntityTextUrl::ID: {
auto entity_text_url = static_cast<const telegram_api::messageEntityTextUrl *>(entity.get());
auto r_url = check_url(entity_text_url->url_);
auto r_url = LinkManager::check_link(entity_text_url->url_);
if (r_url.is_error()) {
LOG(ERROR) << "Wrong URL entity: \"" << entity_text_url->url_ << "\": " << r_url.error().message() << " from "
<< source;
@ -3268,7 +3338,7 @@ vector<MessageEntity> get_message_entities(vector<tl_object_ptr<secret_api::Mess
LOG(WARNING) << "Wrong URL entity: \"" << entity_text_url->url_ << '"';
continue;
}
auto r_url = check_url(entity_text_url->url_);
auto r_url = LinkManager::check_link(entity_text_url->url_);
if (r_url.is_error()) {
LOG(WARNING) << "Wrong URL entity: \"" << entity_text_url->url_ << "\": " << r_url.error().message();
continue;
@ -3444,7 +3514,6 @@ static std::pair<size_t, int32> remove_invalid_entities(const string &text, vect
size_t last_non_whitespace_pos = text.size();
int32 utf16_offset = 0;
int32 last_space_utf16_offset = -1;
int32 last_non_whitespace_utf16_offset = -1;
td::remove_if(entities, [](const auto &entity) { return entity.length == 0; });
@ -3457,12 +3526,9 @@ static std::pair<size_t, int32> remove_invalid_entities(const string &text, vect
break;
}
auto have_hidden_data = entity->type == MessageEntity::Type::TextUrl ||
entity->type == MessageEntity::Type::MentionName || is_pre_entity(entity->type);
if (last_non_whitespace_utf16_offset >= entity->offset ||
(last_space_utf16_offset >= entity->offset && have_hidden_data)) {
// TODO check entity for validness, for example, that mentions, hashtags, cashtags and URLs are valid
if (last_non_whitespace_utf16_offset >= entity->offset || is_hidden_data_entity(entity->type)) {
// keep entity
// TODO check entity for validness, for example, that mentions, hashtags, cashtags and URLs are valid
} else {
entity->length = 0;
}
@ -3483,8 +3549,7 @@ static std::pair<size_t, int32> remove_invalid_entities(const string &text, vect
// one continuous entity for the given offset
for (size_t i = nested_entities_stack.size(); i > 0; i--) {
auto *entity = nested_entities_stack[i - 1];
if (entity->offset != utf16_offset || entity->type == MessageEntity::Type::TextUrl ||
entity->type == MessageEntity::Type::MentionName || is_pre_entity(entity->type)) {
if (entity->offset != utf16_offset || is_hidden_data_entity(entity->type)) {
break;
}
entity->offset++;
@ -3499,9 +3564,7 @@ static std::pair<size_t, int32> remove_invalid_entities(const string &text, vect
auto c = static_cast<unsigned char>(text[pos]);
switch (c) {
case '\n':
break;
case 32:
last_space_utf16_offset = utf16_offset;
break;
default:
while (!is_utf8_character_first_code_unit(static_cast<unsigned char>(text[pos + 1]))) {
@ -3742,8 +3805,7 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
CHECK(last_non_whitespace_pos < result.size());
result.resize(last_non_whitespace_pos + 1);
while (!entities.empty() && entities.back().offset > last_non_whitespace_utf16_offset) {
CHECK(entities.back().type == MessageEntity::Type::TextUrl ||
entities.back().type == MessageEntity::Type::MentionName || is_pre_entity(entities.back().type));
CHECK(is_hidden_data_entity(entities.back().type));
entities.pop_back();
}
bool need_sort = false;

View File

@ -135,13 +135,14 @@ vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<
td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text);
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool only_urls = false);
vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands);
vector<Slice> find_mentions(Slice str);
vector<Slice> find_bot_commands(Slice str);
vector<Slice> find_hashtags(Slice str);
vector<Slice> find_cashtags(Slice str);
vector<Slice> find_bank_card_numbers(Slice str);
vector<Slice> find_tg_urls(Slice str);
bool is_email_address(Slice str);
vector<std::pair<Slice, bool>> find_urls(Slice str); // slice + is_email_address

View File

@ -0,0 +1,30 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/ChannelId.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/MessageId.h"
#include "td/utils/common.h"
namespace td {
struct MessageLinkInfo {
string username;
// or
ChannelId channel_id;
MessageId message_id;
bool is_single = false;
DialogId comment_dialog_id;
MessageId comment_message_id;
bool for_comment = false;
};
} // namespace td

View File

@ -28,6 +28,7 @@
#include "td/telegram/GroupCallParticipant.h"
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/InputMessageText.h"
#include "td/telegram/LinkManager.h"
#include "td/telegram/Location.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MemoryManager.h"
@ -2163,7 +2164,8 @@ class SearchMessagesQuery : public Td::ResultHandler {
} else if (top_thread_message_id.is_valid() && query.empty() && !sender_dialog_id.is_valid() &&
filter == MessageSearchFilter::Empty) {
handle_errors_ = dialog_id.get_type() != DialogType::Channel ||
td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) != ChannelType::Broadcast;
td->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) !=
ContactsManager::ChannelType::Broadcast;
send_query(G()->net_query_creator().create(telegram_api::messages_getReplies(
std::move(input_peer), top_thread_message_id.get_server_message_id().get(),
from_message_id.get_server_message_id().get(), 0, offset, limit, std::numeric_limits<int32>::max(), 0, 0)));
@ -3869,7 +3871,7 @@ class SetTypingQuery : public Td::ResultHandler {
}
void on_error(uint64 id, Status status) override {
if (status.code() == NetQuery::Cancelled) {
if (status.code() == NetQuery::Canceled) {
return promise_.set_value(Unit());
}
@ -4622,135 +4624,6 @@ class GetStatsUrlQuery : public Td::ResultHandler {
}
};
class RequestUrlAuthQuery : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> promise_;
string url_;
DialogId dialog_id_;
public:
explicit RequestUrlAuthQuery(Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise)
: promise_(std::move(promise)) {
}
void send(string url, DialogId dialog_id, MessageId message_id, int32 button_id) {
url_ = std::move(url);
int32 flags = 0;
tl_object_ptr<telegram_api::InputPeer> input_peer;
if (dialog_id.is_valid()) {
dialog_id_ = dialog_id;
input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
flags |= telegram_api::messages_requestUrlAuth::PEER_MASK;
} else {
flags |= telegram_api::messages_requestUrlAuth::URL_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_requestUrlAuth(
flags, std::move(input_peer), message_id.get_server_message_id().get(), button_id, url_)));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::messages_requestUrlAuth>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
LOG(INFO) << "Receive " << to_string(result);
switch (result->get_id()) {
case telegram_api::urlAuthResultRequest::ID: {
auto request = telegram_api::move_object_as<telegram_api::urlAuthResultRequest>(result);
UserId bot_user_id = ContactsManager::get_user_id(request->bot_);
if (!bot_user_id.is_valid()) {
return on_error(id, Status::Error(500, "Receive invalid bot_user_id"));
}
td->contacts_manager_->on_get_user(std::move(request->bot_), "RequestUrlAuthQuery");
bool request_write_access =
(request->flags_ & telegram_api::urlAuthResultRequest::REQUEST_WRITE_ACCESS_MASK) != 0;
promise_.set_value(td_api::make_object<td_api::loginUrlInfoRequestConfirmation>(
url_, request->domain_, td->contacts_manager_->get_user_id_object(bot_user_id, "RequestUrlAuthQuery"),
request_write_access));
break;
}
case telegram_api::urlAuthResultAccepted::ID: {
auto accepted = telegram_api::move_object_as<telegram_api::urlAuthResultAccepted>(result);
promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(accepted->url_, true));
break;
}
case telegram_api::urlAuthResultDefault::ID:
promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url_, false));
break;
}
}
void on_error(uint64 id, Status status) override {
if (!dialog_id_.is_valid() ||
!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "RequestUrlAuthQuery")) {
LOG(INFO) << "RequestUrlAuthQuery returned " << status;
}
promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url_, false));
}
};
class AcceptUrlAuthQuery : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::httpUrl>> promise_;
string url_;
DialogId dialog_id_;
public:
explicit AcceptUrlAuthQuery(Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) : promise_(std::move(promise)) {
}
void send(string url, DialogId dialog_id, MessageId message_id, int32 button_id, bool allow_write_access) {
url_ = std::move(url);
int32 flags = 0;
tl_object_ptr<telegram_api::InputPeer> input_peer;
if (dialog_id.is_valid()) {
dialog_id_ = dialog_id;
input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
flags |= telegram_api::messages_acceptUrlAuth::PEER_MASK;
} else {
flags |= telegram_api::messages_acceptUrlAuth::URL_MASK;
}
if (allow_write_access) {
flags |= telegram_api::messages_acceptUrlAuth::WRITE_ALLOWED_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_acceptUrlAuth(
flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), button_id, url_)));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::messages_acceptUrlAuth>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
LOG(INFO) << "Receive " << to_string(result);
switch (result->get_id()) {
case telegram_api::urlAuthResultRequest::ID:
LOG(ERROR) << "Receive unexpected " << to_string(result);
return on_error(id, Status::Error(500, "Receive unexpected urlAuthResultRequest"));
case telegram_api::urlAuthResultAccepted::ID: {
auto accepted = telegram_api::move_object_as<telegram_api::urlAuthResultAccepted>(result);
promise_.set_value(td_api::make_object<td_api::httpUrl>(accepted->url_));
break;
}
case telegram_api::urlAuthResultDefault::ID:
promise_.set_value(td_api::make_object<td_api::httpUrl>(url_));
break;
}
}
void on_error(uint64 id, Status status) override {
if (!dialog_id_.is_valid() ||
!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "AcceptUrlAuthQuery")) {
LOG(INFO) << "AcceptUrlAuthQuery returned " << status;
}
promise_.set_error(std::move(status));
}
};
class GetChannelDifferenceQuery : public Td::ResultHandler {
DialogId dialog_id_;
int32 pts_;
@ -6114,12 +5987,12 @@ void MessagesManager::on_preload_folder_dialog_list_timeout_callback(void *messa
td_api::object_ptr<td_api::MessageSender> MessagesManager::get_message_sender_object_const(UserId user_id,
DialogId dialog_id) const {
if (dialog_id.is_valid()) {
CHECK(have_dialog(dialog_id));
if (dialog_id.is_valid() && have_dialog(dialog_id)) {
return td_api::make_object<td_api::messageSenderChat>(dialog_id.get());
}
if (!user_id.is_valid()) {
// can happen only if the server sends a message with wrong sender
LOG(ERROR) << "Receive message with wrong sender " << user_id << '/' << dialog_id;
user_id = td_->contacts_manager_->add_service_notifications_user();
}
return td_api::make_object<td_api::messageSenderUser>(
@ -7468,7 +7341,7 @@ void MessagesManager::on_user_dialog_action(DialogId dialog_id, MessageId top_th
}
if (!td_->contacts_manager_->is_user_bot(user_id) &&
!it->action.is_cancelled_by_message_of_type(message_content_type)) {
!it->action.is_canceled_by_message_of_type(message_content_type)) {
return;
}
@ -8778,30 +8651,30 @@ void MessagesManager::get_dialog_statistics_url(DialogId dialog_id, const string
td_->create_handler<GetStatsUrlQuery>(std::move(promise))->send(dialog_id, parameters, is_dark);
}
Result<string> MessagesManager::get_login_button_url(DialogId dialog_id, MessageId message_id, int32 button_id) {
Dialog *d = get_dialog_force(dialog_id, "get_login_button_url");
Result<string> MessagesManager::get_login_button_url(FullMessageId full_message_id, int32 button_id) {
Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), "get_login_button_url");
if (d == nullptr) {
return Status::Error(3, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
return Status::Error(3, "Can't access the chat");
}
auto m = get_message_force(d, message_id, "get_login_button_url");
auto m = get_message_force(d, full_message_id.get_message_id(), "get_login_button_url");
if (m == nullptr) {
return Status::Error(5, "Message not found");
}
if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
return Status::Error(5, "Message has no inline keyboard");
}
if (message_id.is_scheduled()) {
if (m->message_id.is_scheduled()) {
return Status::Error(5, "Can't use login buttons from scheduled messages");
}
if (!message_id.is_server()) {
if (!m->message_id.is_server()) {
// it shouldn't have UrlAuth buttons anyway
return Status::Error(5, "Message is not server");
}
if (dialog_id.get_type() == DialogType::SecretChat) {
if (d->dialog_id.get_type() == DialogType::SecretChat) {
// secret chat messages can't have reply markup, so this shouldn't happen now
return Status::Error(5, "Message is in a secret chat");
}
@ -8817,43 +8690,6 @@ Result<string> MessagesManager::get_login_button_url(DialogId dialog_id, Message
return Status::Error(5, "Button not found");
}
void MessagesManager::get_login_url_info(DialogId dialog_id, MessageId message_id, int32 button_id,
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
auto r_url = get_login_button_url(dialog_id, message_id, button_id);
if (r_url.is_error()) {
return promise.set_error(r_url.move_as_error());
}
td_->create_handler<RequestUrlAuthQuery>(std::move(promise))
->send(r_url.move_as_ok(), dialog_id, message_id, button_id);
}
void MessagesManager::get_login_url(DialogId dialog_id, MessageId message_id, int32 button_id, bool allow_write_access,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) {
auto r_url = get_login_button_url(dialog_id, message_id, button_id);
if (r_url.is_error()) {
return promise.set_error(r_url.move_as_error());
}
td_->create_handler<AcceptUrlAuthQuery>(std::move(promise))
->send(r_url.move_as_ok(), dialog_id, message_id, button_id, allow_write_access);
}
void MessagesManager::get_link_login_url_info(const string &url,
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
if (G()->close_flag()) {
return promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url, false));
}
td_->create_handler<RequestUrlAuthQuery>(std::move(promise))->send(url, DialogId(), MessageId(), 0);
}
void MessagesManager::get_link_login_url(const string &url, bool allow_write_access,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) {
td_->create_handler<AcceptUrlAuthQuery>(std::move(promise))
->send(url, DialogId(), MessageId(), 0, allow_write_access);
}
void MessagesManager::load_secret_thumbnail(FileId thumbnail_file_id) {
class Callback : public FileManager::DownloadCallback {
public:
@ -8899,7 +8735,7 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api
auto it = being_uploaded_files_.find(file_id);
if (it == being_uploaded_files_.end()) {
// callback may be called just before the file upload was cancelled
// callback may be called just before the file upload was canceled
return;
}
@ -8911,7 +8747,7 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it
// file upload should be already cancelled in cancel_send_message_query, it shouldn't happen
// file upload should be already canceled in cancel_send_message_query, it shouldn't happen
LOG(ERROR) << "Message with a media has already been deleted";
return;
}
@ -9003,11 +8839,10 @@ 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), layer),
file_id, thumbnail_file_id);
get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail)), file_id,
thumbnail_file_id);
}
void MessagesManager::on_upload_media_error(FileId file_id, Status status) {
@ -9021,7 +8856,7 @@ void MessagesManager::on_upload_media_error(FileId file_id, Status status) {
auto it = being_uploaded_files_.find(file_id);
if (it == being_uploaded_files_.end()) {
// callback may be called just before the file upload was cancelled
// callback may be called just before the file upload was canceled
return;
}
@ -9095,7 +8930,7 @@ void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id,
auto it = being_uploaded_thumbnails_.find(thumbnail_file_id);
if (it == being_uploaded_thumbnails_.end()) {
// callback may be called just before the thumbnail upload was cancelled
// callback may be called just before the thumbnail upload was canceled
return;
}
@ -9108,7 +8943,7 @@ void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id,
Message *m = get_message(full_message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it
// thumbnail file upload should be already cancelled in cancel_send_message_query
// thumbnail file upload should be already canceled in cancel_send_message_query
LOG(ERROR) << "Message with a media has already been deleted";
return;
}
@ -11112,7 +10947,7 @@ void MessagesManager::delete_dialog_messages_from_user(DialogId dialog_id, UserI
case DialogType::Channel: {
channel_id = dialog_id.get_channel_id();
auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
if (channel_type != ChannelType::Megagroup) {
if (channel_type != ContactsManager::ChannelType::Megagroup) {
return promise.set_error(Status::Error(3, "The method is available only for supergroup chats"));
}
channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
@ -11352,6 +11187,9 @@ void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise<Unit> &&prom
if (remove_recently_found_dialog_internal(dialog_id)) {
save_recently_found_dialogs();
}
if (dialog_id.get_type() == DialogType::Channel) {
G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id));
}
close_dialog(d);
promise.set_value(Unit());
@ -12117,16 +11955,12 @@ void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id,
Dialog *d = get_dialog_force(dialog_id, source);
if (d != nullptr) {
if (max_unavailable_message_id > d->last_new_message_id && from_update) {
if (d->last_new_message_id.is_valid()) {
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Tried to set " << dialog_id << " max unavailable message to " << max_unavailable_message_id
<< " from " << source << ", but last new message is " << d->last_new_message_id;
}
max_unavailable_message_id = d->last_new_message_id;
} else if (max_unavailable_message_id.is_valid() && max_unavailable_message_id.is_server()) {
set_dialog_last_new_message_id(d, max_unavailable_message_id, source);
if (d->last_new_message_id.is_valid() && max_unavailable_message_id > d->last_new_message_id && from_update) {
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Tried to set " << dialog_id << " max unavailable message to " << max_unavailable_message_id
<< " from " << source << ", but last new message is " << d->last_new_message_id;
}
max_unavailable_message_id = d->last_new_message_id;
}
if (d->max_unavailable_message_id == max_unavailable_message_id) {
@ -12238,7 +12072,7 @@ void MessagesManager::on_update_dialog_online_member_count_timeout(DialogId dial
} else {
td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(),
td_api::make_object<td_api::supergroupMembersFilterRecent>(),
string(), 0, 200, 200, true, Auto());
string(), 0, 200, 200, Auto());
}
return;
}
@ -12272,6 +12106,7 @@ MessageId MessagesManager::get_message_id(const tl_object_ptr<telegram_api::Mess
}
DialogId MessagesManager::get_message_dialog_id(const tl_object_ptr<telegram_api::Message> &message_ptr) {
CHECK(message_ptr != nullptr);
switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID: {
auto message = static_cast<const telegram_api::messageEmpty *>(message_ptr.get());
@ -12590,6 +12425,7 @@ void MessagesManager::init() {
if (is_authorized && td_->auth_manager_->is_bot()) {
disable_get_dialog_filter_ = true;
}
authorization_date_ = G()->shared_config().get_option_integer("authorization_date");
if (was_authorized_user) {
vector<NotificationSettingsScope> scopes{NotificationSettingsScope::Private, NotificationSettingsScope::Group,
@ -12995,6 +12831,8 @@ void MessagesManager::init() {
void MessagesManager::on_authorization_success() {
CHECK(td_->auth_manager_->is_authorized());
authorization_date_ = G()->shared_config().get_option_integer("authorization_date");
if (td_->auth_manager_->is_bot()) {
disable_get_dialog_filter_ = true;
return;
@ -13782,7 +13620,7 @@ std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::creat
}
int32 ttl = message_info.ttl;
bool is_content_secret = is_secret_message_content(ttl, content_type); // should be calculated before TTL is adjusted
bool is_content_secret = is_secret_message_content(ttl, content_type); // must be calculated before TTL is adjusted
if (ttl < 0 || (message_id.is_scheduled() && ttl != 0)) {
LOG(ERROR) << "Wrong TTL = " << ttl << " received in " << message_id << " in " << dialog_id;
ttl = 0;
@ -13811,6 +13649,10 @@ std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::creat
bool has_forward_info = message_info.forward_header != nullptr;
if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id && have_dialog_info_force(sender_dialog_id)) {
force_create_dialog(sender_dialog_id, "create_message", true);
}
LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id << "/" << sender_dialog_id;
auto message = make_unique<Message>();
@ -17825,169 +17667,8 @@ void MessagesManager::on_get_public_message_link(FullMessageId full_message_id,
std::move(url), std::move(html)};
}
Result<MessagesManager::MessageLinkInfo> MessagesManager::get_message_link_info(Slice url) {
if (url.empty()) {
return Status::Error("URL must be non-empty");
}
url.truncate(url.find('#'));
string lower_cased_url = to_lower(url);
url = lower_cased_url;
Slice username;
Slice channel_id_slice;
Slice message_id_slice;
Slice comment_message_id_slice = "0";
bool is_single = false;
bool for_comment = false;
if (begins_with(url, "tg:")) {
url = url.substr(3);
if (begins_with(url, "//")) {
url = url.substr(2);
}
// resolve?domain=username&post=12345&single
// privatepost?channel=123456789&msg_id=12345
bool is_resolve = false;
if (begins_with(url, "resolve")) {
url = url.substr(7);
is_resolve = true;
} else if (begins_with(url, "privatepost")) {
url = url.substr(11);
} else {
return Status::Error("Wrong message link URL");
}
if (begins_with(url, "/")) {
url = url.substr(1);
}
if (!begins_with(url, "?")) {
return Status::Error("Wrong message link URL");
}
url = url.substr(1);
auto args = full_split(url, '&');
for (auto arg : args) {
auto key_value = split(arg, '=');
if (is_resolve) {
if (key_value.first == "domain") {
username = key_value.second;
}
if (key_value.first == "post") {
message_id_slice = key_value.second;
}
} else {
if (key_value.first == "channel") {
channel_id_slice = key_value.second;
}
if (key_value.first == "msg_id") {
message_id_slice = key_value.second;
}
}
if (key_value.first == "single") {
is_single = true;
}
if (key_value.first == "comment") {
comment_message_id_slice = key_value.second;
}
if (key_value.first == "thread") {
for_comment = true;
}
}
} else {
if (begins_with(url, "http://") || begins_with(url, "https://")) {
url = url.substr(url[4] == 's' ? 8 : 7);
}
// t.me/c/123456789/12345
// t.me/username/12345?single
vector<Slice> t_me_urls{Slice("t.me/"), Slice("telegram.me/"), Slice("telegram.dog/")};
string cur_t_me_url = G()->shared_config().get_option_string("t_me_url");
if (begins_with(cur_t_me_url, "http://") || begins_with(cur_t_me_url, "https://")) {
Slice t_me_url = cur_t_me_url;
t_me_url = t_me_url.substr(url[4] == 's' ? 8 : 7);
if (!td::contains(t_me_urls, t_me_url)) {
t_me_urls.push_back(t_me_url);
}
}
for (auto t_me_url : t_me_urls) {
if (begins_with(url, t_me_url)) {
url = url.substr(t_me_url.size());
auto username_end_pos = url.find('/');
if (username_end_pos == Slice::npos) {
return Status::Error("Wrong message link URL");
}
username = url.substr(0, username_end_pos);
url = url.substr(username_end_pos + 1);
if (username == "c") {
username = Slice();
auto channel_id_end_pos = url.find('/');
if (channel_id_end_pos == Slice::npos) {
return Status::Error("Wrong message link URL");
}
channel_id_slice = url.substr(0, channel_id_end_pos);
url = url.substr(channel_id_end_pos + 1);
}
auto query_pos = url.find('?');
message_id_slice = url.substr(0, query_pos);
if (query_pos != Slice::npos) {
auto args = full_split(url.substr(query_pos + 1), '&');
for (auto arg : args) {
auto key_value = split(arg, '=');
if (key_value.first == "single") {
is_single = true;
}
if (key_value.first == "comment") {
comment_message_id_slice = key_value.second;
}
if (key_value.first == "thread") {
for_comment = true;
}
}
}
break;
}
}
}
ChannelId channel_id;
if (username.empty()) {
auto r_channel_id = to_integer_safe<int32>(channel_id_slice);
if (r_channel_id.is_error() || !ChannelId(r_channel_id.ok()).is_valid()) {
return Status::Error("Wrong channel ID");
}
channel_id = ChannelId(r_channel_id.ok());
}
auto r_message_id = to_integer_safe<int32>(message_id_slice);
if (r_message_id.is_error() || !ServerMessageId(r_message_id.ok()).is_valid()) {
return Status::Error("Wrong message ID");
}
auto r_comment_message_id = to_integer_safe<int32>(comment_message_id_slice);
if (r_comment_message_id.is_error() ||
!(r_comment_message_id.ok() == 0 || ServerMessageId(r_comment_message_id.ok()).is_valid())) {
return Status::Error("Wrong comment message ID");
}
MessageLinkInfo info;
info.username = username.str();
info.channel_id = channel_id;
info.message_id = MessageId(ServerMessageId(r_message_id.ok()));
info.comment_message_id = MessageId(ServerMessageId(r_comment_message_id.ok()));
info.is_single = is_single;
info.for_comment = for_comment;
LOG(INFO) << "Have link to " << info.message_id << " in chat @" << info.username << "/" << channel_id.get();
return std::move(info);
}
void MessagesManager::get_message_link_info(Slice url, Promise<MessageLinkInfo> &&promise) {
auto r_message_link_info = get_message_link_info(url);
auto r_message_link_info = LinkManager::get_message_link_info(url);
if (r_message_link_info.is_error()) {
return promise.set_error(Status::Error(400, r_message_link_info.error().message()));
}
@ -20088,7 +19769,7 @@ void MessagesManager::open_dialog(Dialog *d) {
if (participant_count < 195) { // include unknown participant_count
td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(),
td_api::make_object<td_api::supergroupMembersFilterRecent>(),
string(), 0, 200, 200, true, Auto());
string(), 0, 200, 200, Auto());
}
}
get_channel_difference(dialog_id, d->pts, true, "open_dialog");
@ -20215,7 +19896,7 @@ td_api::object_ptr<td_api::ChatType> MessagesManager::get_chat_type_object(Dialo
auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
return td_api::make_object<td_api::chatTypeSupergroup>(
td_->contacts_manager_->get_supergroup_id_object(channel_id, "chatTypeSupergroup"),
channel_type != ChannelType::Megagroup);
channel_type != ContactsManager::ChannelType::Megagroup);
}
case DialogType::SecretChat: {
auto secret_chat_id = dialog_id.get_secret_chat_id();
@ -23474,13 +23155,13 @@ Status MessagesManager::can_send_message(DialogId dialog_id) const {
auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
switch (channel_type) {
case ChannelType::Unknown:
case ChannelType::Megagroup:
case ContactsManager::ChannelType::Unknown:
case ContactsManager::ChannelType::Megagroup:
if (!channel_status.can_send_messages()) {
return Status::Error(400, "Have no rights to send a message");
}
break;
case ChannelType::Broadcast: {
case ContactsManager::ChannelType::Broadcast: {
if (!channel_status.can_post_messages()) {
return Status::Error(400, "Need administrator rights in the channel chat");
}
@ -24079,8 +23760,7 @@ 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 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);
auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice());
if (secret_input_media.empty()) {
LOG(INFO) << "Ask to upload encrypted file " << file_id;
CHECK(file_view.is_encrypted_secret());
@ -24531,7 +24211,7 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
send_closure(td_->create_net_actor<SendSecretMessageActor>(), &SendSecretMessageActor::send, dialog_id,
m->reply_to_random_id, m->ttl, message_text->text,
get_secret_input_media(content, td_, nullptr, BufferSlice(), layer),
get_secret_input_media(content, td_, nullptr, BufferSlice()),
get_input_secret_message_entities(message_text->entities, layer), m->via_bot_user_id,
m->media_album_id, m->disable_notification, random_id);
} else {
@ -24656,14 +24336,14 @@ Result<MessageId> MessagesManager::send_bot_start_message(UserId bot_user_id, Di
return Status::Error(3, "Can't access the chat");
}
switch (td_->contacts_manager_->get_channel_type(channel_id)) {
case ChannelType::Megagroup:
case ContactsManager::ChannelType::Megagroup:
if (!bot_data.can_join_groups) {
return Status::Error(5, "The bot can't join groups");
}
break;
case ChannelType::Broadcast:
case ContactsManager::ChannelType::Broadcast:
return Status::Error(3, "Bots can't be invited to channel chats. Add them as administrators instead");
case ChannelType::Unknown:
case ContactsManager::ChannelType::Unknown:
default:
UNREACHABLE();
}
@ -25111,7 +24791,8 @@ bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const {
return false;
}
return td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast;
return td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) ==
ContactsManager::ChannelType::Broadcast;
}
bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const {
@ -25451,7 +25132,7 @@ void MessagesManager::edit_message_media(FullMessageId full_message_id,
return promise.set_error(r_new_reply_markup.move_as_error());
}
cancel_edit_message_media(dialog_id, m, "Cancelled by new editMessageMedia request");
cancel_edit_message_media(dialog_id, m, "Canceled by new editMessageMedia request");
m->edited_content =
dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send, MessageCopyOptions());
@ -27474,7 +27155,7 @@ MessagesManager::MessageNotificationGroup MessagesManager::get_message_notificat
VLOG(notifications) << "Loaded " << r_value.ok() << " from database by " << group_id;
d = get_dialog_force(r_value.ok().dialog_id, "get_message_notification_group_force");
} else {
CHECK(r_value.error().message() == "Not found");
LOG_CHECK(r_value.error().message() == "Not found") << r_value.error();
VLOG(notifications) << "Failed to load " << group_id << " from database";
}
dialog_db->commit_transaction().ensure();
@ -28230,6 +27911,9 @@ bool MessagesManager::is_dialog_message_notification_disabled(DialogId dialog_id
default:
UNREACHABLE();
}
if (message_date < authorization_date_) {
return true;
}
return false;
}
@ -29282,7 +28966,7 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
if (it == being_sent_messages_.end()) {
// we can't receive fail more than once
// but message can be successfully sent before
if (error.code() != NetQuery::Cancelled) {
if (error.code() != NetQuery::Canceled) {
LOG(ERROR) << "Receive error " << error << " about successfully sent message with random_id = " << random_id;
}
return;
@ -29300,7 +28984,7 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
LOG(INFO) << "Fail to send already deleted by the user or sent to inaccessible chat " << full_message_id;
return;
}
LOG_IF(ERROR, error.code() == NetQuery::Cancelled)
LOG_IF(ERROR, error.code() == NetQuery::Canceled)
<< "Receive error " << error << " about sent message with random_id = " << random_id;
auto dialog_id = full_message_id.get_dialog_id();
@ -29574,8 +29258,7 @@ void MessagesManager::fail_send_message(FullMessageId full_message_id, int error
}
void MessagesManager::fail_send_message(FullMessageId full_message_id, Status error) {
fail_send_message(full_message_id, error.code() > 0 ? error.code() : 500,
error.message().str()); // TODO CHECK that status has always a code
fail_send_message(full_message_id, error.code(), error.message().str());
}
void MessagesManager::fail_edit_message_media(FullMessageId full_message_id, Status &&error) {
@ -30425,6 +30108,15 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search,
if (dialog_id.is_valid()) {
if (have_input_peer(dialog_id, AccessRights::Read)) {
if (!force && reload_voice_chat_on_search_usernames_.count(username)) {
reload_voice_chat_on_search_usernames_.erase(username);
if (dialog_id.get_type() == DialogType::Channel) {
td_->contacts_manager_->reload_channel_full(dialog_id.get_channel_id(), std::move(promise),
"search_public_dialog");
return DialogId();
}
}
if (td_->auth_manager_->is_bot()) {
force_create_dialog(dialog_id, "search_public_dialog", true);
} else {
@ -30451,6 +30143,10 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search,
return DialogId();
}
void MessagesManager::reload_voice_chat_on_search(const string &username) {
reload_voice_chat_on_search_usernames_.insert(clean_username(username));
}
void MessagesManager::send_get_dialog_notification_settings_query(DialogId dialog_id, Promise<Unit> &&promise) {
if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
LOG(WARNING) << "Can't get notification settings for " << dialog_id;
@ -32348,14 +32044,14 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
}
if (max_message_id != MessageId() && message_id > max_message_id) {
if (!message->from_database) {
LOG(DEBUG) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source
<< ". Last is " << max_message_id << ", channel difference " << debug_channel_difference_dialog_
<< " " << to_string(get_message_object(dialog_id, message.get()));
LOG(ERROR) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source
<< ". Maximum allowed is " << max_message_id << ", last is " << d->last_message_id
<< ", being added message is " << d->being_added_message_id << ", channel difference "
<< debug_channel_difference_dialog_ << " "
<< to_string(get_message_object(dialog_id, message.get()));
dump_debug_message_op(d, 3);
// keep consistent with need_channel_difference_to_add_message
if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read) &&
dialog_id != debug_channel_difference_dialog_) {
if (need_channel_difference_to_add_message(dialog_id, nullptr)) {
LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
@ -32413,8 +32109,8 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
(message->reply_markup->type == ReplyMarkup::Type::RemoveKeyboard ||
(message->reply_markup->type == ReplyMarkup::Type::ForceReply && !message->reply_markup->is_personal)) &&
!td_->auth_manager_->is_bot()) {
if (*need_update && message->reply_markup->is_personal) { // if this keyboard is for us
if (d->reply_markup_message_id != MessageId()) {
if (from_update && message->reply_markup->is_personal) { // if this keyboard is for us
if (d->reply_markup_message_id != MessageId() && message_id > d->reply_markup_message_id) {
const Message *old_message = get_message_force(d, d->reply_markup_message_id, "add_message_to_dialog 1");
if (old_message == nullptr ||
(old_message->sender_user_id.is_valid() && old_message->sender_user_id == message->sender_user_id)) {
@ -34162,7 +33858,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr<Dialog> &&d,
break;
case DialogType::Channel: {
auto channel_type = td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id());
if (channel_type == ChannelType::Broadcast) {
if (channel_type == ContactsManager::ChannelType::Broadcast) {
d->last_read_outbox_message_id = MessageId::max();
d->is_last_read_outbox_message_id_inited = true;
}
@ -34437,12 +34133,6 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_datab
LOG(ERROR) << "Drop invalid last_new_message_id " << d->last_new_message_id << " in " << dialog_id;
d->last_new_message_id = MessageId();
}
if (!d->last_new_message_id.is_valid() && d->max_unavailable_message_id.is_valid() &&
d->max_unavailable_message_id.is_server()) {
LOG(ERROR) << "Bugfixing wrong last_new_message_id with max_unavailable_message_id to "
<< d->max_unavailable_message_id << " in " << dialog_id;
set_dialog_last_new_message_id(d, d->max_unavailable_message_id, "fix_new_dialog 11");
}
if (last_message_id.is_valid()) {
if ((last_message_id.is_server() || dialog_type == DialogType::SecretChat) && !d->last_new_message_id.is_valid()) {
LOG(ERROR) << "Bugfixing wrong last_new_message_id to " << last_message_id << " in " << dialog_id;
@ -35650,7 +35340,7 @@ string MessagesManager::get_channel_pts_key(DialogId dialog_id) {
}
int32 MessagesManager::load_channel_pts(DialogId dialog_id) const {
if (G()->ignore_background_updates()) {
if (G()->ignore_background_updates() || !have_input_peer(dialog_id, AccessRights::Read)) {
G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); // just in case
return 0;
}
@ -35701,7 +35391,7 @@ void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *sour
repair_channel_server_unread_count(d);
}
}
if (!G()->ignore_background_updates()) {
if (!G()->ignore_background_updates() && have_input_peer(d->dialog_id, AccessRights::Read)) {
G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts));
}
} else if (new_pts < d->pts) {
@ -35712,11 +35402,13 @@ void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *sour
bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id,
const tl_object_ptr<telegram_api::Message> &message_ptr) {
// keep consistent with add_message_to_dialog
if (dialog_id.get_type() != DialogType::Channel || !have_input_peer(dialog_id, AccessRights::Read) ||
dialog_id == debug_channel_difference_dialog_) {
return false;
}
if (message_ptr == nullptr) {
return true;
}
if (get_message_dialog_id(message_ptr) != dialog_id) {
return false;
}
@ -36463,13 +36155,13 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) {
break;
case DialogType::Channel:
switch (td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())) {
case ChannelType::Broadcast:
case ContactsManager::ChannelType::Broadcast:
category = TopDialogCategory::Channel;
break;
case ChannelType::Megagroup:
case ContactsManager::ChannelType::Megagroup:
category = TopDialogCategory::Group;
break;
case ChannelType::Unknown:
case ContactsManager::ChannelType::Unknown:
break;
default:
UNREACHABLE();

View File

@ -28,6 +28,7 @@
#include "td/telegram/MessageContentType.h"
#include "td/telegram/MessageCopyOptions.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageReplyInfo.h"
#include "td/telegram/MessagesDb.h"
#include "td/telegram/MessageSearchFilter.h"
@ -369,6 +370,8 @@ class MessagesManager : public Actor {
DialogId search_public_dialog(const string &username_to_search, bool force, Promise<Unit> &&promise);
void reload_voice_chat_on_search(const string &username);
Result<MessageId> send_message(
DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
tl_object_ptr<td_api::messageSendOptions> &&options, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
@ -585,18 +588,6 @@ class MessagesManager : public Actor {
void on_get_public_message_link(FullMessageId full_message_id, bool for_group, string url, string html);
struct MessageLinkInfo {
string username;
// or
ChannelId channel_id;
MessageId message_id;
bool is_single = false;
DialogId comment_dialog_id;
MessageId comment_message_id;
bool for_comment = false;
};
void get_message_link_info(Slice url, Promise<MessageLinkInfo> &&promise);
td_api::object_ptr<td_api::messageLinkInfo> get_message_link_info_object(const MessageLinkInfo &info) const;
@ -802,17 +793,6 @@ class MessagesManager : public Actor {
void get_dialog_statistics_url(DialogId dialog_id, const string &parameters, bool is_dark,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
void get_login_url_info(DialogId dialog_id, MessageId message_id, int32 button_id,
Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_login_url(DialogId dialog_id, MessageId message_id, int32 button_id, bool allow_write_access,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
void get_link_login_url_info(const string &url, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
void get_link_login_url(const string &url, bool allow_write_access,
Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
void on_authorization_success();
void before_get_difference();
@ -917,6 +897,8 @@ class MessagesManager : public Actor {
void stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise);
Result<string> get_login_button_url(FullMessageId full_message_id, int32 button_id);
Result<ServerMessageId> get_invoice_message_id(FullMessageId full_message_id);
Result<ServerMessageId> get_payment_successful_message_id(FullMessageId full_message_id);
@ -1657,7 +1639,7 @@ class MessagesManager : public Actor {
static constexpr int32 MAX_GET_HISTORY = 100; // server side limit
static constexpr int32 MAX_SEARCH_MESSAGES = 100; // server side limit
static constexpr int32 MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN = 4; // server side limit
static constexpr int32 MIN_CHANNEL_DIFFERENCE = 10;
static constexpr int32 MIN_CHANNEL_DIFFERENCE = 1;
static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 1000000; // server side limit
static constexpr int32 MAX_CHANNEL_DIFFERENCE = MAX_BOT_CHANNEL_DIFFERENCE;
static constexpr int32 MAX_RECENTLY_FOUND_DIALOGS = 30; // some reasonable value
@ -2638,8 +2620,6 @@ class MessagesManager : public Actor {
void ttl_db_loop(double server_now);
void ttl_db_on_result(Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> r_result, bool dummy);
static Result<MessageLinkInfo> get_message_link_info(Slice url);
void on_get_message_link_dialog(MessageLinkInfo &&info, Promise<MessageLinkInfo> &&promise);
void on_get_message_link_message(MessageLinkInfo &&info, DialogId dialog_id, Promise<MessageLinkInfo> &&promise);
@ -2975,8 +2955,6 @@ class MessagesManager : public Actor {
void suffix_load_till_date(Dialog *d, int32 date, Promise<> promise);
void suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<> promise);
Result<string> get_login_button_url(DialogId dialog_id, MessageId message_id, int32 button_id);
bool is_broadcast_channel(DialogId dialog_id) const;
bool is_deleted_secret_chat(const Dialog *d) const;
@ -3286,6 +3264,7 @@ class MessagesManager : public Actor {
std::unordered_map<string, ResolvedUsername> resolved_usernames_;
std::unordered_map<string, DialogId> inaccessible_resolved_usernames_;
std::unordered_set<string> reload_voice_chat_on_search_usernames_;
struct PendingOnGetDialogs {
FolderId folder_id;
@ -3350,6 +3329,8 @@ class MessagesManager : public Actor {
uint32 scheduled_messages_sync_generation_ = 1;
int64 authorization_date_ = 0;
DialogId removed_sponsored_dialog_id_;
DialogId sponsored_dialog_id_;
DialogSource sponsored_dialog_source_;

View File

@ -3031,7 +3031,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);
send_closure(td_->auth_manager_actor_, &AuthManager::on_authorization_lost, "SESSION_REVOKE");
} else {
LOG(ERROR) << "Receive unencrypted SESSION_REVOKE push notification";
}

View File

@ -6,10 +6,12 @@
//
#include "td/telegram/PasswordManager.h"
#include "td/telegram/ConfigManager.h"
#include "td/telegram/DhCache.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/SuggestedAction.h"
#include "td/telegram/TdDb.h"
#include "td/mtproto/DhHandshake.h"
@ -303,6 +305,9 @@ void PasswordManager::on_finish_create_temp_password(Result<TempPasswordState> r
}
void PasswordManager::get_full_state(string password, Promise<PasswordFullState> promise) {
send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action,
SuggestedAction{SuggestedAction::Type::CheckPassword});
do_get_state(PromiseCreator::lambda([password = std::move(password), promise = std::move(promise),
actor_id = actor_id(this)](Result<PasswordState> r_state) mutable {
if (r_state.is_error()) {

View File

@ -8,7 +8,6 @@
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/SecureStorage.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"

View File

@ -6,8 +6,10 @@
//
#include "td/telegram/PhoneNumberManager.h"
#include "td/telegram/ConfigManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/SuggestedAction.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
@ -48,6 +50,8 @@ void PhoneNumberManager::set_phone_number(uint64 query_id, string phone_number,
switch (type_) {
case Type::ChangePhone:
send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action,
SuggestedAction{SuggestedAction::Type::CheckPhoneNumber});
return process_send_code_result(query_id, send_code_helper_.send_change_phone_code(phone_number, settings));
case Type::VerifyPhone:
return process_send_code_result(query_id, send_code_helper_.send_verify_phone_code(phone_number, settings));

View File

@ -140,20 +140,15 @@ static StringBuilder &operator<<(StringBuilder &string_builder, PhotoFormat form
}
static FileId register_photo(FileManager *file_manager, const PhotoSizeSource &source, int64 id, int64 access_hash,
std::string file_reference,
tl_object_ptr<telegram_api::fileLocationToBeDeprecated> &&location,
DialogId owner_dialog_id, int32 file_size, DcId dc_id, PhotoFormat format) {
int32 local_id = location->local_id_;
int64 volume_id = location->volume_id_;
LOG(DEBUG) << "Receive " << format << " photo of type " << source.get_file_type() << " in [" << dc_id << ","
<< volume_id << "," << local_id << "]. Id: (" << id << ", " << access_hash << ")";
auto suggested_name = PSTRING() << static_cast<uint64>(volume_id) << "_" << static_cast<uint64>(local_id) << '.'
<< format;
std::string file_reference, DialogId owner_dialog_id, int32 file_size, DcId dc_id,
PhotoFormat format) {
LOG(DEBUG) << "Receive " << format << " photo " << id << " of type " << source.get_file_type() << " from " << dc_id;
auto suggested_name = PSTRING() << source.get_unique_name(id) << '.' << format;
auto file_location_source = owner_dialog_id.get_type() == DialogType::SecretChat ? FileLocationSource::FromUser
: FileLocationSource::FromServer;
return file_manager->register_remote(
FullRemoteFileLocation(source, id, access_hash, local_id, volume_id, dc_id, std::move(file_reference)),
file_location_source, owner_dialog_id, file_size, 0, std::move(suggested_name));
FullRemoteFileLocation(source, id, access_hash, dc_id, std::move(file_reference)), file_location_source,
owner_dialog_id, file_size, 0, std::move(suggested_name));
}
ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash,
@ -172,11 +167,11 @@ ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64
result.id = profile_photo->photo_id_;
result.minithumbnail = profile_photo->stripped_thumb_.as_slice().str();
result.small_file_id =
register_photo(file_manager, {DialogId(user_id), user_access_hash, false}, result.id, 0, "",
std::move(profile_photo->photo_small_), DialogId(), 0, dc_id, PhotoFormat::Jpeg);
register_photo(file_manager, {DialogId(user_id), user_access_hash, false}, result.id, 0 /*access_hash*/,
"" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
result.big_file_id =
register_photo(file_manager, {DialogId(user_id), user_access_hash, true}, result.id, 0, "",
std::move(profile_photo->photo_big_), DialogId(), 0, dc_id, PhotoFormat::Jpeg);
register_photo(file_manager, {DialogId(user_id), user_access_hash, true}, result.id, 0 /*access_hash*/,
"" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
break;
}
default:
@ -200,17 +195,9 @@ tl_object_ptr<td_api::profilePhoto> get_profile_photo_object(FileManager *file_m
bool operator==(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
bool location_differs = lhs.small_file_id != rhs.small_file_id || lhs.big_file_id != rhs.big_file_id;
bool id_differs;
if (lhs.id == -1 && rhs.id == -1) {
// group chat photo
id_differs = location_differs;
} else {
id_differs = lhs.id != rhs.id;
}
bool id_differs = lhs.id != rhs.id;
if (location_differs) {
LOG_IF(ERROR, !id_differs) << "Photo " << lhs.id << " location has changed. First profilePhoto: " << lhs
<< ", second profilePhoto: " << rhs;
return false;
}
return lhs.has_animation == rhs.has_animation && lhs.minithumbnail == rhs.minithumbnail && !id_differs;
@ -240,11 +227,10 @@ DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int6
auto dc_id = DcId::create(chat_photo->dc_id_);
result.has_animation = (chat_photo->flags_ & telegram_api::chatPhoto::HAS_VIDEO_MASK) != 0;
result.minithumbnail = chat_photo->stripped_thumb_.as_slice().str();
result.small_file_id =
register_photo(file_manager, {dialog_id, dialog_access_hash, false}, 0, 0, "",
std::move(chat_photo->photo_small_), DialogId(), 0, dc_id, PhotoFormat::Jpeg);
result.big_file_id = register_photo(file_manager, {dialog_id, dialog_access_hash, true}, 0, 0, "",
std::move(chat_photo->photo_big_), DialogId(), 0, dc_id, PhotoFormat::Jpeg);
result.small_file_id = register_photo(file_manager, {dialog_id, dialog_access_hash, false}, chat_photo->photo_id_,
0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
result.big_file_id = register_photo(file_manager, {dialog_id, dialog_access_hash, true}, chat_photo->photo_id_, 0,
"", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
break;
}
@ -278,7 +264,7 @@ vector<FileId> dialog_photo_get_file_ids(const DialogPhoto &dialog_photo) {
return result;
}
DialogPhoto as_fake_dialog_photo(const Photo &photo) {
DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id) {
DialogPhoto result;
if (!photo.is_empty()) {
for (auto &size : photo.photos) {
@ -288,9 +274,10 @@ DialogPhoto as_fake_dialog_photo(const Photo &photo) {
result.big_file_id = size.file_id;
}
}
result.minithumbnail = photo.minithumbnail;
result.has_animation = !photo.animations.empty();
if (!result.small_file_id.is_valid() || !result.big_file_id.is_valid()) {
LOG(ERROR) << "Failed to convert " << photo << " to chat photo";
LOG(ERROR) << "Failed to convert " << photo << " to chat photo of " << dialog_id;
return DialogPhoto();
}
}
@ -299,7 +286,7 @@ DialogPhoto as_fake_dialog_photo(const Photo &photo) {
ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo) {
ProfilePhoto result;
static_cast<DialogPhoto &>(result) = as_fake_dialog_photo(photo);
static_cast<DialogPhoto &>(result) = as_fake_dialog_photo(photo, DialogId(user_id));
if (!result.small_file_id.is_valid()) {
return result;
}
@ -349,14 +336,12 @@ PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice
// generate some random remote location to save
auto dc_id = DcId::invalid();
auto local_id = -(Random::secure_int32() & 0x7FFFFFFF);
auto volume_id = Random::secure_int64();
auto photo_id = -(Random::secure_int64() & std::numeric_limits<int64>::max());
res.file_id = file_manager->register_remote(
FullRemoteFileLocation(PhotoSizeSource(FileType::EncryptedThumbnail, 't'), 0, 0, local_id, volume_id, dc_id,
string()),
FullRemoteFileLocation(PhotoSizeSource(FileType::EncryptedThumbnail, 't'), photo_id, 0, dc_id, string()),
FileLocationSource::FromServer, owner_dialog_id, res.size, 0,
PSTRING() << static_cast<uint64>(volume_id) << "_" << static_cast<uint64>(local_id) << ".jpg");
PSTRING() << static_cast<uint64>(photo_id) << ".jpg");
file_manager->set_content(res.file_id, std::move(bytes));
return res;
@ -368,7 +353,6 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
PhotoFormat format) {
CHECK(size_ptr != nullptr);
tl_object_ptr<telegram_api::fileLocationToBeDeprecated> location;
string type;
PhotoSize res;
BufferSlice content;
@ -379,7 +363,6 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
auto size = move_tl_object_as<telegram_api::photoSize>(size_ptr);
type = std::move(size->type_);
location = std::move(size->location_);
res.dimensions = get_dimensions(size->w_, size->h_, "photoSize");
res.size = size->size_;
@ -389,7 +372,6 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
auto size = move_tl_object_as<telegram_api::photoCachedSize>(size_ptr);
type = std::move(size->type_);
location = std::move(size->location_);
CHECK(size->bytes_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max()));
res.dimensions = get_dimensions(size->w_, size->h_, "photoCachedSize");
res.size = static_cast<int32>(size->bytes_.size());
@ -404,7 +386,8 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
LOG(DEBUG) << "Receive unexpected JPEG minithumbnail";
} else {
LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo of format " << format;
LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo " << id << " from " << source << " of format "
<< format;
}
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
return std::string("");
@ -422,13 +405,12 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
auto size = move_tl_object_as<telegram_api::photoSizeProgressive>(size_ptr);
if (size->sizes_.empty()) {
LOG(ERROR) << "Receive " << to_string(size);
LOG(ERROR) << "Receive photo " << id << " from " << source << " with empty size " << to_string(size);
return std::move(res);
}
std::sort(size->sizes_.begin(), size->sizes_.end());
type = std::move(size->type_);
location = std::move(size->location_);
res.dimensions = get_dimensions(size->w_, size->h_, "photoSizeProgressive");
res.size = size->sizes_.back();
size->sizes_.pop_back();
@ -439,7 +421,8 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
case telegram_api::photoPathSize::ID: {
auto size = move_tl_object_as<telegram_api::photoPathSize>(size_ptr);
if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp) {
LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo of format " << format;
LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo " << id << " from " << source << " of format "
<< format;
return std::move(res);
}
return size->bytes_.as_slice().str();
@ -450,17 +433,21 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
}
if (type.size() != 1) {
res.type = 0;
LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
res.type = 0;
} else {
res.type = static_cast<uint8>(type[0]);
if (res.type >= 128) {
LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
res.type = 0;
}
}
if (source.get_type() == PhotoSizeSource::Type::Thumbnail) {
source.thumbnail().thumbnail_type = res.type;
}
res.file_id = register_photo(file_manager, source, id, access_hash, file_reference, std::move(location),
owner_dialog_id, res.size, dc_id, format);
res.file_id =
register_photo(file_manager, source, id, access_hash, file_reference, owner_dialog_id, res.size, dc_id, format);
if (!content.empty()) {
file_manager->set_content(res.file_id, std::move(content));
@ -481,6 +468,10 @@ AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource sour
LOG(ERROR) << "Wrong videoSize \"" << size->type_ << "\" in " << to_string(size);
}
res.type = static_cast<uint8>(size->type_[0]);
if (res.type >= 128) {
LOG(ERROR) << "Wrong videoSize \"" << res.type << "\" " << res;
res.type = 0;
}
res.dimensions = get_dimensions(size->w_, size->h_, "get_animation_size");
res.size = size->size_;
if ((size->flags_ & telegram_api::videoSize::VIDEO_START_TS_MASK) != 0) {
@ -491,8 +482,8 @@ AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource sour
source.thumbnail().thumbnail_type = res.type;
}
res.file_id = register_photo(file_manager, source, id, access_hash, file_reference, std::move(size->location_),
owner_dialog_id, res.size, dc_id, PhotoFormat::Mpeg4);
res.file_id = register_photo(file_manager, source, id, access_hash, file_reference, owner_dialog_id, res.size, dc_id,
PhotoFormat::Mpeg4);
return res;
}
@ -957,20 +948,14 @@ StringBuilder &operator<<(StringBuilder &string_builder, const Photo &photo) {
return string_builder << ']';
}
static tl_object_ptr<telegram_api::fileLocationToBeDeprecated> copy_location(
const tl_object_ptr<telegram_api::fileLocationToBeDeprecated> &location) {
CHECK(location != nullptr);
return make_tl_object<telegram_api::fileLocationToBeDeprecated>(location->volume_id_, location->local_id_);
}
tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
const tl_object_ptr<telegram_api::photo> &photo) {
if (photo == nullptr) {
return nullptr;
}
tl_object_ptr<telegram_api::fileLocationToBeDeprecated> photo_small;
tl_object_ptr<telegram_api::fileLocationToBeDeprecated> photo_big;
bool have_photo_small = false;
bool have_photo_big = false;
for (auto &size_ptr : photo->sizes_) {
switch (size_ptr->get_id()) {
case telegram_api::photoSizeEmpty::ID:
@ -978,18 +963,18 @@ tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
case telegram_api::photoSize::ID: {
auto size = static_cast<const telegram_api::photoSize *>(size_ptr.get());
if (size->type_ == "a") {
photo_small = copy_location(size->location_);
have_photo_small = true;
} else if (size->type_ == "c") {
photo_big = copy_location(size->location_);
have_photo_big = true;
}
break;
}
case telegram_api::photoCachedSize::ID: {
auto size = static_cast<const telegram_api::photoCachedSize *>(size_ptr.get());
if (size->type_ == "a") {
photo_small = copy_location(size->location_);
have_photo_small = true;
} else if (size->type_ == "c") {
photo_big = copy_location(size->location_);
have_photo_big = true;
}
break;
}
@ -998,9 +983,9 @@ tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
case telegram_api::photoSizeProgressive::ID: {
auto size = static_cast<const telegram_api::photoSizeProgressive *>(size_ptr.get());
if (size->type_ == "a") {
photo_small = copy_location(size->location_);
have_photo_small = true;
} else if (size->type_ == "c") {
photo_big = copy_location(size->location_);
have_photo_big = true;
}
break;
}
@ -1009,15 +994,15 @@ tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
break;
}
}
if (photo_small == nullptr || photo_big == nullptr) {
if (!have_photo_small || !have_photo_big) {
return nullptr;
}
int32 flags = 0;
if (!photo->video_sizes_.empty()) {
flags |= telegram_api::userProfilePhoto::HAS_VIDEO_MASK;
}
return make_tl_object<telegram_api::userProfilePhoto>(flags, false /*ignored*/, photo->id_, std::move(photo_small),
std::move(photo_big), BufferSlice(), photo->dc_id_);
return make_tl_object<telegram_api::userProfilePhoto>(flags, false /*ignored*/, photo->id_, BufferSlice(),
photo->dc_id_);
}
} // namespace td

View File

@ -96,7 +96,7 @@ DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int6
tl_object_ptr<td_api::chatPhotoInfo> get_chat_photo_info_object(FileManager *file_manager,
const DialogPhoto *dialog_photo);
DialogPhoto as_fake_dialog_photo(const Photo &photo);
DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id);
ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo);

View File

@ -99,6 +99,10 @@ void parse(PhotoSize &photo_size, ParserT &parser) {
} else {
photo_size.progressive_sizes.clear();
}
if (photo_size.type < 0 || photo_size.type >= 128) {
parser.set_error("Wrong PhotoSize type");
return;
}
LOG(DEBUG) << "Parsed photo size " << photo_size;
}

View File

@ -11,6 +11,11 @@
#include "td/telegram/UserId.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/tl_storers.h"
namespace td {
@ -40,22 +45,117 @@ tl_object_ptr<telegram_api::InputPeer> PhotoSizeSource::DialogPhoto::get_input_p
FileType PhotoSizeSource::get_file_type() const {
switch (get_type()) {
case PhotoSizeSource::Type::Thumbnail:
case Type::Thumbnail:
return thumbnail().file_type;
case PhotoSizeSource::Type::DialogPhotoSmall:
case PhotoSizeSource::Type::DialogPhotoBig:
case Type::DialogPhotoSmall:
case Type::DialogPhotoBig:
case Type::DialogPhotoSmallLegacy:
case Type::DialogPhotoBigLegacy:
return FileType::ProfilePhoto;
case PhotoSizeSource::Type::StickerSetThumbnail:
case Type::StickerSetThumbnail:
case Type::StickerSetThumbnailLegacy:
case Type::StickerSetThumbnailVersion:
return FileType::Thumbnail;
case PhotoSizeSource::Type::Legacy:
case Type::Legacy:
case Type::FullLegacy:
default:
UNREACHABLE();
return FileType::Thumbnail;
}
}
string PhotoSizeSource::get_unique() const {
auto ptr = StackAllocator::alloc(16);
MutableSlice data = ptr.as_slice();
TlStorerUnsafe storer(data.ubegin());
switch (get_type()) {
case Type::Legacy:
UNREACHABLE();
break;
case Type::Thumbnail: {
auto type = thumbnail().thumbnail_type;
CHECK(0 <= type && type <= 127);
if (type == 'a') {
type = 0;
} else if (type == 'c') {
type = 1;
} else {
type += 5;
}
return string(1, static_cast<char>(type));
}
case Type::DialogPhotoSmall:
// it doesn't matter to which Dialog the photo belongs
return string(1, '\x00');
case Type::DialogPhotoBig:
// it doesn't matter to which Dialog the photo belongs
return string(1, '\x01');
case Type::StickerSetThumbnail:
UNREACHABLE();
break;
case Type::FullLegacy: {
auto &legacy = full_legacy();
td::store(legacy.volume_id, storer);
td::store(legacy.local_id, storer);
break;
}
case Type::DialogPhotoSmallLegacy:
case Type::DialogPhotoBigLegacy: {
auto &legacy = dialog_photo_legacy();
td::store(legacy.volume_id, storer);
td::store(legacy.local_id, storer);
break;
}
case Type::StickerSetThumbnailLegacy: {
auto &legacy = sticker_set_thumbnail_legacy();
td::store(legacy.volume_id, storer);
td::store(legacy.local_id, storer);
break;
}
case Type::StickerSetThumbnailVersion: {
auto &thumbnail = sticker_set_thumbnail_version();
storer.store_slice(Slice("\x02"));
td::store(thumbnail.sticker_set_id, storer);
td::store(thumbnail.version, storer);
break;
}
default:
UNREACHABLE();
break;
}
auto size = storer.get_buf() - data.ubegin();
CHECK(size <= 13);
return string(data.begin(), size);
}
string PhotoSizeSource::get_unique_name(int64 photo_id) const {
switch (get_type()) {
case Type::Thumbnail:
CHECK(0 <= thumbnail().thumbnail_type && thumbnail().thumbnail_type <= 127);
return PSTRING() << photo_id << '_' << thumbnail().thumbnail_type;
case Type::DialogPhotoSmall:
return to_string(photo_id);
case Type::DialogPhotoBig:
return PSTRING() << photo_id << '_' << 1;
case Type::StickerSetThumbnailVersion:
return PSTRING() << sticker_set_thumbnail_version().sticker_set_id << '_'
<< static_cast<uint32>(sticker_set_thumbnail_version().version);
case Type::Legacy:
case Type::StickerSetThumbnail:
case Type::FullLegacy:
case Type::DialogPhotoSmallLegacy:
case Type::DialogPhotoBigLegacy:
case Type::StickerSetThumbnailLegacy:
default:
UNREACHABLE();
break;
}
return 0;
}
static bool operator==(const PhotoSizeSource::Legacy &lhs, const PhotoSizeSource::Legacy &rhs) {
return lhs.secret == rhs.secret;
UNREACHABLE();
return false;
}
static bool operator==(const PhotoSizeSource::Thumbnail &lhs, const PhotoSizeSource::Thumbnail &rhs) {
@ -81,6 +181,42 @@ static bool operator==(const PhotoSizeSource::StickerSetThumbnail &lhs,
return lhs.sticker_set_id == rhs.sticker_set_id && lhs.sticker_set_access_hash == rhs.sticker_set_access_hash;
}
static bool operator==(const PhotoSizeSource::FullLegacy &lhs, const PhotoSizeSource::FullLegacy &rhs) {
return lhs.volume_id == rhs.volume_id && lhs.local_id == rhs.local_id && lhs.secret == rhs.secret;
}
static bool operator==(const PhotoSizeSource::DialogPhotoLegacy &lhs, const PhotoSizeSource::DialogPhotoLegacy &rhs) {
return static_cast<const PhotoSizeSource::DialogPhoto &>(lhs) ==
static_cast<const PhotoSizeSource::DialogPhoto &>(rhs) &&
lhs.volume_id == rhs.volume_id && lhs.local_id == rhs.local_id;
}
static bool operator==(const PhotoSizeSource::DialogPhotoSmallLegacy &lhs,
const PhotoSizeSource::DialogPhotoSmallLegacy &rhs) {
return static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(lhs) ==
static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(rhs);
}
static bool operator==(const PhotoSizeSource::DialogPhotoBigLegacy &lhs,
const PhotoSizeSource::DialogPhotoBigLegacy &rhs) {
return static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(lhs) ==
static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(rhs);
}
static bool operator==(const PhotoSizeSource::StickerSetThumbnailLegacy &lhs,
const PhotoSizeSource::StickerSetThumbnailLegacy &rhs) {
return static_cast<const PhotoSizeSource::StickerSetThumbnail &>(lhs) ==
static_cast<const PhotoSizeSource::StickerSetThumbnail &>(rhs) &&
lhs.volume_id == rhs.volume_id && lhs.local_id == rhs.local_id;
}
static bool operator==(const PhotoSizeSource::StickerSetThumbnailVersion &lhs,
const PhotoSizeSource::StickerSetThumbnailVersion &rhs) {
return static_cast<const PhotoSizeSource::StickerSetThumbnail &>(lhs) ==
static_cast<const PhotoSizeSource::StickerSetThumbnail &>(rhs) &&
lhs.version == rhs.version;
}
bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs) {
return lhs.variant == rhs.variant;
}
@ -91,6 +227,8 @@ bool operator!=(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs) {
StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSizeSource &source) {
switch (source.get_type()) {
case PhotoSizeSource::Type::Legacy:
return string_builder << "PhotoSizeSourceLegacy[]";
case PhotoSizeSource::Type::Thumbnail:
return string_builder << "PhotoSizeSourceThumbnail[" << source.thumbnail().file_type
<< ", type = " << source.thumbnail().thumbnail_type << ']';
@ -101,8 +239,19 @@ StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSizeSource &
case PhotoSizeSource::Type::StickerSetThumbnail:
return string_builder << "PhotoSizeSourceStickerSetThumbnail[" << source.sticker_set_thumbnail().sticker_set_id
<< ']';
case PhotoSizeSource::Type::Legacy:
return string_builder << "PhotoSizeSourceLegacy[]";
case PhotoSizeSource::Type::FullLegacy:
return string_builder << "PhotoSizeSourceFullLegacy[]";
case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
return string_builder << "PhotoSizeSourceChatPhotoSmallLegacy[" << source.dialog_photo().dialog_id << ']';
case PhotoSizeSource::Type::DialogPhotoBigLegacy:
return string_builder << "PhotoSizeSourceChatPhotoBigLegacy[" << source.dialog_photo().dialog_id << ']';
case PhotoSizeSource::Type::StickerSetThumbnailLegacy:
return string_builder << "PhotoSizeSourceStickerSetThumbnailLegacy["
<< source.sticker_set_thumbnail().sticker_set_id << ']';
case PhotoSizeSource::Type::StickerSetThumbnailVersion:
return string_builder << "PhotoSizeSourceStickerSetThumbnailVersion["
<< source.sticker_set_thumbnail().sticker_set_id << '_'
<< source.sticker_set_thumbnail_version().version << ']';
default:
UNREACHABLE();
return string_builder;

View File

@ -15,10 +15,23 @@
#include "td/utils/StringBuilder.h"
#include "td/utils/Variant.h"
#include <cstddef>
namespace td {
struct PhotoSizeSource {
enum class Type : int32 { Legacy, Thumbnail, DialogPhotoSmall, DialogPhotoBig, StickerSetThumbnail };
enum class Type : int32 {
Legacy,
Thumbnail,
DialogPhotoSmall,
DialogPhotoBig,
StickerSetThumbnail,
FullLegacy,
DialogPhotoSmallLegacy,
DialogPhotoBigLegacy,
StickerSetThumbnailLegacy,
StickerSetThumbnailVersion
};
// for legacy photos with secret
struct Legacy {
@ -75,9 +88,59 @@ struct PhotoSizeSource {
}
};
// for legacy photos with volume_id, local_id, secret
struct FullLegacy {
FullLegacy() = default;
FullLegacy(int64 volume_id, int32 local_id, int64 secret)
: volume_id(volume_id), local_id(local_id), secret(secret) {
}
int64 volume_id = 0;
int32 local_id = 0;
int64 secret = 0;
};
// for legacy dialog photos
struct DialogPhotoLegacy : public DialogPhoto {
DialogPhotoLegacy() = default;
DialogPhotoLegacy(DialogId dialog_id, int64 dialog_access_hash, int64 volume_id, int32 local_id)
: DialogPhoto(dialog_id, dialog_access_hash), volume_id(volume_id), local_id(local_id) {
}
int64 volume_id = 0;
int32 local_id = 0;
};
struct DialogPhotoSmallLegacy : public DialogPhotoLegacy {
using DialogPhotoLegacy::DialogPhotoLegacy;
};
struct DialogPhotoBigLegacy : public DialogPhotoLegacy {
using DialogPhotoLegacy::DialogPhotoLegacy;
};
// for legacy sticker set thumbnails
struct StickerSetThumbnailLegacy : public StickerSetThumbnail {
StickerSetThumbnailLegacy() = default;
StickerSetThumbnailLegacy(int64 sticker_set_id, int64 sticker_set_access_hash, int64 volume_id, int32 local_id)
: StickerSetThumbnail(sticker_set_id, sticker_set_access_hash), volume_id(volume_id), local_id(local_id) {
}
int64 volume_id = 0;
int32 local_id = 0;
};
// for sticker set thumbnails identified by version
struct StickerSetThumbnailVersion : public StickerSetThumbnail {
StickerSetThumbnailVersion() = default;
StickerSetThumbnailVersion(int64 sticker_set_id, int64 sticker_set_access_hash, int32 version)
: StickerSetThumbnail(sticker_set_id, sticker_set_access_hash), version(version) {
}
int32 version = 0;
};
PhotoSizeSource() = default;
explicit PhotoSizeSource(int64 secret) : variant(Legacy(secret)) {
}
PhotoSizeSource(FileType file_type, int32 thumbnail_type) : variant(Thumbnail(file_type, thumbnail_type)) {
}
PhotoSizeSource(DialogId dialog_id, int64 dialog_access_hash, bool is_big) {
@ -90,6 +153,22 @@ struct PhotoSizeSource {
PhotoSizeSource(int64 sticker_set_id, int64 sticker_set_access_hash)
: variant(StickerSetThumbnail(sticker_set_id, sticker_set_access_hash)) {
}
PhotoSizeSource(std::nullptr_t, int64 volume_id, int32 local_id, int64 secret)
: variant(FullLegacy(volume_id, local_id, secret)) {
}
PhotoSizeSource(DialogId dialog_id, int64 dialog_access_hash, bool is_big, int64 volume_id, int32 local_id) {
if (is_big) {
variant = DialogPhotoBigLegacy(dialog_id, dialog_access_hash, volume_id, local_id);
} else {
variant = DialogPhotoSmallLegacy(dialog_id, dialog_access_hash, volume_id, local_id);
}
}
PhotoSizeSource(int64 sticker_set_id, int64 sticker_set_access_hash, int64 volume_id, int32 local_id)
: variant(StickerSetThumbnailLegacy(sticker_set_id, sticker_set_access_hash, volume_id, local_id)) {
}
PhotoSizeSource(int64 sticker_set_id, int64 sticker_set_access_hash, int32 version)
: variant(StickerSetThumbnailVersion(sticker_set_id, sticker_set_access_hash, version)) {
}
Type get_type() const {
auto offset = variant.get_offset();
@ -110,15 +189,55 @@ struct PhotoSizeSource {
return variant.get<Thumbnail>();
}
const DialogPhoto &dialog_photo() const {
if (variant.get_offset() == 2) {
return variant.get<DialogPhotoSmall>();
} else {
return variant.get<DialogPhotoBig>();
switch (variant.get_offset()) {
case 2:
return variant.get<DialogPhotoSmall>();
case 3:
return variant.get<DialogPhotoBig>();
case 6:
return variant.get<DialogPhotoSmallLegacy>();
case 7:
return variant.get<DialogPhotoBigLegacy>();
default:
UNREACHABLE();
return variant.get<DialogPhotoSmall>();
}
}
const StickerSetThumbnail &sticker_set_thumbnail() const {
return variant.get<StickerSetThumbnail>();
switch (variant.get_offset()) {
case 4:
return variant.get<StickerSetThumbnail>();
case 8:
return variant.get<StickerSetThumbnailLegacy>();
case 9:
return variant.get<StickerSetThumbnailVersion>();
default:
UNREACHABLE();
return variant.get<StickerSetThumbnail>();
}
}
const FullLegacy &full_legacy() const {
return variant.get<FullLegacy>();
}
const DialogPhotoLegacy &dialog_photo_legacy() const {
if (variant.get_offset() == 6) {
return variant.get<DialogPhotoSmallLegacy>();
} else {
return variant.get<DialogPhotoBigLegacy>();
}
}
const StickerSetThumbnailLegacy &sticker_set_thumbnail_legacy() const {
return variant.get<StickerSetThumbnailLegacy>();
}
const StickerSetThumbnailVersion &sticker_set_thumbnail_version() const {
return variant.get<StickerSetThumbnailVersion>();
}
// returns unique representation of the source
string get_unique() const;
// can't be called for Legacy sources
string get_unique_name(int64 photo_id) const;
template <class StorerT>
void store(StorerT &storer) const;
@ -128,7 +247,9 @@ struct PhotoSizeSource {
friend bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs);
private:
Variant<Legacy, Thumbnail, DialogPhotoSmall, DialogPhotoBig, StickerSetThumbnail> variant;
Variant<Legacy, Thumbnail, DialogPhotoSmall, DialogPhotoBig, StickerSetThumbnail, FullLegacy, DialogPhotoSmallLegacy,
DialogPhotoBigLegacy, StickerSetThumbnailLegacy, StickerSetThumbnailVersion>
variant;
};
bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs);

View File

@ -15,6 +15,7 @@ namespace td {
template <class StorerT>
void store(const PhotoSizeSource::Legacy &source, StorerT &storer) {
UNREACHABLE();
store(source.secret, storer);
}
@ -39,23 +40,11 @@ void parse(PhotoSizeSource::Thumbnail &source, ParserT &parser) {
source.file_type = static_cast<FileType>(raw_type);
parse(source.thumbnail_type, parser);
if (source.thumbnail_type < 0 || source.thumbnail_type > 255) {
if (source.thumbnail_type < 0 || source.thumbnail_type > 127) {
parser.set_error("Wrong thumbnail type");
}
}
template <class StorerT>
void store(const PhotoSizeSource::StickerSetThumbnail &source, StorerT &storer) {
store(source.sticker_set_id, storer);
store(source.sticker_set_access_hash, storer);
}
template <class ParserT>
void parse(PhotoSizeSource::StickerSetThumbnail &source, ParserT &parser) {
parse(source.sticker_set_id, parser);
parse(source.sticker_set_access_hash, parser);
}
template <class StorerT>
void store(const PhotoSizeSource::DialogPhoto &source, StorerT &storer) {
store(source.dialog_id, storer);
@ -96,6 +85,101 @@ void parse(PhotoSizeSource::DialogPhotoBig &source, ParserT &parser) {
parse(static_cast<PhotoSizeSource::DialogPhoto &>(source), parser);
}
template <class StorerT>
void store(const PhotoSizeSource::StickerSetThumbnail &source, StorerT &storer) {
store(source.sticker_set_id, storer);
store(source.sticker_set_access_hash, storer);
}
template <class ParserT>
void parse(PhotoSizeSource::StickerSetThumbnail &source, ParserT &parser) {
parse(source.sticker_set_id, parser);
parse(source.sticker_set_access_hash, parser);
}
template <class StorerT>
void store(const PhotoSizeSource::FullLegacy &source, StorerT &storer) {
store(source.volume_id, storer);
store(source.secret, storer);
store(source.local_id, storer);
}
template <class ParserT>
void parse(PhotoSizeSource::FullLegacy &source, ParserT &parser) {
parse(source.volume_id, parser);
parse(source.secret, parser);
parse(source.local_id, parser);
if (source.local_id < 0) {
parser.set_error("Wrong local_id");
}
}
template <class StorerT>
void store(const PhotoSizeSource::DialogPhotoLegacy &source, StorerT &storer) {
store(static_cast<const PhotoSizeSource::DialogPhoto &>(source), storer);
store(source.volume_id, storer);
store(source.local_id, storer);
}
template <class ParserT>
void parse(PhotoSizeSource::DialogPhotoLegacy &source, ParserT &parser) {
parse(static_cast<PhotoSizeSource::DialogPhoto &>(source), parser);
parse(source.volume_id, parser);
parse(source.local_id, parser);
if (source.local_id < 0) {
parser.set_error("Wrong local_id");
}
}
template <class StorerT>
void store(const PhotoSizeSource::DialogPhotoSmallLegacy &source, StorerT &storer) {
store(static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(source), storer);
}
template <class ParserT>
void parse(PhotoSizeSource::DialogPhotoSmallLegacy &source, ParserT &parser) {
parse(static_cast<PhotoSizeSource::DialogPhotoLegacy &>(source), parser);
}
template <class StorerT>
void store(const PhotoSizeSource::DialogPhotoBigLegacy &source, StorerT &storer) {
store(static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(source), storer);
}
template <class ParserT>
void parse(PhotoSizeSource::DialogPhotoBigLegacy &source, ParserT &parser) {
parse(static_cast<PhotoSizeSource::DialogPhotoLegacy &>(source), parser);
}
template <class StorerT>
void store(const PhotoSizeSource::StickerSetThumbnailLegacy &source, StorerT &storer) {
store(static_cast<const PhotoSizeSource::StickerSetThumbnail &>(source), storer);
store(source.volume_id, storer);
store(source.local_id, storer);
}
template <class ParserT>
void parse(PhotoSizeSource::StickerSetThumbnailLegacy &source, ParserT &parser) {
parse(static_cast<PhotoSizeSource::StickerSetThumbnail &>(source), parser);
parse(source.volume_id, parser);
parse(source.local_id, parser);
if (source.local_id < 0) {
parser.set_error("Wrong local_id");
}
}
template <class StorerT>
void store(const PhotoSizeSource::StickerSetThumbnailVersion &source, StorerT &storer) {
store(static_cast<const PhotoSizeSource::StickerSetThumbnail &>(source), storer);
store(source.version, storer);
}
template <class ParserT>
void parse(PhotoSizeSource::StickerSetThumbnailVersion &source, ParserT &parser) {
parse(static_cast<PhotoSizeSource::StickerSetThumbnail &>(source), parser);
parse(source.version, parser);
}
template <class StorerT>
void PhotoSizeSource::store(StorerT &storer) const {
td::store(variant, storer);

View File

@ -162,7 +162,7 @@ void PrivacyManager::UserPrivacySettingRule::set_chat_ids(const vector<int64> &d
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
if (td->contacts_manager_->get_channel_type(channel_id) != ChannelType::Megagroup) {
if (td->contacts_manager_->get_channel_type(channel_id) != ContactsManager::ChannelType::Megagroup) {
LOG(ERROR) << "Ignore broadcast " << channel_id;
break;
}

View File

@ -8,6 +8,7 @@
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/LinkManager.h"
#include "td/telegram/misc.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
@ -25,6 +26,7 @@ namespace td {
static constexpr int32 REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD = 1 << 0;
static constexpr int32 REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD = 1 << 1;
static constexpr int32 REPLY_MARKUP_FLAG_IS_PERSONAL = 1 << 2;
static constexpr int32 REPLY_MARKUP_FLAG_HAS_PLACEHOLDER = 1 << 3;
static bool operator==(const KeyboardButton &lhs, const KeyboardButton &rhs) {
return lhs.type == rhs.type && lhs.text == rhs.text;
@ -105,6 +107,9 @@ bool operator==(const ReplyMarkup &lhs, const ReplyMarkup &rhs) {
if (lhs.is_personal != rhs.is_personal) {
return false;
}
if (lhs.placeholder != rhs.placeholder) {
return false;
}
if (lhs.type != ReplyMarkup::Type::ShowKeyboard) {
return true;
}
@ -137,6 +142,9 @@ StringBuilder &ReplyMarkup::print(StringBuilder &string_builder) const {
if (is_personal) {
string_builder << ", personal";
}
if (!placeholder.empty()) {
string_builder << ", placeholder \"" << placeholder << '"';
}
if (type == ReplyMarkup::Type::ShowKeyboard) {
if (need_resize_keyboard) {
@ -308,6 +316,7 @@ unique_ptr<ReplyMarkup> get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup
reply_markup->need_resize_keyboard = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD) != 0;
reply_markup->is_one_time_keyboard = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD) != 0;
reply_markup->is_personal = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
reply_markup->placeholder = std::move(keyboard_markup->placeholder_);
reply_markup->keyboard.reserve(keyboard_markup->rows_.size());
for (auto &row : keyboard_markup->rows_) {
vector<KeyboardButton> buttons;
@ -337,6 +346,7 @@ unique_ptr<ReplyMarkup> get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup
auto force_reply_markup = move_tl_object_as<telegram_api::replyKeyboardForceReply>(reply_markup_ptr);
reply_markup->type = ReplyMarkup::Type::ForceReply;
reply_markup->is_personal = (force_reply_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
reply_markup->placeholder = std::move(force_reply_markup->placeholder_);
break;
}
default:
@ -425,10 +435,14 @@ static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_
switch (button_type_id) {
case td_api::inlineKeyboardButtonTypeUrl::ID: {
current_button.type = InlineKeyboardButton::Type::Url;
TRY_RESULT_ASSIGN(current_button.data,
check_url(static_cast<const td_api::inlineKeyboardButtonTypeUrl *>(button->type_.get())->url_));
auto r_url =
LinkManager::check_link(static_cast<const td_api::inlineKeyboardButtonTypeUrl *>(button->type_.get())->url_);
if (r_url.is_error()) {
return Status::Error(400, "Inline keyboard button URL is invalid");
}
current_button.data = r_url.move_as_ok();
if (!clean_input_string(current_button.data)) {
return Status::Error(400, "Inline keyboard button url must be encoded in UTF-8");
return Status::Error(400, "Inline keyboard button URL must be encoded in UTF-8");
}
break;
}
@ -467,10 +481,14 @@ static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_
case td_api::inlineKeyboardButtonTypeLoginUrl::ID: {
current_button.type = InlineKeyboardButton::Type::UrlAuth;
auto login_url = td_api::move_object_as<td_api::inlineKeyboardButtonTypeLoginUrl>(button->type_);
TRY_RESULT_ASSIGN(current_button.data, check_url(login_url->url_));
auto r_url = LinkManager::check_link(login_url->url_);
if (r_url.is_error()) {
return Status::Error(400, "Inline keyboard button login URL is invalid");
}
current_button.data = r_url.move_as_ok();
current_button.forward_text = std::move(login_url->forward_text_);
if (!clean_input_string(current_button.data)) {
return Status::Error(400, "Inline keyboard button login url must be encoded in UTF-8");
return Status::Error(400, "Inline keyboard button login URL must be encoded in UTF-8");
}
if (!clean_input_string(current_button.forward_text)) {
return Status::Error(400, "Inline keyboard button forward text must be encoded in UTF-8");
@ -509,6 +527,7 @@ Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMark
reply_markup->need_resize_keyboard = show_keyboard_markup->resize_keyboard_;
reply_markup->is_one_time_keyboard = show_keyboard_markup->one_time_;
reply_markup->is_personal = show_keyboard_markup->is_personal_;
reply_markup->placeholder = std::move(show_keyboard_markup->input_field_placeholder_);
reply_markup->keyboard.reserve(show_keyboard_markup->rows_.size());
int32 total_button_count = 0;
@ -590,6 +609,7 @@ Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMark
auto force_reply_markup = move_tl_object_as<td_api::replyMarkupForceReply>(reply_markup_ptr);
reply_markup->type = ReplyMarkup::Type::ForceReply;
reply_markup->is_personal = force_reply_markup->is_personal_;
reply_markup->placeholder = std::move(force_reply_markup->input_field_placeholder_);
break;
}
default:
@ -701,13 +721,14 @@ tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup() c
return make_tl_object<telegram_api::replyKeyboardMarkup>(
need_resize_keyboard * REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD +
is_one_time_keyboard * REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD +
is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(rows));
is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL + (!placeholder.empty()) * REPLY_MARKUP_FLAG_HAS_PLACEHOLDER,
false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(rows), placeholder);
}
case ReplyMarkup::Type::ForceReply:
LOG(DEBUG) << "Return replyKeyboardForceReply to send it";
return make_tl_object<telegram_api::replyKeyboardForceReply>(is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
false /*ignored*/, false /*ignored*/);
return make_tl_object<telegram_api::replyKeyboardForceReply>(
is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL + (!placeholder.empty()) * REPLY_MARKUP_FLAG_HAS_PLACEHOLDER,
false /*ignored*/, false /*ignored*/, placeholder);
case ReplyMarkup::Type::RemoveKeyboard:
LOG(DEBUG) << "Return replyKeyboardHide to send it";
return make_tl_object<telegram_api::replyKeyboardHide>(is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
@ -811,12 +832,12 @@ tl_object_ptr<td_api::ReplyMarkup> ReplyMarkup::get_reply_markup_object() const
}
return make_tl_object<td_api::replyMarkupShowKeyboard>(std::move(rows), need_resize_keyboard,
is_one_time_keyboard, is_personal);
is_one_time_keyboard, is_personal, placeholder);
}
case ReplyMarkup::Type::RemoveKeyboard:
return make_tl_object<td_api::replyMarkupRemoveKeyboard>(is_personal);
case ReplyMarkup::Type::ForceReply:
return make_tl_object<td_api::replyMarkupForceReply>(is_personal);
return make_tl_object<td_api::replyMarkupForceReply>(is_personal, placeholder);
default:
UNREACHABLE();
return nullptr;

View File

@ -58,6 +58,7 @@ struct ReplyMarkup {
bool need_resize_keyboard = false; // for ShowKeyboard
bool is_one_time_keyboard = false; // for ShowKeyboard
vector<vector<KeyboardButton>> keyboard; // for ShowKeyboard
string placeholder; // for ShowKeyboard, ForceReply
vector<vector<InlineKeyboardButton>> inline_keyboard; // for InlineKeyboard

View File

@ -48,12 +48,14 @@ template <class StorerT>
void store(const ReplyMarkup &reply_markup, StorerT &storer) {
bool has_keyboard = !reply_markup.keyboard.empty();
bool has_inline_keyboard = !reply_markup.inline_keyboard.empty();
bool has_placeholder = !reply_markup.placeholder.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(reply_markup.is_personal);
STORE_FLAG(reply_markup.need_resize_keyboard);
STORE_FLAG(reply_markup.is_one_time_keyboard);
STORE_FLAG(has_keyboard);
STORE_FLAG(has_inline_keyboard);
STORE_FLAG(has_placeholder);
END_STORE_FLAGS();
store(reply_markup.type, storer);
if (has_keyboard) {
@ -62,18 +64,23 @@ void store(const ReplyMarkup &reply_markup, StorerT &storer) {
if (has_inline_keyboard) {
store(reply_markup.inline_keyboard, storer);
}
if (has_placeholder) {
store(reply_markup.placeholder, storer);
}
}
template <class ParserT>
void parse(ReplyMarkup &reply_markup, ParserT &parser) {
bool has_keyboard;
bool has_inline_keyboard;
bool has_placeholder;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(reply_markup.is_personal);
PARSE_FLAG(reply_markup.need_resize_keyboard);
PARSE_FLAG(reply_markup.is_one_time_keyboard);
PARSE_FLAG(has_keyboard);
PARSE_FLAG(has_inline_keyboard);
PARSE_FLAG(has_placeholder);
END_PARSE_FLAGS();
parse(reply_markup.type, parser);
if (has_keyboard) {
@ -82,6 +89,9 @@ void parse(ReplyMarkup &reply_markup, ParserT &parser) {
if (has_inline_keyboard) {
parse(reply_markup.inline_keyboard, parser);
}
if (has_placeholder) {
parse(reply_markup.placeholder, parser);
}
}
} // namespace td

View File

@ -180,7 +180,7 @@ void SecretChatActor::replay_inbound_message(unique_ptr<log_event::InboundSecret
CHECK(!binlog_replay_finish_flag_);
CHECK(message->decrypted_message_layer); // from binlog
if (message->is_pending) { // wait for gaps?
// check_status(do_inbound_message_decrypted_unchecked(std::move(message)));
// check_status(do_inbound_message_decrypted_unchecked(std::move(message)), -1);
do_inbound_message_decrypted_pending(std::move(message));
} else { // just replay
LOG_CHECK(message->message_id > last_binlog_message_id_)
@ -208,31 +208,23 @@ void SecretChatActor::replay_outbound_message(unique_ptr<log_event::OutboundSecr
}
// NB: my_seq_no is just after message is sent, i.e. my_out_seq_no is already incremented
Result<BufferSlice> SecretChatActor::create_encrypted_message(int32 layer, int32 my_in_seq_no, int32 my_out_seq_no,
Result<BufferSlice> SecretChatActor::create_encrypted_message(int32 my_in_seq_no, int32 my_out_seq_no,
tl_object_ptr<secret_api::DecryptedMessage> &message) {
if (message->get_id() == secret_api::decryptedMessage::ID && layer < MTPROTO_2_LAYER) {
auto old = secret_api::move_object_as<secret_api::decryptedMessage>(message);
old->flags_ &= ~secret_api::decryptedMessage::GROUPED_ID_MASK;
message = secret_api::make_object<secret_api::decryptedMessage46>(
old->flags_, old->random_id_, old->ttl_, std::move(old->message_), std::move(old->media_),
std::move(old->entities_), std::move(old->via_bot_name_), old->reply_to_random_id_);
}
mtproto::AuthKey *auth_key = &pfs_state_.auth_key;
auto in_seq_no = my_in_seq_no * 2 + auth_state_.x;
auto out_seq_no = my_out_seq_no * 2 - 1 - auth_state_.x;
auto layer = current_layer();
BufferSlice random_bytes(32);
Random::secure_bytes(random_bytes.as_slice().ubegin(), random_bytes.size());
auto message_with_layer = secret_api::make_object<secret_api::decryptedMessageLayer>(
std::move(random_bytes), layer, in_seq_no, out_seq_no, std::move(message));
LOG(INFO) << to_string(message_with_layer);
LOG(INFO) << "Create message " << to_string(message_with_layer);
auto storer = create_storer(*message_with_layer);
auto new_storer = mtproto::PacketStorer<SecretImpl>(storer);
mtproto::PacketInfo info;
info.type = mtproto::PacketInfo::EndToEnd;
// Send with mtproto 2.0 if current layer is at least MTPROTO_2_LAYER
info.version = layer >= MTPROTO_2_LAYER ? 2 : 1;
info.version = 2;
info.is_creator = auth_state_.x == 0;
auto packet_writer = BufferWriter{mtproto::Transport::write(new_storer, *auth_key, &info), 0, 0};
mtproto::Transport::write(new_storer, *auth_key, &info, packet_writer.as_slice());
@ -249,63 +241,6 @@ void SecretChatActor::send_message(tl_object_ptr<secret_api::DecryptedMessage> m
send_message_impl(std::move(message), std::move(file), SendFlag::External | SendFlag::Push, std::move(promise));
}
static int32 get_min_layer(const secret_api::decryptedMessageActionTyping &message) {
switch (message.action_->get_id()) {
case secret_api::sendMessageRecordRoundAction::ID:
case secret_api::sendMessageUploadRoundAction::ID:
return SecretChatActor::VIDEO_NOTES_LAYER;
}
return 0;
}
static int32 get_min_layer(const secret_api::decryptedMessageService &message) {
switch (message.action_->get_id()) {
case secret_api::decryptedMessageActionTyping::ID:
return get_min_layer(static_cast<const secret_api::decryptedMessageActionTyping &>(*message.action_));
default:
return 0;
}
}
static int32 get_min_layer(const secret_api::DocumentAttribute &attribute) {
switch (attribute.get_id()) {
case secret_api::documentAttributeVideo66::ID:
return SecretChatActor::VIDEO_NOTES_LAYER;
default:
return 0;
}
}
static int32 get_min_layer(const secret_api::decryptedMessageMediaDocument &message) {
int32 res = 0;
for (auto &attribute : message.attributes_) {
auto attrirbute_layer = get_min_layer(*attribute);
if (attrirbute_layer > res) {
res = attrirbute_layer;
}
return res;
}
return res;
}
static int32 get_min_layer(const secret_api::decryptedMessage &message) {
if (!message.media_) {
return 0;
}
switch (message.media_->get_id()) {
case secret_api::decryptedMessageMediaDocument::ID:
return get_min_layer(static_cast<const secret_api::decryptedMessageMediaDocument &>(*message.media_));
default:
return 0;
}
}
static int32 get_min_layer(const secret_api::DecryptedMessage &message) {
switch (message.get_id()) {
case secret_api::decryptedMessageService::ID:
return get_min_layer(static_cast<const secret_api::decryptedMessageService &>(message));
case secret_api::decryptedMessage::ID:
return get_min_layer(static_cast<const secret_api::decryptedMessage &>(message));
default:
return 0;
}
}
void SecretChatActor::send_message_impl(tl_object_ptr<secret_api::DecryptedMessage> message,
tl_object_ptr<telegram_api::InputEncryptedFile> file, int32 flags,
Promise<> promise) {
@ -317,16 +252,11 @@ void SecretChatActor::send_message_impl(tl_object_ptr<secret_api::DecryptedMessa
LOG(ERROR) << "Ignore send_message: " << tag("message", to_string(message)) << tag("file", to_string(file));
return promise.set_error(Status::Error(400, "Chat is not accessible"));
}
if (get_min_layer(*message) > config_state_.his_layer) {
return promise.set_error(Status::Error(400, "Message is not supported by the other side"));
}
LOG_CHECK(binlog_replay_finish_flag_) << "Trying to send message before binlog replay is finished: "
<< to_string(*message) << to_string(file);
int64 random_id = 0;
downcast_call(*message, [&](auto &x) { random_id = x.random_id_; });
LOG(INFO) << "Send message: " << to_string(message) << to_string(file);
auto it = random_id_to_outbound_message_state_token_.find(random_id);
if (it != random_id_to_outbound_message_state_token_.end()) {
return on_outbound_outer_send_message_promise(it->second, std::move(promise));
@ -341,8 +271,7 @@ void SecretChatActor::send_message_impl(tl_object_ptr<secret_api::DecryptedMessa
binlog_event->my_out_seq_no = seq_no_state_.my_out_seq_no + 1;
binlog_event->his_in_seq_no = seq_no_state_.his_in_seq_no;
binlog_event->encrypted_message =
create_encrypted_message(current_layer(), binlog_event->my_in_seq_no, binlog_event->my_out_seq_no, message)
.move_as_ok();
create_encrypted_message(binlog_event->my_in_seq_no, binlog_event->my_out_seq_no, message).move_as_ok();
binlog_event->need_notify_user = (flags & SendFlag::Push) == 0;
binlog_event->is_external = (flags & SendFlag::External) != 0;
binlog_event->is_silent = (message->get_id() == secret_api::decryptedMessage::ID &&
@ -617,7 +546,7 @@ void SecretChatActor::run_fill_gaps() {
LOG(INFO) << "Replay pending event: " << tag("seq_no", next_seq_no);
auto message = std::move(begin->second);
pending_inbound_messages_.erase(begin);
check_status(do_inbound_message_decrypted_unchecked(std::move(message)));
check_status(do_inbound_message_decrypted_unchecked(std::move(message), -1));
CHECK(pending_inbound_messages_.find(next_seq_no) == pending_inbound_messages_.end());
} else {
break;
@ -861,7 +790,6 @@ Result<std::tuple<uint64, BufferSlice, int32>> SecretChatActor::decrypt(BufferSl
int32 mtproto_version = -1;
Result<mtproto::Transport::ReadResult> r_read_result;
for (size_t i = 0; i < versions.size(); i++) {
bool is_last = i + 1 == versions.size();
encrypted_message_copy = encrypted_message.copy();
data = encrypted_message_copy.as_slice();
CHECK(is_aligned_pointer<4>(data.data()));
@ -872,7 +800,7 @@ Result<std::tuple<uint64, BufferSlice, int32>> SecretChatActor::decrypt(BufferSl
info.version = mtproto_version;
info.is_creator = auth_state_.x == 0;
r_read_result = mtproto::Transport::read(data, *auth_key, &info);
if (!is_last && r_read_result.is_error()) {
if (i + 1 == versions.size() && r_read_result.is_error()) {
LOG(WARNING) << tag("mtproto", mtproto_version) << " decryption failed " << r_read_result.error();
continue;
}
@ -883,7 +811,7 @@ Result<std::tuple<uint64, BufferSlice, int32>> SecretChatActor::decrypt(BufferSl
case mtproto::Transport::ReadResult::Quickack:
return Status::Error("Got quickack instead of a message");
case mtproto::Transport::ReadResult::Error:
return Status::Error(PSLICE() << "Got mtproto error code instead of a message: " << read_result.error());
return Status::Error(PSLICE() << "Got MTProto error code instead of a message: " << read_result.error());
case mtproto::Transport::ReadResult::Nop:
return Status::Error("Got nop instead of a message");
case mtproto::Transport::ReadResult::Packet:
@ -938,7 +866,7 @@ Status SecretChatActor::do_inbound_message_encrypted(unique_ptr<log_event::Inbou
return Status::Error(PSLICE() << "Invalid seq_no: " << to_string(message_with_layer));
}
message->decrypted_message_layer = std::move(message_with_layer);
return do_inbound_message_decrypted_unchecked(std::move(message));
return do_inbound_message_decrypted_unchecked(std::move(message), mtproto_version);
} else {
status = Status::Error(PSLICE() << parser.get_error() << format::as_hex_dump<4>(data_buffer.as_slice()));
}
@ -958,7 +886,7 @@ Status SecretChatActor::do_inbound_message_encrypted(unique_ptr<log_event::Inbou
if (!new_parser.get_error()) {
message->decrypted_message_layer = secret_api::make_object<secret_api::decryptedMessageLayer>(
BufferSlice(), config_state_.his_layer, -1, -1, std::move(message_without_layer));
return do_inbound_message_decrypted_unchecked(std::move(message));
return do_inbound_message_decrypted_unchecked(std::move(message), mtproto_version);
}
LOG(ERROR) << "Failed to fetch update (DecryptedMessage): " << new_parser.get_error()
<< format::as_hex_dump<4>(data_buffer.as_slice());
@ -995,7 +923,8 @@ Status SecretChatActor::check_seq_no(int in_seq_no, int out_seq_no, int32 his_la
return Status::OK();
}
Status SecretChatActor::do_inbound_message_decrypted_unchecked(unique_ptr<log_event::InboundSecretMessage> message) {
Status SecretChatActor::do_inbound_message_decrypted_unchecked(unique_ptr<log_event::InboundSecretMessage> message,
int32 mtproto_version) {
SCOPE_EXIT {
CHECK(message == nullptr || !message->promise);
};
@ -1056,7 +985,8 @@ Status SecretChatActor::do_inbound_message_decrypted_unchecked(unique_ptr<log_ev
}
}
LOG(INFO) << "GOT MESSAGE " << to_string(message->decrypted_message_layer);
LOG(INFO) << "Receive message encrypted with MTProto " << mtproto_version << ": "
<< to_string(message->decrypted_message_layer);
if (status.is_error()) {
CHECK(status.code() == 2); // gap found
@ -1592,8 +1522,8 @@ Status SecretChatActor::outbound_rewrite_with_empty(uint64 state_id) {
state->message->random_id, secret_api::make_object<secret_api::decryptedMessageActionDeleteMessages>(
std::vector<int64>{static_cast<int64>(state->message->random_id)}));
TRY_RESULT(encrypted_message, create_encrypted_message(current_layer(), state->message->my_in_seq_no,
state->message->my_out_seq_no, message));
TRY_RESULT(encrypted_message,
create_encrypted_message(state->message->my_in_seq_no, state->message->my_out_seq_no, message));
state->message->encrypted_message = std::move(encrypted_message);
LOG(INFO) << tag("crc", crc64(state->message->encrypted_message.as_slice()));
state->message->is_rewritable = false;
@ -1820,7 +1750,7 @@ void SecretChatActor::on_outbound_outer_send_message_promise(uint64 state_id, Pr
}
auto *state = outbound_message_states_.get(state_id);
CHECK(state);
LOG(INFO) << "Outbound secret message [TODO] " << tag("log_event_id", state->message->log_event_id());
LOG(INFO) << "Outbound secret message " << tag("log_event_id", state->message->log_event_id());
promise.set_value(Unit()); // Seems like this message is at least stored to binlog already
if (state->send_result_) {
state->send_result_({});
@ -1918,7 +1848,6 @@ Status SecretChatActor::on_update_chat(telegram_api::encryptedChat &update) {
// NB: order is important
context_->secret_chat_db()->set_value(pfs_state_);
context_->secret_chat_db()->set_value(auth_state_);
LOG(INFO) << "OK! Ready!";
send_update_secret_chat();
send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(MY_LAYER), SendFlag::None,
Promise<>());

View File

@ -48,10 +48,8 @@ class NetQueryCreator;
class SecretChatActor : public NetQueryCallback {
public:
// do not change DEFAULT_LAYER, unless all it's usages are fixed
enum : int32 {
DEFAULT_LAYER = 46,
VIDEO_NOTES_LAYER = 66,
DEFAULT_LAYER = 73,
MTPROTO_2_LAYER = 73,
NEW_ENTITIES_LAYER = 101,
DELETE_MESSAGES_ON_CLOSE_LAYER = 123,
@ -571,7 +569,8 @@ class SecretChatActor : public NetQueryCallback {
Result<std::tuple<uint64, BufferSlice, int32>> decrypt(BufferSlice &encrypted_message);
Status do_inbound_message_encrypted(unique_ptr<log_event::InboundSecretMessage> message);
Status do_inbound_message_decrypted_unchecked(unique_ptr<log_event::InboundSecretMessage> message);
Status do_inbound_message_decrypted_unchecked(unique_ptr<log_event::InboundSecretMessage> message,
int32 mtproto_version);
Status do_inbound_message_decrypted(unique_ptr<log_event::InboundSecretMessage> message);
void do_inbound_message_decrypted_pending(unique_ptr<log_event::InboundSecretMessage> message);
@ -620,7 +619,8 @@ class SecretChatActor : public NetQueryCallback {
tl_object_ptr<telegram_api::InputEncryptedFile> file, int32 flags, Promise<> promise);
void do_outbound_message_impl(unique_ptr<log_event::OutboundSecretMessage>, Promise<> promise);
Result<BufferSlice> create_encrypted_message(int32 layer, int32 my_in_seq_no, int32 my_out_seq_no,
Result<BufferSlice> create_encrypted_message(int32 my_in_seq_no, int32 my_out_seq_no,
tl_object_ptr<secret_api::DecryptedMessage> &message);
NetQueryPtr create_net_query(const log_event::OutboundSecretMessage &message);

View File

@ -31,7 +31,7 @@ class SecretChatsManager : public Actor {
public:
explicit SecretChatsManager(ActorShared<> parent);
// Proxy query to corrensponding SecretChatActor
// proxy query to corrensponding SecretChatActor
void on_update_chat(tl_object_ptr<telegram_api::updateEncryption> update);
void on_new_message(tl_object_ptr<telegram_api::EncryptedMessage> &&message_ptr, Promise<Unit> &&promise);
@ -47,7 +47,7 @@ class SecretChatsManager : public Actor {
void notify_screenshot_taken(SecretChatId secret_chat_id, Promise<> promise);
void send_set_ttl_message(SecretChatId secret_chat_id, int32 ttl, int64 random_id, Promise<> promise);
// Binlog replay
// binlog replay
void replay_binlog_event(BinlogEvent &&binlog_event);
void binlog_replay_finish();

View File

@ -6,10 +6,6 @@
//
#include "td/telegram/StickersManager.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/AccessRights.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/ConfigManager.h"
@ -29,10 +25,13 @@
#include "td/telegram/misc.h"
#include "td/telegram/net/DcId.h"
#include "td/telegram/net/MtprotoHeader.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/StickerSetId.hpp"
#include "td/telegram/StickersManager.hpp"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/MultiPromise.h"
#include "td/actor/PromiseFuture.h"
@ -901,6 +900,59 @@ class UploadStickerFileQuery : public Td::ResultHandler {
}
};
class SuggestStickerSetShortNameQuery : public Td::ResultHandler {
Promise<string> promise_;
public:
explicit SuggestStickerSetShortNameQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
}
void send(const string &title) {
send_query(G()->net_query_creator().create(telegram_api::stickers_suggestShortName(title)));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::stickers_suggestShortName>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
promise_.set_value(std::move(ptr->short_name_));
}
void on_error(uint64 id, Status status) override {
if (status.message() == "TITLE_INVALID") {
return promise_.set_value(string());
}
promise_.set_error(std::move(status));
}
};
class CheckStickerSetShortNameQuery : public Td::ResultHandler {
Promise<bool> promise_;
public:
explicit CheckStickerSetShortNameQuery(Promise<bool> &&promise) : promise_(std::move(promise)) {
}
void send(const string &short_name) {
send_query(G()->net_query_creator().create(telegram_api::stickers_checkShortName(short_name)));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::stickers_checkShortName>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
void on_error(uint64 id, Status status) override {
promise_.set_error(std::move(status));
}
};
class CreateNewStickerSetQuery : public Td::ResultHandler {
Promise<Unit> promise_;
@ -909,8 +961,8 @@ class CreateNewStickerSetQuery : public Td::ResultHandler {
}
void send(tl_object_ptr<telegram_api::InputUser> &&input_user, const string &title, const string &short_name,
bool is_masks, bool is_animated,
vector<tl_object_ptr<telegram_api::inputStickerSetItem>> &&input_stickers) {
bool is_masks, bool is_animated, vector<tl_object_ptr<telegram_api::inputStickerSetItem>> &&input_stickers,
const string &software) {
CHECK(input_user != nullptr);
int32 flags = 0;
@ -920,10 +972,13 @@ class CreateNewStickerSetQuery : public Td::ResultHandler {
if (is_animated) {
flags |= telegram_api::stickers_createStickerSet::ANIMATED_MASK;
}
if (!software.empty()) {
flags |= telegram_api::stickers_createStickerSet::SOFTWARE_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::stickers_createStickerSet(flags, false /*ignored*/, false /*ignored*/, std::move(input_user),
title, short_name, nullptr, std::move(input_stickers))));
title, short_name, nullptr, std::move(input_stickers), software)));
}
void on_result(uint64 id, BufferSlice packet) override {
@ -2275,7 +2330,9 @@ void StickersManager::create_sticker(FileId file_id, string minithumbnail, Photo
auto s = make_unique<Sticker>();
s->file_id = file_id;
s->dimensions = dimensions;
s->minithumbnail = std::move(minithumbnail);
if (!td_->auth_manager_->is_bot()) {
s->minithumbnail = std::move(minithumbnail);
}
add_sticker_thumbnail(s.get(), thumbnail);
if (sticker != nullptr) {
s->set_id = on_get_input_sticker_set(file_id, std::move(sticker->stickerset_), load_data_multipromise_ptr);
@ -2474,8 +2531,8 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
PhotoSize thumbnail;
string minithumbnail;
for (auto &thumb : set->thumbs_) {
auto photo_size = get_photo_size(td_->file_manager_.get(), {set_id.get(), s->access_hash}, 0, 0, "",
DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb),
auto photo_size = get_photo_size(td_->file_manager_.get(), {set_id.get(), s->access_hash, set->thumb_version_}, 0,
0, "", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb),
is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp);
if (photo_size.get_offset() == 0) {
if (!thumbnail.file_id.is_valid()) {
@ -2490,7 +2547,9 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
s->is_inited = true;
s->title = std::move(set->title_);
s->short_name = std::move(set->short_name_);
s->minithumbnail = std::move(minithumbnail);
if (!td_->auth_manager_->is_bot()) {
s->minithumbnail = std::move(minithumbnail);
}
s->thumbnail = std::move(thumbnail);
s->is_thumbnail_reloaded = true;
s->are_legacy_sticker_thumbnails_reloaded = true;
@ -2651,7 +2710,7 @@ StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_s
}
if (sticker_set_id.is_valid() && sticker_set_id != set_id) {
LOG(ERROR) << "Expected " << sticker_set_id << ", but receive " << set_id << " from " << source;
on_load_sticker_set_fail(sticker_set_id, Status::Error(500, "Internal Server Error"));
on_load_sticker_set_fail(sticker_set_id, Status::Error(500, "Internal Server Error: wrong sticker set received"));
return StickerSetId();
}
@ -4675,8 +4734,13 @@ Result<std::tuple<FileId, bool, bool, bool>> StickersManager::prepare_input_file
return std::make_tuple(file_id, is_url, is_local, is_animated);
}
FileId StickersManager::upload_sticker_file(UserId user_id, const tl_object_ptr<td_api::InputFile> &sticker,
FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr<td_api::InputSticker> &&sticker,
Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
if (!is_bot) {
user_id = td_->contacts_manager_->get_my_id();
}
auto input_user = td_->contacts_manager_->get_input_user(user_id);
if (input_user == nullptr) {
promise.set_error(Status::Error(3, "User not found"));
@ -4689,7 +4753,7 @@ FileId StickersManager::upload_sticker_file(UserId user_id, const tl_object_ptr<
return FileId();
}
auto r_file_id = prepare_input_file(sticker, false, false);
auto r_file_id = prepare_input_sticker(sticker.get());
if (r_file_id.is_error()) {
promise.set_error(r_file_id.move_as_error());
return FileId();
@ -4749,9 +4813,60 @@ tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_stic
get_input_sticker_emojis(sticker), std::move(mask_coords));
}
void StickersManager::get_suggested_sticker_set_name(string title, Promise<string> &&promise) {
title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH);
if (title.empty()) {
return promise.set_error(Status::Error(3, "Sticker set title can't be empty"));
}
td_->create_handler<SuggestStickerSetShortNameQuery>(std::move(promise))->send(title);
}
void StickersManager::check_sticker_set_name(const string &name, Promise<CheckStickerSetNameResult> &&promise) {
if (name.empty()) {
return promise.set_value(CheckStickerSetNameResult::Invalid);
}
auto request_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<bool> result) mutable {
if (result.is_error()) {
auto error = result.move_as_error();
if (error.message() == "SHORT_NAME_INVALID") {
return promise.set_value(CheckStickerSetNameResult::Invalid);
}
if (error.message() == "SHORT_NAME_OCCUPIED") {
return promise.set_value(CheckStickerSetNameResult::Occupied);
}
return promise.set_error(std::move(error));
}
promise.set_value(CheckStickerSetNameResult::Ok);
});
return td_->create_handler<CheckStickerSetShortNameQuery>(std::move(request_promise))->send(name);
}
td_api::object_ptr<td_api::CheckStickerSetNameResult> StickersManager::get_check_sticker_set_name_result_object(
CheckStickerSetNameResult result) {
switch (result) {
case CheckStickerSetNameResult::Ok:
return td_api::make_object<td_api::checkStickerSetNameResultOk>();
case CheckStickerSetNameResult::Invalid:
return td_api::make_object<td_api::checkStickerSetNameResultNameInvalid>();
case CheckStickerSetNameResult::Occupied:
return td_api::make_object<td_api::checkStickerSetNameResultNameOccupied>();
default:
UNREACHABLE();
return nullptr;
}
}
void StickersManager::create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks,
vector<tl_object_ptr<td_api::InputSticker>> &&stickers,
vector<tl_object_ptr<td_api::InputSticker>> &&stickers, string software,
Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
if (!is_bot) {
user_id = td_->contacts_manager_->get_my_id();
}
auto input_user = td_->contacts_manager_->get_input_user(user_id);
if (input_user == nullptr) {
return promise.set_error(Status::Error(3, "User not found"));
@ -4817,6 +4932,7 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri
pending_new_sticker_set->is_animated = is_animated;
pending_new_sticker_set->file_ids = std::move(file_ids);
pending_new_sticker_set->stickers = std::move(stickers);
pending_new_sticker_set->software = std::move(software);
pending_new_sticker_set->promise = std::move(promise);
auto &multipromise = pending_new_sticker_set->upload_files_multipromise;
@ -4989,7 +5105,7 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result<Unit> res
td_->create_handler<CreateNewStickerSetQuery>(std::move(pending_new_sticker_set->promise))
->send(std::move(input_user), pending_new_sticker_set->title, pending_new_sticker_set->short_name, is_masks,
is_animated, std::move(input_stickers));
is_animated, std::move(input_stickers), std::move(pending_new_sticker_set->software));
}
void StickersManager::add_sticker_to_set(UserId user_id, string &short_name,

View File

@ -167,10 +167,19 @@ class StickersManager : public Actor {
void reorder_installed_sticker_sets(bool is_masks, const vector<StickerSetId> &sticker_set_ids,
Promise<Unit> &&promise);
FileId upload_sticker_file(UserId user_id, const tl_object_ptr<td_api::InputFile> &sticker, Promise<Unit> &&promise);
FileId upload_sticker_file(UserId user_id, tl_object_ptr<td_api::InputSticker> &&sticker, Promise<Unit> &&promise);
void get_suggested_sticker_set_name(string short_name, Promise<string> &&promise);
enum class CheckStickerSetNameResult : uint8 { Ok, Invalid, Occupied };
void check_sticker_set_name(const string &name, Promise<CheckStickerSetNameResult> &&promise);
static td_api::object_ptr<td_api::CheckStickerSetNameResult> get_check_sticker_set_name_result_object(
CheckStickerSetNameResult result);
void create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks,
vector<tl_object_ptr<td_api::InputSticker>> &&stickers, Promise<Unit> &&promise);
vector<tl_object_ptr<td_api::InputSticker>> &&stickers, string software,
Promise<Unit> &&promise);
void add_sticker_to_set(UserId user_id, string &short_name, tl_object_ptr<td_api::InputSticker> &&sticker,
Promise<Unit> &&promise);
@ -369,6 +378,7 @@ class StickersManager : public Actor {
bool is_animated;
vector<FileId> file_ids;
vector<tl_object_ptr<td_api::InputSticker>> stickers;
string software;
Promise<> promise;
};

View File

@ -16,6 +16,7 @@
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/utf8.h"
namespace td {
@ -324,6 +325,13 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser
if (expires_at > sticker_set->expires_at) {
sticker_set->expires_at = expires_at;
}
if (!check_utf8(sticker_set->title)) {
return parser.set_error("Have invalid sticker set title");
}
if (!check_utf8(sticker_set->short_name)) {
return parser.set_error("Have invalid sticker set name");
}
}
}

View File

@ -361,7 +361,7 @@ void StorageManager::timeout_expired() {
next_gc_at_ = 0;
run_gc({}, false, PromiseCreator::lambda([actor_id = actor_id(this)](Result<FileStats> r_stats) {
if (!r_stats.is_error() || r_stats.error().code() != 500) {
// do not save gc timestamp if request was cancelled
// do not save gc timestamp if request was canceled
send_closure(actor_id, &StorageManager::save_last_gc_timestamp);
}
send_closure(actor_id, &StorageManager::schedule_next_gc);

View File

@ -25,6 +25,10 @@ void SuggestedAction::init(Type type) {
SuggestedAction::SuggestedAction(Slice action_str) {
if (action_str == Slice("AUTOARCHIVE_POPULAR")) {
init(Type::EnableArchiveAndMuteNewChats);
} else if (action_str == Slice("VALIDATE_PASSWORD")) {
init(Type::CheckPassword);
} else if (action_str == Slice("VALIDATE_PHONE_NUMBER")) {
init(Type::CheckPhoneNumber);
} else if (action_str == Slice("NEWCOMER_TICKS")) {
init(Type::SeeTicksHint);
}
@ -46,6 +50,9 @@ SuggestedAction::SuggestedAction(const td_api::object_ptr<td_api::SuggestedActio
case td_api::suggestedActionEnableArchiveAndMuteNewChats::ID:
init(Type::EnableArchiveAndMuteNewChats);
break;
case td_api::suggestedActionCheckPassword::ID:
init(Type::CheckPassword);
break;
case td_api::suggestedActionCheckPhoneNumber::ID:
init(Type::CheckPhoneNumber);
break;
@ -70,6 +77,10 @@ string SuggestedAction::get_suggested_action_str() const {
switch (type_) {
case Type::EnableArchiveAndMuteNewChats:
return "AUTOARCHIVE_POPULAR";
case Type::CheckPassword:
return "VALIDATE_PASSWORD";
case Type::CheckPhoneNumber:
return "VALIDATE_PHONE_NUMBER";
case Type::SeeTicksHint:
return "NEWCOMER_TICKS";
case Type::ConvertToGigagroup:
@ -85,6 +96,8 @@ td_api::object_ptr<td_api::SuggestedAction> SuggestedAction::get_suggested_actio
return nullptr;
case Type::EnableArchiveAndMuteNewChats:
return td_api::make_object<td_api::suggestedActionEnableArchiveAndMuteNewChats>();
case Type::CheckPassword:
return td_api::make_object<td_api::suggestedActionCheckPassword>();
case Type::CheckPhoneNumber:
return td_api::make_object<td_api::suggestedActionCheckPhoneNumber>();
case Type::SeeTicksHint:

View File

@ -15,7 +15,14 @@
namespace td {
struct SuggestedAction {
enum class Type : int32 { Empty, EnableArchiveAndMuteNewChats, CheckPhoneNumber, SeeTicksHint, ConvertToGigagroup };
enum class Type : int32 {
Empty,
EnableArchiveAndMuteNewChats,
CheckPhoneNumber,
SeeTicksHint,
ConvertToGigagroup,
CheckPassword
};
Type type_ = Type::Empty;
DialogId dialog_id_;

View File

@ -14,6 +14,7 @@
#include "td/telegram/BackgroundId.h"
#include "td/telegram/BackgroundManager.h"
#include "td/telegram/BackgroundType.h"
#include "td/telegram/BotCommand.h"
#include "td/telegram/CallbackQueriesManager.h"
#include "td/telegram/CallId.h"
#include "td/telegram/CallManager.h"
@ -49,11 +50,13 @@
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/JsonValue.h"
#include "td/telegram/LanguagePackManager.h"
#include "td/telegram/LinkManager.h"
#include "td/telegram/Location.h"
#include "td/telegram/Logging.h"
#include "td/telegram/MessageCopyOptions.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageSearchFilter.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
@ -404,7 +407,7 @@ class UpdateStatusQuery : public Td::ResultHandler {
}
void on_error(uint64 id, Status status) override {
if (status.code() != NetQuery::Cancelled && !G()->is_expected_error(status)) {
if (status.code() != NetQuery::Canceled && !G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for UpdateStatusQuery: " << status;
}
status.ignore();
@ -889,7 +892,7 @@ class SearchPublicChatRequest : public RequestActor<> {
public:
SearchPublicChatRequest(ActorShared<Td> td, uint64 request_id, string username)
: RequestActor(std::move(td), request_id), username_(std::move(username)) {
set_tries(3);
set_tries(4); // 1 for server request + 1 for reload voice chat + 1 for reload dialog + 1 for result
}
};
@ -1177,12 +1180,12 @@ class GetMessageEmbeddingCodeRequest : public RequestActor<> {
}
};
class GetMessageLinkInfoRequest : public RequestActor<MessagesManager::MessageLinkInfo> {
class GetMessageLinkInfoRequest : public RequestActor<MessageLinkInfo> {
string url_;
MessagesManager::MessageLinkInfo message_link_info_;
MessageLinkInfo message_link_info_;
void do_run(Promise<MessagesManager::MessageLinkInfo> &&promise) override {
void do_run(Promise<MessageLinkInfo> &&promise) override {
if (get_tries() < 2) {
promise.set_value(std::move(message_link_info_));
return;
@ -1190,7 +1193,7 @@ class GetMessageLinkInfoRequest : public RequestActor<MessagesManager::MessageLi
td->messages_manager_->get_message_link_info(url_, std::move(promise));
}
void do_set_result(MessagesManager::MessageLinkInfo &&result) override {
void do_set_result(MessageLinkInfo &&result) override {
message_link_info_ = std::move(result);
}
@ -1873,7 +1876,7 @@ class CreateNewSecretChatRequest : public RequestActor<SecretChatId> {
void do_send_result() override {
CHECK(secret_chat_id_.is_valid());
// SecretChatActor will send this update by himself.
// SecretChatActor will send this update by itself
// But since the update may still be on its way, we will update essential fields here.
td->contacts_manager_->on_update_secret_chat(
secret_chat_id_, 0 /* no access_hash */, user_id_, SecretChatState::Unknown, true /* it is outbound chat */,
@ -2535,12 +2538,12 @@ class ChangeStickerSetRequest : public RequestOnceActor {
class UploadStickerFileRequest : public RequestOnceActor {
UserId user_id_;
tl_object_ptr<td_api::InputFile> sticker_;
tl_object_ptr<td_api::InputSticker> sticker_;
FileId file_id;
void do_run(Promise<Unit> &&promise) override {
file_id = td->stickers_manager_->upload_sticker_file(user_id_, sticker_, std::move(promise));
file_id = td->stickers_manager_->upload_sticker_file(user_id_, std::move(sticker_), std::move(promise));
}
void do_send_result() override {
@ -2549,7 +2552,7 @@ class UploadStickerFileRequest : public RequestOnceActor {
public:
UploadStickerFileRequest(ActorShared<Td> td, uint64 request_id, int32 user_id,
tl_object_ptr<td_api::InputFile> &&sticker)
tl_object_ptr<td_api::InputSticker> &&sticker)
: RequestOnceActor(std::move(td), request_id), user_id_(user_id), sticker_(std::move(sticker)) {
}
};
@ -2560,10 +2563,11 @@ class CreateNewStickerSetRequest : public RequestOnceActor {
string name_;
bool is_masks_;
vector<tl_object_ptr<td_api::InputSticker>> stickers_;
string software_;
void do_run(Promise<Unit> &&promise) override {
td->stickers_manager_->create_new_sticker_set(user_id_, title_, name_, is_masks_, std::move(stickers_),
std::move(promise));
std::move(software_), std::move(promise));
}
void do_send_result() override {
@ -2576,13 +2580,14 @@ class CreateNewStickerSetRequest : public RequestOnceActor {
public:
CreateNewStickerSetRequest(ActorShared<Td> td, uint64 request_id, int32 user_id, string &&title, string &&name,
bool is_masks, vector<tl_object_ptr<td_api::InputSticker>> &&stickers)
bool is_masks, vector<tl_object_ptr<td_api::InputSticker>> &&stickers, string &&software)
: RequestOnceActor(std::move(td), request_id)
, user_id_(user_id)
, title_(std::move(title))
, name_(std::move(name))
, is_masks_(is_masks)
, stickers_(std::move(stickers)) {
, stickers_(std::move(stickers))
, software_(std::move(software)) {
}
};
@ -2960,14 +2965,14 @@ class GetBackgroundsRequest : public RequestOnceActor {
class SearchBackgroundRequest : public RequestActor<> {
string name_;
BackgroundId background_id_;
std::pair<BackgroundId, BackgroundType> background_;
void do_run(Promise<Unit> &&promise) override {
background_id_ = td->background_manager_->search_background(name_, std::move(promise));
background_ = td->background_manager_->search_background(name_, std::move(promise));
}
void do_send_result() override {
send_result(td->background_manager_->get_background_object(background_id_, false));
send_result(td->background_manager_->get_background_object(background_.first, false, &background_.second));
}
public:
@ -3188,13 +3193,6 @@ void Td::schedule_get_promo_data(int32 expires_in) {
}
}
void Td::on_channel_unban_timeout(int64 channel_id_long) {
if (close_flag_ >= 2) {
return;
}
contacts_manager_->on_channel_unban_timeout(ChannelId(narrow_cast<int32>(channel_id_long)));
}
bool Td::is_online() const {
return is_online_;
}
@ -3288,6 +3286,7 @@ bool Td::is_preinitialization_request(int32 id) {
bool Td::is_preauthentication_request(int32 id) {
switch (id) {
case td_api::getInternalLinkType::ID:
case td_api::getLocalizationTargetInfo::ID:
case td_api::getLanguagePackInfo::ID:
case td_api::getLanguagePackStrings::ID:
@ -3614,7 +3613,9 @@ void Td::on_config_option_updated(const string &name) {
return;
}
if (name == "auth") {
return on_authorization_lost();
send_closure(auth_manager_actor_, &AuthManager::on_authorization_lost,
G()->shared_config().get_option_string(name));
return;
} else if (name == "saved_animations_limit") {
return animations_manager_->on_update_saved_animations_limit(
narrow_cast<int32>(G()->shared_config().get_option_integer(name)));
@ -3722,11 +3723,6 @@ void Td::on_connection_state_changed(StateManager::State new_state) {
make_tl_object<td_api::updateConnectionState>(get_connection_state_object(connection_state_)));
}
void Td::on_authorization_lost() {
LOG(WARNING) << "Lost authorization";
send_closure(auth_manager_actor_, &AuthManager::on_authorization_lost);
}
void Td::start_up() {
always_wait_for_mailbox();
@ -3822,6 +3818,8 @@ void Td::dec_actor_refcnt() {
LOG(DEBUG) << "GroupCallManager was cleared" << timer;
inline_queries_manager_.reset();
LOG(DEBUG) << "InlineQueriesManager was cleared" << timer;
link_manager_.reset();
LOG(DEBUG) << "LinkManager was cleared" << timer;
messages_manager_.reset();
LOG(DEBUG) << "MessagesManager was cleared" << timer;
notification_manager_.reset();
@ -4021,6 +4019,8 @@ void Td::clear() {
LOG(DEBUG) << "GroupCallManager actor was cleared" << timer;
inline_queries_manager_actor_.reset();
LOG(DEBUG) << "InlineQueriesManager actor was cleared" << timer;
link_manager_actor_.reset();
LOG(DEBUG) << "LinkManager actor was cleared" << timer;
messages_manager_actor_.reset(); // TODO: Stop silent
LOG(DEBUG) << "MessagesManager actor was cleared" << timer;
notification_manager_actor_.reset();
@ -4470,6 +4470,9 @@ void Td::init_managers() {
G()->set_group_call_manager(group_call_manager_actor_.get());
inline_queries_manager_ = make_unique<InlineQueriesManager>(this, create_reference());
inline_queries_manager_actor_ = register_actor("InlineQueriesManager", inline_queries_manager_.get());
link_manager_ = make_unique<LinkManager>(this, create_reference());
link_manager_actor_ = register_actor("LinkManager", link_manager_.get());
G()->set_link_manager(link_manager_actor_.get());
messages_manager_ = make_unique<MessagesManager>(this, create_reference());
messages_manager_actor_ = register_actor("MessagesManager", messages_manager_.get());
G()->set_messages_manager(messages_manager_actor_.get());
@ -5480,8 +5483,8 @@ void Td::on_request(uint64 id, td_api::checkChatUsername &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.username_);
CREATE_REQUEST_PROMISE();
auto query_promise =
PromiseCreator::lambda([promise = std::move(promise)](Result<CheckDialogUsernameResult> result) mutable {
auto query_promise = PromiseCreator::lambda(
[promise = std::move(promise)](Result<ContactsManager::CheckDialogUsernameResult> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
@ -5551,19 +5554,23 @@ void Td::on_request(uint64 id, const td_api::openMessageContent &request) {
id, messages_manager_->open_message_content({DialogId(request.chat_id_), MessageId(request.message_id_)}));
}
void Td::on_request(uint64 id, const td_api::getInternalLinkType &request) {
auto type = link_manager_->parse_internal_link(request.link_);
send_closure(actor_id(this), &Td::send_result, id, type == nullptr ? nullptr : type->get_internal_link_type_object());
}
void Td::on_request(uint64 id, td_api::getExternalLinkInfo &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.link_);
CREATE_REQUEST_PROMISE();
send_closure_later(G()->config_manager(), &ConfigManager::get_external_link_info, std::move(request.link_),
std::move(promise));
link_manager_->get_external_link_info(std::move(request.link_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getExternalLink &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.link_);
CREATE_REQUEST_PROMISE();
messages_manager_->get_link_login_url(request.link_, request.allow_write_access_, std::move(promise));
link_manager_->get_link_login_url(request.link_, request.allow_write_access_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getChatHistory &request) {
@ -6093,10 +6100,47 @@ void Td::on_request(uint64 id, const td_api::toggleGroupCallEnabledStartNotifica
void Td::on_request(uint64 id, td_api::joinGroupCall &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.invite_hash_);
CLEAN_INPUT_STRING(request.payload_);
CREATE_REQUEST_PROMISE();
group_call_manager_->join_group_call(
GroupCallId(request.group_call_id_), group_call_manager_->get_group_call_participant_id(request.participant_id_),
std::move(request.payload_), request.source_, request.is_muted_, request.invite_hash_, std::move(promise));
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
}
});
group_call_manager_->join_group_call(GroupCallId(request.group_call_id_),
group_call_manager_->get_group_call_participant_id(request.participant_id_),
request.audio_source_id_, std::move(request.payload_), request.is_muted_,
request.is_my_video_enabled_, request.invite_hash_, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::startGroupCallScreenSharing &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.payload_);
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
}
});
group_call_manager_->start_group_call_screen_sharing(GroupCallId(request.group_call_id_), std::move(request.payload_),
std::move(query_promise));
}
void Td::on_request(uint64 id, const td_api::toggleGroupCallScreenSharingIsPaused &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->toggle_group_call_is_my_presentation_paused(GroupCallId(request.group_call_id_),
request.is_paused_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::endGroupCallScreenSharing &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->end_group_call_screen_sharing(GroupCallId(request.group_call_id_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::setGroupCallTitle &request) {
@ -6156,11 +6200,25 @@ void Td::on_request(uint64 id, const td_api::endGroupCallRecording &request) {
std::move(promise));
}
void Td::on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoPaused &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->toggle_group_call_is_my_video_paused(GroupCallId(request.group_call_id_),
request.is_my_video_paused_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoEnabled &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->toggle_group_call_is_my_video_enabled(GroupCallId(request.group_call_id_),
request.is_my_video_enabled_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::setGroupCallParticipantIsSpeaking &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->set_group_call_participant_is_speaking(GroupCallId(request.group_call_id_), request.source_,
request.is_speaking_, std::move(promise));
group_call_manager_->set_group_call_participant_is_speaking(
GroupCallId(request.group_call_id_), request.audio_source_, request.is_speaking_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request) {
@ -6454,8 +6512,8 @@ void Td::on_request(uint64 id, const td_api::banChatMember &request) {
void Td::on_request(uint64 id, const td_api::canTransferOwnership &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
auto query_promise =
PromiseCreator::lambda([promise = std::move(promise)](Result<CanTransferOwnershipResult> result) mutable {
auto query_promise = PromiseCreator::lambda(
[promise = std::move(promise)](Result<ContactsManager::CanTransferOwnershipResult> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
@ -6490,7 +6548,7 @@ void Td::on_request(uint64 id, td_api::searchChatMembers &request) {
}
});
contacts_manager_->search_dialog_participants(DialogId(request.chat_id_), request.query_, request.limit_,
get_dialog_participants_filter(request.filter_), false,
get_dialog_participants_filter(request.filter_),
std::move(query_promise));
}
@ -6619,7 +6677,7 @@ void Td::on_request(uint64 id, const td_api::downloadFile &request) {
// we can't have two pending requests with different offset and limit, so cancel all previous requests
for (auto request_id : info->request_ids) {
send_closure(actor_id(this), &Td::send_error, request_id,
Status::Error(200, "Cancelled by another downloadFile request"));
Status::Error(200, "Canceled by another downloadFile request"));
}
info->request_ids.clear();
}
@ -6659,7 +6717,7 @@ void Td::on_file_download_finished(FileId file_id) {
download_offset + downloaded_size - it->second.offset >= limit))) {
send_result(id, std::move(file_object));
} else {
send_error_impl(id, td_api::make_object<td_api::error>(400, "File download has failed or was cancelled"));
send_error_impl(id, td_api::make_object<td_api::error>(400, "File download has failed or was canceled"));
}
}
pending_file_downloads_.erase(it);
@ -6887,7 +6945,20 @@ void Td::on_request(uint64 id, td_api::setUsername &request) {
void Td::on_request(uint64 id, td_api::setCommands &request) {
CHECK_IS_BOT();
CREATE_OK_REQUEST_PROMISE();
contacts_manager_->set_commands(std::move(request.commands_), std::move(promise));
set_commands(this, std::move(request.scope_), std::move(request.language_code_), std::move(request.commands_),
std::move(promise));
}
void Td::on_request(uint64 id, td_api::deleteCommands &request) {
CHECK_IS_BOT();
CREATE_OK_REQUEST_PROMISE();
delete_commands(this, std::move(request.scope_), std::move(request.language_code_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getCommands &request) {
CHECK_IS_BOT();
CREATE_REQUEST_PROMISE();
get_commands(this, std::move(request.scope_), std::move(request.language_code_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::setLocation &request) {
@ -6963,7 +7034,7 @@ void Td::on_request(uint64 id, td_api::getSupergroupMembers &request) {
}
});
contacts_manager_->get_channel_participants(ChannelId(request.supergroup_id_), std::move(request.filter_), string(),
request.offset_, request.limit_, -1, false, std::move(query_promise));
request.offset_, request.limit_, -1, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::closeSecretChat &request) {
@ -7042,16 +7113,42 @@ void Td::on_request(uint64 id, td_api::reorderInstalledStickerSets &request) {
}
void Td::on_request(uint64 id, td_api::uploadStickerFile &request) {
CHECK_IS_BOT();
CREATE_REQUEST(UploadStickerFileRequest, request.user_id_, std::move(request.png_sticker_));
CREATE_REQUEST(UploadStickerFileRequest, request.user_id_, std::move(request.sticker_));
}
void Td::on_request(uint64 id, td_api::getSuggestedStickerSetName &request) {
CLEAN_INPUT_STRING(request.title_);
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
}
});
stickers_manager_->get_suggested_sticker_set_name(std::move(request.title_), std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::checkStickerSetName &request) {
CLEAN_INPUT_STRING(request.name_);
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda(
[promise = std::move(promise)](Result<StickersManager::CheckStickerSetNameResult> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(StickersManager::get_check_sticker_set_name_result_object(result.ok()));
}
});
stickers_manager_->check_sticker_set_name(request.name_, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::createNewStickerSet &request) {
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.title_);
CLEAN_INPUT_STRING(request.name_);
CLEAN_INPUT_STRING(request.source_);
CREATE_REQUEST(CreateNewStickerSetRequest, request.user_id_, std::move(request.title_), std::move(request.name_),
request.is_masks_, std::move(request.stickers_));
request.is_masks_, std::move(request.stickers_), std::move(request.source_));
}
void Td::on_request(uint64 id, td_api::addStickerToSet &request) {
@ -7798,15 +7895,15 @@ void Td::on_request(uint64 id, const td_api::hideSuggestedAction &request) {
void Td::on_request(uint64 id, const td_api::getLoginUrlInfo &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
messages_manager_->get_login_url_info(DialogId(request.chat_id_), MessageId(request.message_id_), request.button_id_,
std::move(promise));
link_manager_->get_login_url_info({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.button_id_,
std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getLoginUrl &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
messages_manager_->get_login_url(DialogId(request.chat_id_), MessageId(request.message_id_), request.button_id_,
request.allow_write_access_, std::move(promise));
link_manager_->get_login_url({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.button_id_,
request.allow_write_access_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::getInlineQueryResults &request) {
@ -8213,14 +8310,14 @@ void Td::on_request(uint64 id, const td_api::getPhoneNumberInfo &request) {
country_info_manager_->get_phone_number_info(request.phone_number_prefix_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getInviteText &request) {
void Td::on_request(uint64 id, const td_api::getApplicationDownloadLink &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
promise.set_value(make_tl_object<td_api::httpUrl>(result.move_as_ok()));
}
});
create_handler<GetInviteTextQuery>(std::move(query_promise))->send();

View File

@ -61,6 +61,7 @@ class GroupCallManager;
class InlineQueriesManager;
class HashtagHints;
class LanguagePackManager;
class LinkManager;
class MessagesManager;
class NetStatsManager;
class NotificationManager;
@ -125,13 +126,9 @@ class Td final : public NetQueryCallback {
void on_update_server_time_difference();
void on_authorization_lost();
void on_online_updated(bool force, bool send_update);
void on_update_status_success(bool is_online);
void on_channel_unban_timeout(int64 channel_id_long);
bool is_online() const;
void set_is_bot_online(bool is_bot_online);
@ -174,6 +171,8 @@ class Td final : public NetQueryCallback {
ActorOwn<GroupCallManager> group_call_manager_actor_;
unique_ptr<InlineQueriesManager> inline_queries_manager_;
ActorOwn<InlineQueriesManager> inline_queries_manager_actor_;
unique_ptr<LinkManager> link_manager_;
ActorOwn<LinkManager> link_manager_actor_;
unique_ptr<MessagesManager> messages_manager_;
ActorOwn<MessagesManager> messages_manager_actor_;
unique_ptr<NotificationManager> notification_manager_;
@ -595,6 +594,8 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::openMessageContent &request);
void on_request(uint64 id, const td_api::getInternalLinkType &request);
void on_request(uint64 id, td_api::getExternalLinkInfo &request);
void on_request(uint64 id, td_api::getExternalLink &request);
@ -733,6 +734,10 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::joinGroupCall &request);
void on_request(uint64 id, td_api::startGroupCallScreenSharing &request);
void on_request(uint64 id, const td_api::endGroupCallScreenSharing &request);
void on_request(uint64 id, td_api::setGroupCallTitle &request);
void on_request(uint64 id, const td_api::toggleGroupCallMuteNewParticipants &request);
@ -745,8 +750,14 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::startGroupCallRecording &request);
void on_request(uint64 id, const td_api::toggleGroupCallScreenSharingIsPaused &request);
void on_request(uint64 id, const td_api::endGroupCallRecording &request);
void on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoPaused &request);
void on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoEnabled &request);
void on_request(uint64 id, const td_api::setGroupCallParticipantIsSpeaking &request);
void on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request);
@ -925,6 +936,10 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::setCommands &request);
void on_request(uint64 id, td_api::deleteCommands &request);
void on_request(uint64 id, td_api::getCommands &request);
void on_request(uint64 id, const td_api::setLocation &request);
void on_request(uint64 id, td_api::setProfilePhoto &request);
@ -977,6 +992,10 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::uploadStickerFile &request);
void on_request(uint64 id, td_api::getSuggestedStickerSetName &request);
void on_request(uint64 id, td_api::checkStickerSetName &request);
void on_request(uint64 id, td_api::createNewStickerSet &request);
void on_request(uint64 id, td_api::addStickerToSet &request);
@ -1173,7 +1192,7 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::getPhoneNumberInfo &request);
void on_request(uint64 id, const td_api::getInviteText &request);
void on_request(uint64 id, const td_api::getApplicationDownloadLink &request);
void on_request(uint64 id, td_api::getDeepLinkInfo &request);

View File

@ -223,7 +223,7 @@ void UpdatesManager::fill_get_difference_gap(void *td) {
void UpdatesManager::fill_gap(void *td, const char *source) {
CHECK(td != nullptr);
if (G()->close_flag()) {
if (G()->close_flag() || !static_cast<Td *>(td)->auth_manager_->is_authorized()) {
return;
}
auto updates_manager = static_cast<Td *>(td)->updates_manager_.get();
@ -236,7 +236,7 @@ void UpdatesManager::fill_gap(void *td, const char *source) {
}
void UpdatesManager::get_difference(const char *source) {
if (G()->close_flag()) {
if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
if (get_pts() == -1) {
@ -244,10 +244,6 @@ void UpdatesManager::get_difference(const char *source) {
return;
}
if (!td_->auth_manager_->is_authorized()) {
return;
}
if (running_get_difference_) {
VLOG(get_difference) << "Skip running getDifference from " << source << " because it is already running";
return;
@ -881,7 +877,7 @@ void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updat
}
void UpdatesManager::on_failed_get_updates_state(Status &&error) {
if (G()->close_flag()) {
if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
if (error.code() != 401) {
@ -893,7 +889,7 @@ void UpdatesManager::on_failed_get_updates_state(Status &&error) {
}
void UpdatesManager::on_failed_get_difference(Status &&error) {
if (G()->close_flag()) {
if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
if (error.code() != 401) {
@ -909,8 +905,12 @@ void UpdatesManager::on_failed_get_difference(Status &&error) {
}
void UpdatesManager::schedule_get_difference(const char *source) {
if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
if (!retry_timeout_.has_timeout()) {
LOG(WARNING) << "Schedule getDifference in " << retry_time_ << " seconds from " << source;
LOG(WARNING) << "Schedule getDifference in " << retry_time_ << " seconds with pts = " << get_pts()
<< ", qts = " << get_qts() << ", date = " << get_date() << " from " << source;
retry_timeout_.set_callback(std::move(fill_get_difference_gap));
retry_timeout_.set_callback_data(static_cast<void *>(td_));
retry_timeout_.set_timeout_in(retry_time_);
@ -969,6 +969,11 @@ const vector<tl_object_ptr<telegram_api::Update>> *UpdatesManager::get_updates(
}
}
vector<tl_object_ptr<telegram_api::Update>> *UpdatesManager::get_updates(telegram_api::Updates *updates_ptr) {
return const_cast<vector<tl_object_ptr<telegram_api::Update>> *>(
get_updates(static_cast<const telegram_api::Updates *>(updates_ptr)));
}
std::unordered_set<int64> UpdatesManager::get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr) {
std::unordered_set<int64> random_ids;
auto updates = get_updates(updates_ptr);
@ -1026,6 +1031,20 @@ vector<InputGroupCallId> UpdatesManager::get_update_new_group_call_ids(const tel
return input_group_call_ids;
}
string UpdatesManager::extract_join_group_call_presentation_params(telegram_api::Updates *updates_ptr) {
auto updates = get_updates(updates_ptr);
for (auto it = updates->begin(); it != updates->end(); ++it) {
auto *update = it->get();
if (update->get_id() == telegram_api::updateGroupCallConnection::ID &&
static_cast<const telegram_api::updateGroupCallConnection *>(update)->presentation_) {
string result = std::move(static_cast<telegram_api::updateGroupCallConnection *>(update)->params_->data_);
updates->erase(it);
return result;
}
}
return string();
}
vector<DialogId> UpdatesManager::get_update_notify_settings_dialog_ids(const telegram_api::Updates *updates_ptr) {
vector<DialogId> dialog_ids;
auto updates = get_updates(updates_ptr);
@ -1128,7 +1147,7 @@ int32 UpdatesManager::get_update_edit_message_pts(const telegram_api::Updates *u
}
void UpdatesManager::init_state() {
if (!td_->auth_manager_->is_authorized()) {
if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
@ -1737,6 +1756,12 @@ void UpdatesManager::process_updates(vector<tl_object_ptr<telegram_api::Update>>
continue;
}
// updateGroupCallConnection must be processed before updateGroupCall
if (constructor_id == telegram_api::updateGroupCallConnection::ID) {
on_update(move_tl_object_as<telegram_api::updateGroupCallConnection>(update), mpas.get_promise());
continue;
}
// updatePtsChanged forces get difference, so process it last
if (constructor_id == telegram_api::updatePtsChanged::ID) {
update_pts_changed = move_tl_object_as<telegram_api::updatePtsChanged>(update);
@ -1977,6 +2002,13 @@ void UpdatesManager::process_qts_update(tl_object_ptr<telegram_api::Update> &&up
add_qts(qts));
break;
}
case telegram_api::updateMessagePollVote::ID: {
auto update = move_tl_object_as<telegram_api::updateMessagePollVote>(update_ptr);
td_->poll_manager_->on_get_poll_vote(PollId(update->poll_id_), UserId(update->user_id_),
std::move(update->options_));
add_qts(qts).set_value(Unit());
break;
}
case telegram_api::updateBotStopped::ID: {
auto update = move_tl_object_as<telegram_api::updateBotStopped>(update_ptr);
td_->contacts_manager_->on_update_bot_stopped(UserId(update->user_id_), update->date_,
@ -2494,6 +2526,7 @@ int32 UpdatesManager::get_update_pts(const telegram_api::Update *update) {
bool UpdatesManager::is_qts_update(const telegram_api::Update *update) {
switch (update->get_id()) {
case telegram_api::updateNewEncryptedMessage::ID:
case telegram_api::updateMessagePollVote::ID:
case telegram_api::updateBotStopped::ID:
case telegram_api::updateChatParticipant::ID:
case telegram_api::updateChannelParticipant::ID:
@ -2507,6 +2540,8 @@ int32 UpdatesManager::get_update_qts(const telegram_api::Update *update) {
switch (update->get_id()) {
case telegram_api::updateNewEncryptedMessage::ID:
return static_cast<const telegram_api::updateNewEncryptedMessage *>(update)->qts_;
case telegram_api::updateMessagePollVote::ID:
return static_cast<const telegram_api::updateMessagePollVote *>(update)->qts_;
case telegram_api::updateBotStopped::ID:
return static_cast<const telegram_api::updateBotStopped *>(update)->qts_;
case telegram_api::updateChatParticipant::ID:
@ -2824,6 +2859,16 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePhoneCallSignal
promise.set_value(Unit());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCallConnection> update, Promise<Unit> &&promise) {
if (update->presentation_) {
LOG(ERROR) << "Receive unexpected updateGroupCallConnection";
} else {
send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call_connection,
std::move(update->params_->data_));
}
promise.set_value(Unit());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCall> update, Promise<Unit> &&promise) {
DialogId dialog_id(ChatId(update->chat_id_));
if (!td_->messages_manager_->have_dialog_force(dialog_id, "updateGroupCall")) {
@ -2872,8 +2917,8 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePoll> up
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePollVote> update, Promise<Unit> &&promise) {
td_->poll_manager_->on_get_poll_vote(PollId(update->poll_id_), UserId(update->user_id_), std::move(update->options_));
promise.set_value(Unit());
auto qts = update->qts_;
add_pending_qts_update(std::move(update), qts, std::move(promise));
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewScheduledMessage> update, Promise<Unit> &&promise) {

View File

@ -104,6 +104,8 @@ class UpdatesManager : public Actor {
static vector<InputGroupCallId> get_update_new_group_call_ids(const telegram_api::Updates *updates_ptr);
static string extract_join_group_call_presentation_params(telegram_api::Updates *updates_ptr);
static vector<DialogId> get_update_notify_settings_dialog_ids(const telegram_api::Updates *updates_ptr);
static vector<DialogId> get_chat_dialog_ids(const telegram_api::Updates *updates_ptr);
@ -308,6 +310,8 @@ class UpdatesManager : public Actor {
static const vector<tl_object_ptr<telegram_api::Update>> *get_updates(const telegram_api::Updates *updates_ptr);
static vector<tl_object_ptr<telegram_api::Update>> *get_updates(telegram_api::Updates *updates_ptr);
bool is_acceptable_user(UserId user_id) const;
bool is_acceptable_chat(ChatId chat_id) const;
@ -434,6 +438,7 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updatePhoneCall> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updatePhoneCallSignalingData> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateGroupCallConnection> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateGroupCall> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update, Promise<Unit> &&promise);

View File

@ -8,7 +8,7 @@
namespace td {
constexpr int32 MTPROTO_LAYER = 127;
constexpr int32 MTPROTO_LAYER = 130;
enum class Version : int32 {
Initial, // 0
@ -43,6 +43,7 @@ enum class Version : int32 {
AddLiveLocationHeading,
AddLiveLocationProximityAlertDistance, // 30
SupportBannedChannels,
RemovePhotoVolumeAndLocalId,
Next
};

View File

@ -6,15 +6,15 @@
//
#include "td/telegram/VideoNotesManager.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/SecretChatActor.h"
#include "td/telegram/ConfigShared.h"
#include "td/telegram/Td.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Status.h"
@ -171,9 +171,7 @@ void VideoNotesManager::create_video_note(FileId file_id, string minithumbnail,
} else {
LOG(INFO) << "Receive wrong video note dimensions " << dimensions;
}
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
v->minithumbnail = "";
} else {
if (!td_->auth_manager_->is_bot() && !G()->shared_config().get_option_boolean("disable_minithumbnails")) {
v->minithumbnail = std::move(minithumbnail);
}
v->thumbnail = std::move(thumbnail);
@ -182,7 +180,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<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer) const {
BufferSlice thumbnail) 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);
@ -199,7 +197,6 @@ SecretInputMedia VideoNotesManager::get_secret_input_media(FileId video_note_fil
if (video_note->thumbnail.file_id.is_valid() && thumbnail.empty()) {
return SecretInputMedia{};
}
CHECK(layer >= SecretChatActor::VIDEO_NOTES_LAYER);
vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
attributes.push_back(make_tl_object<secret_api::documentAttributeVideo66>(
secret_api::documentAttributeVideo66::ROUND_MESSAGE_MASK, true, video_note->duration,

View File

@ -43,7 +43,7 @@ class VideoNotesManager {
SecretInputMedia get_secret_input_media(FileId video_note_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer) const;
BufferSlice thumbnail) const;
FileId get_video_note_thumbnail_file_id(FileId file_id) const;

View File

@ -6,14 +6,14 @@
//
#include "td/telegram/VideosManager.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/ConfigShared.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/Td.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Status.h"
@ -216,9 +216,7 @@ void VideosManager::create_video(FileId file_id, string minithumbnail, PhotoSize
v->mime_type = std::move(mime_type);
v->duration = max(duration, 0);
v->dimensions = dimensions;
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
v->minithumbnail = "";
} else {
if (!td_->auth_manager_->is_bot() && G()->shared_config().get_option_boolean("disable_minithumbnails")) {
v->minithumbnail = std::move(minithumbnail);
}
v->thumbnail = std::move(thumbnail);

View File

@ -6,13 +6,12 @@
//
#include "td/telegram/VoiceNotesManager.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/Td.h"
#include "td/utils/buffer.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"

View File

@ -12,8 +12,8 @@
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
#include "td/telegram/ClientActor.h"
#include "td/telegram/Client.h"
#include "td/telegram/ClientActor.h"
#include "td/telegram/Td.h" // for VERBOSITY_NAME(td_requests)
#include "td/telegram/td_api_json.h"
@ -145,7 +145,6 @@ static char *command_generator(const char *text, int state) {
"CancelDownloadFile",
"ImportContacts",
"RemoveContacts",
"DumpNetQueries",
"CreateSecretChat",
"CreateNewSecretChat"};
static size_t cmd_i;
@ -434,7 +433,7 @@ class CliClient final : public Actor {
static char get_delimiter(Slice str) {
std::unordered_set<char> chars;
for (auto c : trim(str)) {
if (!is_alnum(c) && c != '-') {
if (!is_alnum(c) && c != '-' && c != '.' && c != '/') {
chars.insert(c);
}
}
@ -835,7 +834,7 @@ class CliClient final : public Actor {
if (message != nullptr && message->content_->get_id() == td_api::messageText::ID) {
auto chat_id = message->chat_id_;
auto text = static_cast<const td_api::messageText *>(message->content_.get())->text_->text_;
if (text == "/start" && use_test_dc_) {
if (text == "/start" && !message->is_outgoing_ && use_test_dc_) {
on_cmd(PSTRING() << "sm " << chat_id << " Hi!");
}
}
@ -1389,6 +1388,9 @@ class CliClient final : public Actor {
if (action == "unarchive") {
return td_api::make_object<td_api::suggestedActionEnableArchiveAndMuteNewChats>();
}
if (action == "pass") {
return td_api::make_object<td_api::suggestedActionCheckPassword>();
}
if (action == "number") {
return td_api::make_object<td_api::suggestedActionCheckPhoneNumber>();
}
@ -1536,6 +1538,10 @@ class CliClient final : public Actor {
return td_api::make_object<td_api::backgroundFillGradient>(top_color, bottom_color, Random::fast(0, 7) * 45);
}
static td_api::object_ptr<td_api::BackgroundFill> get_background_fill(vector<int32> colors) {
return td_api::make_object<td_api::backgroundFillFreeformGradient>(std::move(colors));
}
static td_api::object_ptr<td_api::BackgroundType> get_solid_pattern_background(int32 color, int32 intensity,
bool is_moving) {
return get_gradient_pattern_background(color, color, intensity, is_moving);
@ -1547,6 +1553,13 @@ class CliClient final : public Actor {
is_moving);
}
static td_api::object_ptr<td_api::BackgroundType> get_freeform_gradient_pattern_background(vector<int32> colors,
int32 intensity,
bool is_moving) {
return td_api::make_object<td_api::backgroundTypePattern>(get_background_fill(std::move(colors)), intensity,
is_moving);
}
static td_api::object_ptr<td_api::BackgroundType> get_solid_background(int32 color) {
return td_api::make_object<td_api::backgroundTypeFill>(get_background_fill(color));
}
@ -1555,6 +1568,10 @@ class CliClient final : public Actor {
return td_api::make_object<td_api::backgroundTypeFill>(get_background_fill(top_color, bottom_color));
}
static td_api::object_ptr<td_api::BackgroundType> get_freeform_gradient_background(vector<int32> colors) {
return td_api::make_object<td_api::backgroundTypeFill>(get_background_fill(std::move(colors)));
}
static td_api::object_ptr<td_api::Object> execute(td_api::object_ptr<td_api::Function> f) {
if (combined_log.get_first_verbosity_level() < VERBOSITY_NAME(td_requests)) {
LOG(ERROR) << "Execute request: " << to_string(f);
@ -2207,12 +2224,19 @@ class CliClient final : public Actor {
send_get_background_url(get_gradient_pattern_background(0xFFFFFF, 0, 100, true));
send_get_background_url(get_gradient_pattern_background(0xABCDEF, 0xFEDCBA, 49, true));
send_get_background_url(get_gradient_pattern_background(0, 0x1000000, 49, true));
send_get_background_url(get_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA}, 49, true));
send_get_background_url(get_freeform_gradient_pattern_background({0xABCDEF, 0x111111, 0x222222}, 49, true));
send_get_background_url(
get_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA, 0x111111, 0x222222}, 49, true));
send_get_background_url(get_solid_background(-1));
send_get_background_url(get_solid_background(0xABCDEF));
send_get_background_url(get_solid_background(0x1000000));
send_get_background_url(get_gradient_background(0xABCDEF, 0xFEDCBA));
send_get_background_url(get_gradient_background(0, 0));
send_get_background_url(get_gradient_background(-1, -1));
send_get_background_url(get_freeform_gradient_background({0xFEDCBA, 0x222222}));
send_get_background_url(get_freeform_gradient_background({0xFEDCBA, 0x111111, 0x222222}));
send_get_background_url(get_freeform_gradient_background({0xABCDEF, 0xFEDCBA, 0x111111, 0x222222}));
} else if (op == "sbg") {
send_request(td_api::make_object<td_api::searchBackground>(args));
} else if (op == "sbgd") {
@ -2230,21 +2254,34 @@ class CliClient final : public Actor {
td_api::make_object<td_api::inputBackgroundLocal>(as_input_file(args)),
get_gradient_pattern_background(0xABCDEF, 0xFE, 51, false), op == "sbggpd"));
} else if (op == "sbgs" || op == "sbgsd") {
send_request(td_api::make_object<td_api::setBackground>(nullptr, get_solid_background(to_integer<int32>(args)),
op == "sbgsd"));
int32 color;
get_args(args, color);
send_request(td_api::make_object<td_api::setBackground>(nullptr, get_solid_background(color), op == "sbgsd"));
} else if (op == "sbgg" || op == "sbggd") {
int32 top_color;
int32 bottom_color;
get_args(args, top_color, bottom_color);
auto background_type = get_gradient_background(top_color, bottom_color);
send_request(td_api::make_object<td_api::setBackground>(nullptr, std::move(background_type), op == "sbggd"));
} else if (op == "sbgwid" || op == "sbgwidd") {
} else if (op == "sbgfg" || op == "sbgfgd") {
auto background_type = get_freeform_gradient_background(to_integers<int32>(args));
send_request(td_api::make_object<td_api::setBackground>(nullptr, std::move(background_type), op == "sbgfgd"));
} else if (op == "sbgfid" || op == "sbgfidd") {
int64 background_id;
get_args(args, background_id);
send_request(td_api::make_object<td_api::setBackground>(
td_api::make_object<td_api::inputBackgroundRemote>(to_integer<int64>(args)),
td_api::make_object<td_api::inputBackgroundRemote>(background_id), nullptr, op == "sbgfidd"));
} else if (op == "sbgwid" || op == "sbgwidd") {
int64 background_id;
get_args(args, background_id);
send_request(td_api::make_object<td_api::setBackground>(
td_api::make_object<td_api::inputBackgroundRemote>(background_id),
td_api::make_object<td_api::backgroundTypeWallpaper>(true, true), op == "sbgwidd"));
} else if (op == "sbgpid" || op == "sbgpidd") {
int64 background_id;
get_args(args, background_id);
send_request(td_api::make_object<td_api::setBackground>(
td_api::make_object<td_api::inputBackgroundRemote>(to_integer<int64>(args)),
td_api::make_object<td_api::inputBackgroundRemote>(background_id),
get_solid_pattern_background(0xabcdef, 49, true), op == "sbgpidd"));
} else if (op == "rbg") {
send_request(td_api::make_object<td_api::removeBackground>(to_integer<int64>(args)));
@ -2256,8 +2293,8 @@ class CliClient final : public Actor {
send_request(td_api::make_object<td_api::getCountryCode>());
} else if (op == "gpni") {
send_request(td_api::make_object<td_api::getPhoneNumberInfo>(args));
} else if (op == "git") {
send_request(td_api::make_object<td_api::getInviteText>());
} else if (op == "gadl") {
send_request(td_api::make_object<td_api::getApplicationDownloadLink>());
} else if (op == "atos") {
send_request(td_api::make_object<td_api::acceptTermsOfService>(args));
} else if (op == "gdli") {
@ -2377,6 +2414,36 @@ class CliClient final : public Actor {
string category;
get_args(args, chat_id, category);
send_request(td_api::make_object<td_api::removeTopChat>(get_top_chat_category(category), as_chat_id(chat_id)));
} else if (op == "gsssn") {
string title = args;
send_request(td_api::make_object<td_api::getSuggestedStickerSetName>(title));
} else if (op == "cssn") {
string name = args;
send_request(td_api::make_object<td_api::checkStickerSetName>(name));
} else if (op == "usf" || op == "usfa") {
td_api::object_ptr<td_api::InputSticker> input_sticker;
if (op == "usfa") {
input_sticker = td_api::make_object<td_api::inputStickerAnimated>(as_input_file(args), "😀");
} else {
input_sticker = td_api::make_object<td_api::inputStickerStatic>(as_input_file(args), "😀", nullptr);
}
send_request(td_api::make_object<td_api::uploadStickerFile>(-1, std::move(input_sticker)));
} else if (op == "cnss" || op == "cnssa") {
string title;
string name;
string stickers;
get_args(args, title, name, stickers);
auto input_stickers =
transform(full_split(stickers, get_delimiter(stickers)),
[op](string sticker) -> td_api::object_ptr<td_api::InputSticker> {
if (op == "cnssa") {
return td_api::make_object<td_api::inputStickerAnimated>(as_input_file(sticker), "😀");
} else {
return td_api::make_object<td_api::inputStickerStatic>(as_input_file(sticker), "😀", nullptr);
}
});
send_request(td_api::make_object<td_api::createNewStickerSet>(my_id_, title, name, false,
std::move(input_stickers), "tg_cli"));
} else if (op == "sss") {
send_request(td_api::make_object<td_api::searchStickerSet>(args));
} else if (op == "siss") {
@ -2694,18 +2761,49 @@ class CliClient final : public Actor {
} else if (op == "tgcesn" || op == "tgcesne") {
send_request(td_api::make_object<td_api::toggleGroupCallEnabledStartNotification>(as_group_call_id(args),
op == "tgcesne"));
} else if (op == "jgc") {
} else if (op == "jgc" || op == "jgcv" || op == "sgcss") {
string group_call_id;
string participant_id;
string invite_hash;
get_args(args, group_call_id, participant_id, invite_hash);
vector<td_api::object_ptr<td_api::groupCallPayloadFingerprint>> fingerprints;
fingerprints.push_back(td_api::make_object<td_api::groupCallPayloadFingerprint>("hash", "setup", "fingerprint"));
fingerprints.push_back(td_api::make_object<td_api::groupCallPayloadFingerprint>("h2", "s2", "fingerprint2"));
send_request(td_api::make_object<td_api::joinGroupCall>(
as_group_call_id(group_call_id), as_message_sender(participant_id),
td_api::make_object<td_api::groupCallPayload>("ufrag", "pwd", std::move(fingerprints)), group_call_source_,
true, invite_hash));
auto payload = PSTRING() << "{\"ufrag\":\"ufrag\",\"pwd\":\"pwd\",\"fingerprints\":[{\"hash\":\"hash\",\"setup\":"
"\"setup\",\"fingerprint\":\"fingerprint\"},{\"hash\":\"h2\",\"setup\":\"s2\","
"\"fingerprint\":\"fingerprint2\"}],\"ssrc\":"
<< group_call_source_ << ',';
if (op == "jgc") {
payload.back() = '}';
} else {
string sim_sources = "[1,2]";
string fid_sources = "[3,4]";
if (op == "sgcss") {
sim_sources = "[5,6]";
fid_sources = "[7,8]";
}
payload +=
"\"payload-types\":[{\"id\":12345,\"name\":\"opus\",\"clockrate\":48000,\"channels\":2,\"rtcp-fbs\":[{"
"\"type\":\"transport-cc\",\"subtype\":\"subtype1\"},{\"type\":\"type2\",\"subtype\":\"subtype2\"}],"
"\"parameters\":{\"minptime\":\"10\",\"useinbandfec\":\"1\"}}],\"rtp-hdrexts\":[{\"id\":1,\"uri\":\"urn:"
"ietf:params:rtp-hdrext:ssrc-audio-level\"}],\"ssrc-groups\":[{\"sources\":" +
sim_sources + ",\"semantics\":\"SIM\"},{\"sources\":" + fid_sources + ",\"semantics\":\"FID\"}]}";
}
if (op == "sgcss") {
send_request(td_api::make_object<td_api::startGroupCallScreenSharing>(as_group_call_id(group_call_id),
std::move(payload)));
} else {
send_request(td_api::make_object<td_api::joinGroupCall>(as_group_call_id(group_call_id),
as_message_sender(participant_id), group_call_source_,
std::move(payload), true, true, invite_hash));
}
} else if (op == "tgcssip") {
string group_call_id;
bool is_paused;
get_args(args, group_call_id, is_paused);
send_request(td_api::make_object<td_api::toggleGroupCallScreenSharingIsPaused>(as_group_call_id(group_call_id),
is_paused));
} else if (op == "egcss") {
string group_call_id = args;
send_request(td_api::make_object<td_api::endGroupCallScreenSharing>(as_group_call_id(group_call_id)));
} else if (op == "sgct") {
string chat_id;
string title;
@ -2716,13 +2814,25 @@ class CliClient final : public Actor {
td_api::make_object<td_api::toggleGroupCallMuteNewParticipants>(as_group_call_id(args), op == "tgcmnpe"));
} else if (op == "rgcil") {
send_request(td_api::make_object<td_api::revokeGroupCallInviteLink>(as_group_call_id(args)));
} else if (op == "tgcimvp") {
string group_call_id;
bool is_my_video_paused;
get_args(args, group_call_id, is_my_video_paused);
send_request(td_api::make_object<td_api::toggleGroupCallIsMyVideoPaused>(as_group_call_id(group_call_id),
is_my_video_paused));
} else if (op == "tgcimve") {
string group_call_id;
bool is_my_video_enabled;
get_args(args, group_call_id, is_my_video_enabled);
send_request(td_api::make_object<td_api::toggleGroupCallIsMyVideoEnabled>(as_group_call_id(group_call_id),
is_my_video_enabled));
} else if (op == "sgcpis") {
string group_call_id;
int32 source;
int32 source_id;
bool is_speaking;
get_args(args, group_call_id, source, is_speaking);
get_args(args, group_call_id, source_id, is_speaking);
send_request(td_api::make_object<td_api::setGroupCallParticipantIsSpeaking>(as_group_call_id(group_call_id),
source, is_speaking));
source_id, is_speaking));
} else if (op == "igcp") {
string group_call_id;
string user_ids;
@ -3083,9 +3193,8 @@ class CliClient final : public Actor {
vector<string> attached_files;
get_args(args, chat_id, message_file, args);
attached_files = full_split(args);
send_request(td_api::make_object<td_api::importMessages>(
as_chat_id(chat_id), as_input_file(message_file),
transform(attached_files, [](const string &attached_file) { return as_input_file(attached_file); })));
send_request(td_api::make_object<td_api::importMessages>(as_chat_id(chat_id), as_input_file(message_file),
transform(attached_files, as_input_file)));
} else if (op == "em") {
string chat_id;
string message_id;
@ -3943,6 +4052,9 @@ class CliClient final : public Actor {
string message_id;
get_args(args, chat_id, message_id);
send_request(td_api::make_object<td_api::openMessageContent>(as_chat_id(chat_id), as_message_id(message_id)));
} else if (op == "gilt") {
string link = args;
send_request(td_api::make_object<td_api::getInternalLinkType>(link));
} else if (op == "geli") {
string link = args;
send_request(td_api::make_object<td_api::getExternalLinkInfo>(link));
@ -4206,7 +4318,7 @@ class CliClient final : public Actor {
}
} else if (op == "q" || op == "Quit") {
quit();
} else if (op == "dnq" || op == "DumpNetQueries") {
} else if (op == "dnq") {
dump_pending_network_queries(*net_query_stats_);
} else if (op == "fatal") {
LOG(FATAL) << "Fatal!";
@ -4457,7 +4569,7 @@ void main(int argc, char **argv) {
combined_log.set_first_verbosity_level(new_verbosity_level);
if (combined_log.get_first() == &cli_log) {
file_log.init("tg_cli.log", 1000 << 20).ensure();
file_log.init("tg_cli.log", 1000 << 20, false).ensure();
file_log.lazy_rotate();
combined_log.set_second(&ts_log);
combined_log.set_second_verbosity_level(VERBOSITY_NAME(DEBUG));

View File

@ -26,11 +26,9 @@
#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/Status.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/tl_parsers.h"
#include "td/utils/tl_storers.h"
namespace td {
@ -40,7 +38,6 @@ Status drop_file_db(SqliteDb &db, int32 version) {
return Status::OK();
}
Status fix_file_remote_location_key_bug(SqliteDb &db);
Status init_file_db(SqliteDb &db, int32 version) {
LOG(INFO) << "Init file database " << tag("version", version);
@ -49,11 +46,9 @@ Status init_file_db(SqliteDb &db, int32 version) {
if (!has_table) {
version = 0;
} else if (version < static_cast<int32>(DbVersion::DialogDbCreated)) {
} else if (version < static_cast<int32>(DbVersion::FixFileRemoteLocationKeyBug)) {
TRY_STATUS(drop_file_db(db, version));
version = 0;
} else if (version < static_cast<int>(DbVersion::FixFileRemoteLocationKeyBug)) {
TRY_STATUS(fix_file_remote_location_key_bug(db));
}
if (version == 0) {
@ -306,30 +301,4 @@ std::shared_ptr<FileDbInterface> create_file_db(std::shared_ptr<SqliteConnection
return std::make_shared<FileDb>(std::move(kv), scheduler_id);
}
Status fix_file_remote_location_key_bug(SqliteDb &db) {
static const int32 OLD_KEY_MAGIC = 0x64378433;
SqliteKeyValue kv;
kv.init_with_connection(db.clone(), "files").ensure();
auto ptr = StackAllocator::alloc(4);
MutableSlice prefix = ptr.as_slice();
TlStorerUnsafe(prefix.ubegin()).store_int(OLD_KEY_MAGIC);
kv.get_by_prefix(prefix, [&](Slice key, Slice value) {
CHECK(TlParser(key).fetch_int() == OLD_KEY_MAGIC);
auto remote_str = PSTRING() << key.substr(4, 4) << Slice("\0\0\0\0") << key.substr(8);
FullRemoteFileLocation remote;
log_event::WithVersion<TlParser> parser(remote_str);
parser.set_version(static_cast<int32>(Version::Initial));
parse(remote, parser);
parser.fetch_end();
auto status = parser.get_status();
if (status.is_ok()) {
kv.set(FileDbInterface::as_key(remote), value);
}
LOG(DEBUG) << "ERASE " << format::as_hex_dump<4>(Slice(key));
kv.erase(key);
return true;
});
return Status::OK();
}
} // namespace td

View File

@ -238,7 +238,7 @@ class MapDownloadGenerateActor : public FileGenerateActor {
}
void hangup_shared() override {
on_error(Status::Error(1, "Cancelled"));
on_error(Status::Error(1, "Canceled"));
}
};
@ -308,7 +308,7 @@ class FileExternalGenerateActor : public FileGenerateActor {
static_cast<int64>(query_id_), generate_location_.original_path_, path_, generate_location_.conversion_));
}
void hangup() override {
check_status(Status::Error(1, "Cancelled"));
check_status(Status::Error(1, "Canceled"));
}
Status do_file_generate_write_part(int32 offset, const string &data) {

Some files were not shown because too many files have changed in this diff Show More