This commit is contained in:
Andrea Cavalli 2020-12-25 19:55:29 +01:00
commit 8e99c3c873
33 changed files with 3822 additions and 529 deletions

View File

@ -324,6 +324,7 @@ set(TDLIB_SOURCE
td/telegram/Game.cpp
td/telegram/Global.cpp
td/telegram/GroupCallManager.cpp
td/telegram/GroupCallParticipant.cpp
td/telegram/HashtagHints.cpp
td/telegram/InlineQueriesManager.cpp
td/telegram/InputDialogId.cpp
@ -499,6 +500,7 @@ set(TDLIB_SOURCE
td/telegram/Game.h
td/telegram/Global.h
td/telegram/GroupCallManager.h
td/telegram/GroupCallParticipant.h
td/telegram/HashtagHints.h
td/telegram/InlineQueriesManager.h
td/telegram/InputDialogId.h

View File

@ -34,6 +34,6 @@ cmake --build . --config Debug
After `TDLib` is built you can open and run TdExample project.
It contains a simple console C# application with implementation of authorization and message sending.
Just open it with Visual Studio 2015 or 2017 and run.
Just open it with Visual Studio 2015 or later and run.
Also see TdExample.csproj for example of including TDLib in C# project with all native shared library dependencies.

View File

@ -26,4 +26,4 @@ If you need to restart the build from scratch, call `.\build.ps1 -mode clean` fi
Now `TDLib` can be freely used from any UWP project, built in Visual Studio.
## Example of usage
The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or 2017 and run.
The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or later and run.

View File

@ -233,6 +233,10 @@ maskPointChin = MaskPoint;
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
//@description Represents a closed vector path. The path begins at the end point of the last command @commands List of vector path commands
closedVectorPath commands:vector<VectorPathCommand> = ClosedVectorPath;
//@description Describes one answer option of a poll @text Option text, 1-100 characters @voter_count Number of voters for this option, available only for closed or voted polls @vote_percentage The percentage of votes for this option, 0-100
//@is_chosen True, if the option was chosen by the user @is_being_chosen True, if the option is being chosen by a pending setPollAnswer request
pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption;
@ -270,8 +274,8 @@ photo has_stickers:Bool minithumbnail:minithumbnail sizes:vector<photoSize> = Ph
//@description Describes a sticker @set_id The identifier of the sticker set to which the sticker belongs; 0 if none @width Sticker width; as defined by the sender @height Sticker height; as defined by the sender
//@emoji Emoji corresponding to the sticker @is_animated True, if the sticker is an animated sticker in TGS format @is_mask True, if the sticker is a mask @mask_position Position where the mask should be placed; may be null
//@cover Sticker cover in an SVG format; may be empty @thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
sticker set_id:int64 width:int32 height:int32 emoji:string is_animated:Bool is_mask:Bool mask_position:maskPosition cover:string thumbnail:thumbnail sticker:file = Sticker;
//@outline Sticker's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner @thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
sticker set_id:int64 width:int32 height:int32 emoji:string is_animated:Bool is_mask:Bool mask_position:maskPosition outline:vector<closedVectorPath> thumbnail:thumbnail sticker:file = Sticker;
//@description Describes a video file @duration Duration of the video, in seconds; as defined by the sender @width Video width; as defined by the sender @height Video height; as defined by the sender
//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender
@ -447,7 +451,7 @@ chatPermissions can_send_messages:Bool can_send_media_messages:Bool can_send_pol
//@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, and ban unprivileged members. In supergroups and channels, there are more detailed options for administrator privileges
//@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
//@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_change_info True, if the administrator can change the chat title, photo, and other settings
@ -458,9 +462,9 @@ chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = C
//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members
//@can_pin_messages True, if the administrator can pin messages; applicable to groups only
//@can_promote_members True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them
//@can_manage_calls True, if the administrator can manage group calls; applicable to supergroups only
//@can_manage_voice_chats True, if the administrator can manage voice chats; applicable to groups only
//@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_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_calls:Bool is_anonymous:Bool = ChatMemberStatus;
chatMemberStatusAdministrator custom_title:string can_be_edited: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
chatMemberStatusMember = ChatMemberStatus;
@ -566,14 +570,13 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int32 memb
//@member_count Number of members in the supergroup or channel; 0 if unknown. Currently it is guaranteed to be known only if the supergroup or channel was received through searchPublicChats, searchChatsNearby, getInactiveSupergroupChats, getSuitableDiscussionChats, getGroupsInCommon, or getUserPrivacySettingRules
//@has_linked_chat True, if the channel has a discussion group, or the supergroup is the designated discussion group for a channel
//@has_location True, if the supergroup is connected to a location, i.e. the supergroup is a location-based supergroup
//@has_active_call True, if the supergroup has active group call
//@sign_messages True, if messages sent to the channel should contain information about the sender. This field is only applicable to channels
//@is_slow_mode_enabled True, if the slow mode is enabled in the supergroup
//@is_channel True, if the supergroup is a channel
//@is_verified True, if the supergroup or channel is verified
//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this supergroup or channel must be restricted
//@is_scam True, if many users reported this supergroup as a scam
supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool has_active_call:Bool sign_messages:Bool is_slow_mode_enabled:Bool is_channel:Bool is_verified:Bool restriction_reason:string is_scam:Bool = Supergroup;
supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool is_slow_mode_enabled:Bool is_channel:Bool is_verified:Bool restriction_reason:string is_scam:Bool = Supergroup;
//@description Contains full information about a supergroup or channel
//@photo Chat photo; may be null
@ -583,7 +586,6 @@ supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_co
//@restricted_count Number of restricted users in the supergroup; 0 if unknown
//@banned_count Number of users banned from chat; 0 if unknown
//@linked_chat_id Chat identifier of a discussion group for the channel, or a channel, for which the supergroup is the designated discussion group; 0 if none or unknown
//@group_call_id Identifier of an active group call; empty if none or unknown
//@slow_mode_delay Delay between consecutive sent messages for non-administrator supergroup members, in seconds
//@slow_mode_delay_expires_in Time left before next message can be sent in the supergroup, in seconds. An updateSupergroupFullInfo update is not triggered when value of this field changes, but both new and old values are non-zero
//@can_get_members True, if members of the chat can be retrieved
@ -597,7 +599,7 @@ supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_co
//@invite_link Invite link for this chat
//@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none
//@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none
supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 group_call_id:string slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:string upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo;
supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:string upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo;
//@class SecretChatState @description Describes the current secret chat state
@ -864,10 +866,12 @@ chatPosition list:ChatList order:int64 is_pinned:Bool source:ChatSource = ChatPo
//@unread_mention_count Number of unread messages with a mention/reply in the chat
//@notification_settings Notification settings for this chat
//@action_bar Describes actions which should be possible to do through a chat action bar; may be null
//@voice_chat_group_call_id Group call identifier of an active voice chat; 0 if none or unknown. The voice chat can be received through the method getGroupCall
//@is_voice_chat_empty True, if an active voice chat is empty
//@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat
//@draft_message A draft of a message in the chat; may be null
//@client_data Contains application-specific data associated with the chat. (For example, the chat scroll position or local chat notification settings can be stored here.) Persistent if the message database is used
chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector<chatPosition> is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:chatNotificationSettings action_bar:ChatActionBar reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat;
chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector<chatPosition> is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:chatNotificationSettings action_bar:ChatActionBar voice_chat_group_call_id:int32 is_voice_chat_empty:Bool reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat;
//@description Represents a list of chats @total_count Approximate total count of chats found @chat_ids List of chat identifiers
chats total_count:int32 chat_ids:vector<int53> = Chats;
@ -1629,11 +1633,14 @@ messageInvoice title:string description:string photo:photo currency:string total
//@description A message with information about an ended call @is_video True, if the call was a video call @discard_reason Reason why the call was discarded @duration Call duration, in seconds
messageCall is_video:Bool discard_reason:CallDiscardReason duration:int32 = MessageContent;
//@description A message with information about a group call @group_call_id Group call identifier @duration Call duration; for finished calls only
messageGroupCall group_call_id:string duration:int32 = MessageContent;
//@description A newly created voice chat @group_call_id Identifier of the voice chat. The voice chat can be received through the method getGroupCall
messageVoiceChatStarted group_call_id:int32 = MessageContent;
//@description A message with information about an invite to a group call @group_call_id Group call identifier @user_id Invited user identifier
messageInviteToGroupCall group_call_id:string user_id:int32 = MessageContent;
//@description A message with information about an ended voice chat @duration Call duration
messageVoiceChatEnded duration:int32 = MessageContent;
//@description A message with information about an invite to a voice chat @group_call_id Identifier of the voice chat. The voice chat can be received through the method getGroupCall @user_ids Invited user identifiers
messageInviteVoiceChatParticipants group_call_id:int32 user_ids:vector<int32> = MessageContent;
//@description A newly created basic group @title Title of the basic group @member_user_ids User identifiers of members in the basic group
messageBasicGroupChatCreate title:string member_user_ids:vector<int32> = MessageContent;
@ -1940,8 +1947,6 @@ 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 is speaking in a call; supergroups only
chatActionSpeakingInCall = ChatAction;
//@description The user has cancelled the previous action
chatActionCancel = ChatAction;
@ -1975,17 +1980,19 @@ emojis emojis:vector<string> = Emojis;
//@description Represents a sticker set
//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP or TGS format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed
//@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
//@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously
//@is_official True, if the sticker set is official @is_animated True, is the stickers in the set are animated @is_masks True, if the stickers in the set are masks @is_viewed True for already viewed trending sticker sets
//@stickers List of stickers in this set @emojis A list of emoji corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object
stickerSet id:int64 title:string name:string thumbnail:thumbnail is_installed:Bool is_archived:Bool is_official:Bool is_animated:Bool is_masks:Bool is_viewed:Bool stickers:vector<sticker> emojis:vector<emojis> = StickerSet;
stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool is_animated:Bool is_masks:Bool is_viewed:Bool stickers:vector<sticker> emojis:vector<emojis> = StickerSet;
//@description Represents short information about a sticker set
//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP or TGS format with width and height 100; may be null
//@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
//@is_installed True, if the sticker set has been installed by current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously
//@is_official True, if the sticker set is official @is_animated True, is the stickers in the set are animated @is_masks True, if the stickers in the set are masks @is_viewed True for already viewed trending sticker sets
//@size Total number of stickers in the set @covers Contains up to the first 5 stickers from the set, depending on the context. If the application needs more stickers the full set should be requested
stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail is_installed:Bool is_archived:Bool is_official:Bool is_animated:Bool is_masks:Bool is_viewed:Bool size:int32 covers:vector<sticker> = StickerSetInfo;
stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool is_animated:Bool is_masks:Bool is_viewed:Bool size:int32 covers:vector<sticker> = StickerSetInfo;
//@description Represents a list of sticker sets @total_count Approximate total number of sticker sets found @sets List of sticker sets
stickerSets total_count:int32 sets:vector<stickerSetInfo> = StickerSets;
@ -2014,7 +2021,7 @@ callDiscardReasonHungUp = CallDiscardReason;
//@udp_reflector True, if connection through UDP reflectors is supported
//@min_layer The minimum supported API layer; use 65
//@max_layer The maximum supported API layer; use 65
//@library_versions List of supported libtgvoip versions
//@library_versions List of supported tgcalls versions
callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 library_versions:vector<string> = CallProtocol;
@ -2035,7 +2042,7 @@ callServer id:int64 ip_address:string ipv6_address:string port:int32 type:CallSe
callId id:int32 = CallId;
//@description Contains the group call identifier @id Group call identifier
groupCallId id:string = GroupCallId;
groupCallId id:int32 = GroupCallId;
//@class CallState @description Describes the current call state
@ -2059,8 +2066,36 @@ callStateDiscarded reason:CallDiscardReason need_rating:Bool need_debug_informat
callStateError error:error = CallState;
//@description Describes a group call @id Group call identifier @is_active True, if the call is active @member_count Number of members in the group call @duration Call duration; for ended calls only
groupCall id:string is_active:Bool member_count:int32 duration:int32 = GroupCall;
//@description Describes a group call
//@id Group call identifier
//@is_active True, if the call is active
//@is_joined True, if the call is joined
//@participant_count Number of participants in the group call
//@loaded_all_participants True, if all group call participants are loaded
//@recent_speaker_user_ids Identifiers of recently speaking users in the group call
//@mute_new_participants True, if only group call administrators can unmute new participants
//@allowed_change_mute_new_participants True, if group call administrators can enable or disable mute_new_participants setting
//@duration Call duration; for ended calls only
groupCall id:int32 is_active:Bool is_joined:Bool participant_count:int32 loaded_all_participants:Bool recent_speaker_user_ids:vector<int32> mute_new_participants:Bool allowed_change_mute_new_participants:Bool 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;
//@description Describes a join response for interaction with tgcalls @payload Join response payload to pass to tgcalls @candidates Join response candidates to pass to tgcalls
groupCallJoinResponse payload:groupCallPayload candidates:vector<groupCallJoinResponseCandidate> = GroupCallJoinResponse;
//@description Represents a group call participant @user_id Identifier of the user @source User's synchronization source
//@is_speaking True, if the user is speaking as set by setGroupCallParticipantIsSpeaking @is_muted True, if the user is muted @can_self_unmute True, if the user can unmute themself
//@order User's order in the group call participant list. The bigger is order, the higher is user in the list. If order is 0, the user must be removed from the participant list
groupCallParticipant user_id:int32 source:int32 is_speaking:Bool is_muted:Bool can_self_unmute:Bool order:int64 = GroupCallParticipant;
//@class CallProblem @description Describes the exact type of a problem with a call
@ -2346,6 +2381,18 @@ chatEventLocationChanged old_location:chatLocation new_location:chatLocation = C
//@description The is_all_history_available setting of a supergroup was toggled @is_all_history_available New value of is_all_history_available
chatEventIsAllHistoryAvailableToggled is_all_history_available:Bool = ChatEventAction;
//@description A voice chat was created @group_call_id Identifier of the voice chat. The voice chat can be received through the method getGroupCall
chatEventVoiceChatCreated group_call_id:int32 = ChatEventAction;
//@description A voice chat was discarded @group_call_id Identifier of the voice chat. The voice chat can be received through the method getGroupCall
chatEventVoiceChatDiscarded group_call_id:int32 = ChatEventAction;
//@description A voice chat participant was muted or unmuted @user_id Identifier of the affected user @is_muted New value of is_muted
chatEventVoiceChatParticipantIsMutedToggled user_id:int32 is_muted:Bool = ChatEventAction;
//@description The mute_new_participants setting of a voice chat was toggled @mute_new_participants New value of the mute_new_participants setting
chatEventVoiceChatMuteNewParticipantsToggled mute_new_participants:Bool = ChatEventAction;
//@description Represents a chat event @id Chat event identifier @date Point in time (Unix timestamp) when the event happened @user_id Identifier of the user who performed the action that triggered the event @action Action performed by the user
chatEvent id:int64 date:int32 user_id:int32 action:ChatEventAction = ChatEvent;
@ -2363,7 +2410,8 @@ chatEvents events:vector<chatEvent> = ChatEvents;
//@member_restrictions True, if member restricted/unrestricted/banned/unbanned events should be returned
//@info_changes True, if changes in chat information should be returned
//@setting_changes True, if changes in chat settings should be returned
chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool member_joins:Bool member_leaves:Bool member_invites:Bool member_promotions:Bool member_restrictions:Bool info_changes:Bool setting_changes:Bool = ChatEventLogFilters;
//@voice_chat_changes True, if voice chat actions should be returned
chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool member_joins:Bool member_leaves:Bool member_invites:Bool member_promotions:Bool member_restrictions:Bool info_changes:Bool setting_changes:Bool voice_chat_changes:Bool = ChatEventLogFilters;
//@class LanguagePackStringValue @description Represents the value of a string in a language pack
@ -3168,6 +3216,19 @@ chatStatisticsChannel period:dateRange member_count:statisticalValue mean_view_c
messageStatistics message_interaction_graph:StatisticalGraph = MessageStatistics;
//@description A point on a Cartesian plane @x The point's first coordinate @y The point's second coordinate
point x:double y:double = Point;
//@class VectorPathCommand @description Reperesents a vector path command
//@description A straight line to a given point @end_point The end point of the straight line
vectorPathCommandLine end_point:point = VectorPathCommand;
//@description A cubic Bézier curve to a given point @start_control_point The start control point of the curve @end_control_point The end control point of the curve @end_point The end point of the curve
vectorPathCommandCubicBezierCurve start_control_point:point end_control_point:point end_point:point = VectorPathCommand;
//@class Update @description Contains notifications about data changes
//@description The user authorization state has changed @authorization_state New authorization state
@ -3236,6 +3297,9 @@ updateChatIsBlocked chat_id:int53 is_blocked:Bool = Update;
//@description A chat's has_scheduled_messages field has changed @chat_id Chat identifier @has_scheduled_messages New value of has_scheduled_messages
updateChatHasScheduledMessages chat_id:int53 has_scheduled_messages:Bool = Update;
//@description A chat voice chat state has changed @chat_id Chat identifier @voice_chat_group_call_id New value of voice_chat_group_call_id @is_voice_chat_empty New value of is_voice_chat_empty
updateChatVoiceChat chat_id:int53 voice_chat_group_call_id:int32 is_voice_chat_empty:Bool = Update;
//@description The value of the default disable_notification parameter, used when a message is sent to the chat, was changed @chat_id Chat identifier @default_disable_notification The new default_disable_notification value
updateChatDefaultDisableNotification chat_id:int53 default_disable_notification:Bool = Update;
@ -3344,9 +3408,14 @@ updateFileGenerationStop generation_id:int64 = Update;
//@description New call was created or information about a call was updated @call New data about a call
updateCall call:call = Update;
//@description Information about a group call was updated @group_call New data about the call
//@description Information about a group call was updated @group_call New data about a group call
updateGroupCall group_call:groupCall = Update;
//@description Information about a group call participant was changed. The updates are sent only after the group call is received through getGroupCall and only if the call is joined or being joined
//@group_call_id Identifier of group call
//@participant New data about a participant. If this is a current user with different source, then the current user joined the call from another device and the call must not be rejoined automatically
updateGroupCallParticipant group_call_id:int32 participant:groupCallParticipant = Update;
//@description New call signaling data arrived @call_id The call identifier @data The data
updateNewCallSignalingData call_id:int32 data:bytes = Update;
@ -3410,8 +3479,8 @@ updateAnimationSearchParameters provider:string emojis:vector<string> = Update;
updateSuggestedActions added_actions:vector<SuggestedAction> removed_actions:vector<SuggestedAction> = Update;
//@description A new incoming inline query; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query @user_location User location; may be null
//@query Text of the query @offset Offset of the first entry to return
updateNewInlineQuery id:int64 sender_user_id:int32 user_location:location query:string offset:string = Update;
//@chat_type Contains information about the type of the chat, from which the query originated; may be null if unknown @query Text of the query @offset Offset of the first entry to return
updateNewInlineQuery id:int64 sender_user_id:int32 user_location:location chat_type:ChatType query:string offset:string = Update;
//@description The user has chosen a result of an inline query; for bots only @sender_user_id Identifier of the user who sent the query @user_location User location; may be null
//@query Text of the query @result_id Identifier of the chosen result @inline_message_id Identifier of the sent inline message, if known
@ -4167,8 +4236,8 @@ leaveChat chat_id:int53 = Ok;
//@chat_id Chat identifier @user_id Identifier of the user @forward_limit The number of earlier messages from the chat to be forwarded to the new member; up to 100. Ignored for supergroups and channels
addChatMember chat_id:int53 user_id:int32 forward_limit:int32 = Ok;
//@description Adds multiple new members to a chat. Currently this option is only available for supergroups and channels. This option can't be used to join a chat. Members can't be added to a channel if it has more than 200 members. Members will not be added until the chat state has been synchronized with the server
//@chat_id Chat identifier @user_ids Identifiers of the users to be added to the chat
//@description Adds multiple new members to a chat. Currently this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members. Members will not be added until the chat state has been synchronized with the server
//@chat_id Chat identifier @user_ids Identifiers of the users to be added to the chat. The maximum number of added users is 20 for supergroups and 100 for channels
addChatMembers chat_id:int53 user_ids:vector<int32> = Ok;
//@description Changes the status of a chat member, needs appropriate privileges. This function is currently not suitable for adding new members to the chat and transferring chat ownership; instead, use addChatMember or transferChatOwnership. The chat member status will not be changed until it has been synchronized with the server
@ -4294,14 +4363,42 @@ sendCallRating call_id:int32 rating:int32 comment:string problems:vector<CallPro
sendCallDebugInformation call_id:int32 debug_information:string = Ok;
//@description Creates a group call in a chat. Available only for supergroups; requires can_manage_calls rights @chat_id Chat identifier
createChatGroupCall chat_id:int53 = GroupCallId;
//@description Creates a voice chat (a group call bound to a chat). Available only for basic groups and supergroups; requires can_manage_voice_chats rights @chat_id Chat identifier
createVoiceChat chat_id:int53 = GroupCallId;
//@description Leaves a group call @group_call_id Group call identifier @source Caller source identifier
leaveGroupCall group_call_id:string source:int32 = Ok;
//@description Returns information about a group call @group_call_id Group call identifier
getGroupCall group_call_id:int32 = GroupCall;
//@description Discards a group call. Requires can_manage_calls rights in the corresponding chat @group_call_id Group call identifier
discardGroupCall group_call_id:string = Ok;
//@description Joins a group call @group_call_id Group call identifier @payload Group join payload, received from tgcalls @source Caller synchronization source identifier; received from tgcalls @is_muted True, if the user's microphone is muted
joinGroupCall group_call_id:int32 payload:groupCallPayload source:int32 is_muted:Bool = GroupCallJoinResponse;
//@description Toggles whether new participants of a group call can be unmuted only by administrators of the group call. Requires can_manage_voice_chats rights in the corresponding chat and allowed_change_mute_mew_participants group call flag
//@group_call_id Group call identifier @mute_new_participants New value of the mute_new_participants setting
toggleGroupCallMuteNewParticipants group_call_id:int32 mute_new_participants:Bool = Ok;
//@description Invites users to a group call. Sends a service message of type messageInviteToGroupCall for voice chats
//@group_call_id Group call identifier @user_ids User identifiers. At most 10 users can be invited simultaneously
inviteGroupCallParticipants group_call_id:int32 user_ids:vector<int32> = Ok;
//@description Informs TDLib that a group call participant speaking state has changed @group_call_id Group call identifier
//@source Group call participant's synchronization source identifier @is_speaking True, if the user is speaking
setGroupCallParticipantIsSpeaking group_call_id:int32 source:int32 is_speaking:Bool = Ok;
//@description Toggles whether a group call participant is muted. Requires can_manage_voice_chats rights to mute other group call participants
//@group_call_id Group call identifier @user_id User identifier @is_muted Pass true if the user must be muted and false otherwise
toggleGroupCallParticipantIsMuted group_call_id:int32 user_id:int32 is_muted:Bool = Ok;
//@description Checks whether a group call is still joined. Should be called every 10 seconds when tgcalls notifies about lost connection with the server @group_call_id Group call identifier
checkGroupCallIsJoined group_call_id:int32 = Ok;
//@description Loads more group call participants. The loaded participants will be received through updates @group_call_id Group call identifier @limit Maximum number of participants to load
loadGroupCallParticipants group_call_id:int32 limit:int32 = Ok;
//@description Leaves a group call @group_call_id Group call identifier
leaveGroupCall group_call_id:int32 = Ok;
//@description Discards a group call. Requires can_manage_voice_chats rights in the corresponding chat @group_call_id Group call identifier
discardGroupCall group_call_id:int32 = Ok;
//@description Changes the block state of a message sender. Currently, only users and supergroup chats can be blocked @sender Message Sender @is_blocked New value of is_blocked

View File

@ -109,12 +109,12 @@ userStatusLastWeek#7bf09fc = UserStatus;
userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#9ba2d800 id:int = Chat;
chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#7328bdb id:int title:string = Chat;
channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull;
chatFull#dc8c181 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull;
channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
@ -170,7 +170,7 @@ messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAc
messageActionContactSignUp#f3f25f76 = MessageAction;
messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction;
messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction;
messageActionInviteToGroupCall#f25f03a call:InputGroupCall user_id:int = MessageAction;
messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector<int> = MessageAction;
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@ -302,7 +302,7 @@ updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update;
updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector<long> = Update;
updateStickerSets#43ae3dec = Update;
updateSavedGifs#9375341e = Update;
updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update;
updateBotInlineQuery#3f2038db flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update;
updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update;
updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
@ -352,8 +352,9 @@ updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update;
updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update;
updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector<int> pts:int pts_count:int = Update;
updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector<int> pts:int pts_count:int = Update;
updateChat#1330a196 chat_id:int = Update;
updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector<GroupCallParticipant> version:int = Update;
updateGroupCall#85fe86ed call:GroupCall = Update;
updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -534,7 +535,7 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
stickerSet#40e237a8 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
@ -866,12 +867,17 @@ channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEve
channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction;
channelAdminLogEventActionToggleSlowMode#53909779 prev_value:int new_value:int = ChannelAdminLogEventAction;
channelAdminLogEventActionStartGroupCall#23209745 call:InputGroupCall = ChannelAdminLogEventAction;
channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = ChannelAdminLogEventAction;
channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction;
channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction;
channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction;
channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
channels.adminLogResults#ed8af74d events:Vector<ChannelAdminLogEvent> chats:Vector<Chat> users:Vector<User> = channels.AdminLogResults;
channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true = ChannelAdminLogEventsFilter;
channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true = ChannelAdminLogEventsFilter;
popularContact#5ce14175 client_id:long importers:int = PopularContact;
@ -1176,12 +1182,18 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2
inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant;
phone.groupCall#985c2087 call:GroupCall sources:Vector<int> participants:Vector<GroupCallParticipant> participants_next_offset:string users:Vector<User> = phone.GroupCall;
phone.groupCall#66ab0bfc call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string users:Vector<User> = phone.GroupCall;
phone.groupParticipants#9cfeb92d count:int participants:Vector<GroupCallParticipant> next_offset:string users:Vector<User> version:int = phone.GroupParticipants;
inlineQueryPeerTypeSameBotPM#3081ed9d = InlineQueryPeerType;
inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType;
inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType;
inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType;
inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1536,15 +1548,15 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati
phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
phone.createGroupCall#e428fa02 channel:InputChannel random_id:int = Updates;
phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates;
phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates;
phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates;
phone.inviteToGroupCall#dd698f84 call:InputGroupCall user_id:InputUser = Updates;
phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
phone.discardGroupCall#7a777135 call:InputGroupCall = Updates;
phone.toggleGroupCallSettings#74bbb43d flags:# call:InputGroupCall join_muted:flags.0?Bool = Updates;
phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall;
phone.getGroupParticipants#ae1910a4 call:InputGroupCall offset:string limit:int = phone.GroupParticipants;
phone.getGroupParticipants#c9f1d285 call:InputGroupCall ids:Vector<int> sources:Vector<int> offset:string limit:int = phone.GroupParticipants;
phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool;
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;

View File

@ -21,6 +21,7 @@
#include "td/telegram/Global.h"
#include "td/telegram/GroupCallManager.h"
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/InputGroupCallId.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/logevent/LogEventHelper.h"
#include "td/telegram/MessagesManager.h"
@ -3474,6 +3475,10 @@ void ContactsManager::Chat::parse(ParserT &parser) {
if (has_cache_version) {
parse(cache_version, parser);
}
if (status.is_administrator() && !status.is_creator()) {
status = DialogParticipantStatus::GroupAdministrator(false);
}
}
template <class StorerT>
@ -3538,6 +3543,7 @@ void ContactsManager::Channel::store(StorerT &storer) const {
bool have_default_permissions = true;
bool has_cache_version = cache_version != 0;
bool has_restriction_reasons = !restriction_reasons.empty();
bool legacy_has_active_group_call = false;
BEGIN_STORE_FLAGS();
STORE_FLAG(false);
STORE_FLAG(false);
@ -3560,7 +3566,7 @@ void ContactsManager::Channel::store(StorerT &storer) const {
STORE_FLAG(has_location);
STORE_FLAG(is_slow_mode_enabled);
STORE_FLAG(has_restriction_reasons);
STORE_FLAG(has_active_group_call);
STORE_FLAG(legacy_has_active_group_call);
END_STORE_FLAGS();
store(status, storer);
@ -3604,6 +3610,7 @@ void ContactsManager::Channel::parse(ParserT &parser) {
bool have_default_permissions;
bool has_cache_version;
bool has_restriction_reasons;
bool legacy_has_active_group_call;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(left);
PARSE_FLAG(kicked);
@ -3626,7 +3633,7 @@ void ContactsManager::Channel::parse(ParserT &parser) {
PARSE_FLAG(has_location);
PARSE_FLAG(is_slow_mode_enabled);
PARSE_FLAG(has_restriction_reasons);
PARSE_FLAG(has_active_group_call);
PARSE_FLAG(legacy_has_active_group_call);
END_PARSE_FLAGS();
if (use_new_rights) {
@ -3674,6 +3681,9 @@ void ContactsManager::Channel::parse(ParserT &parser) {
if (has_cache_version) {
parse(cache_version, parser);
}
if (legacy_has_active_group_call) {
cache_version = 0;
}
}
template <class StorerT>
@ -3694,7 +3704,7 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const {
bool is_slow_mode_delay_active = slow_mode_next_send_date != 0;
bool has_stats_dc_id = stats_dc_id.is_exact();
bool has_photo = !photo.is_empty();
bool has_active_group_call_id = active_group_call_id.is_valid();
bool legacy_has_active_group_call_id = false;
BEGIN_STORE_FLAGS();
STORE_FLAG(has_description);
STORE_FLAG(has_administrator_count);
@ -3719,7 +3729,7 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const {
STORE_FLAG(has_photo);
STORE_FLAG(is_can_view_statistics_inited);
STORE_FLAG(can_view_statistics);
STORE_FLAG(has_active_group_call_id);
STORE_FLAG(legacy_has_active_group_call_id);
END_STORE_FLAGS();
if (has_description) {
store(description, storer);
@ -3768,9 +3778,6 @@ void ContactsManager::ChannelFull::store(StorerT &storer) const {
if (has_photo) {
store(photo, storer);
}
if (has_active_group_call_id) {
store(active_group_call_id, storer);
}
}
template <class ParserT>
@ -3792,7 +3799,7 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) {
bool is_slow_mode_delay_active;
bool has_stats_dc_id;
bool has_photo;
bool has_active_group_call_id;
bool legacy_has_active_group_call_id;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_description);
PARSE_FLAG(has_administrator_count);
@ -3817,7 +3824,7 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) {
PARSE_FLAG(has_photo);
PARSE_FLAG(is_can_view_statistics_inited);
PARSE_FLAG(can_view_statistics);
PARSE_FLAG(has_active_group_call_id);
PARSE_FLAG(legacy_has_active_group_call_id);
END_PARSE_FLAGS();
if (has_description) {
parse(description, parser);
@ -3866,8 +3873,9 @@ void ContactsManager::ChannelFull::parse(ParserT &parser) {
if (has_photo) {
parse(photo, parser);
}
if (has_active_group_call_id) {
parse(active_group_call_id, parser);
if (legacy_has_active_group_call_id) {
InputGroupCallId input_group_call_id;
parse(input_group_call_id, parser);
}
if (legacy_can_view_statistics) {
@ -5914,70 +5922,6 @@ void ContactsManager::set_channel_slow_mode_delay(DialogId dialog_id, int32 slow
td_->create_handler<ToggleSlowModeQuery>(std::move(promise))->send(channel_id, slow_mode_delay);
}
void ContactsManager::create_channel_group_call(DialogId dialog_id, Promise<InputGroupCallId> &&promise) {
if (!dialog_id.is_valid()) {
return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
}
if (!td_->messages_manager_->have_dialog_force(dialog_id)) {
return promise.set_error(Status::Error(400, "Chat not found"));
}
if (dialog_id.get_type() != DialogType::Channel) {
return promise.set_error(Status::Error(400, "Chat is not a supergroup"));
}
auto channel_id = dialog_id.get_channel_id();
const Channel *c = get_channel(channel_id);
if (c == nullptr) {
return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (!c->is_megagroup) {
return promise.set_error(Status::Error(400, "Chat is not a supergroup"));
}
if (!get_channel_permissions(c).can_manage_calls()) {
return promise.set_error(Status::Error(400, "Not enough rights in the supergroup"));
}
auto new_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), channel_id, promise = std::move(promise)](Result<InputGroupCallId> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
send_closure(actor_id, &ContactsManager::on_create_channel_group_call, channel_id, result.move_as_ok(),
std::move(promise));
}
});
send_closure(G()->group_call_manager(), &GroupCallManager::create_group_call, channel_id, std::move(new_promise));
}
void ContactsManager::on_create_channel_group_call(ChannelId channel_id, InputGroupCallId group_call_id,
Promise<InputGroupCallId> &&promise) {
if (G()->close_flag()) {
return promise.set_error(Status::Error(500, "Request aborted"));
}
if (!group_call_id.is_valid()) {
return promise.set_error(Status::Error(500, "Receive invalid group call identifier"));
}
Channel *c = get_channel(channel_id);
if (c == nullptr) {
return promise.set_error(Status::Error(500, "Channel not found"));
}
if (!c->has_active_group_call) {
c->has_active_group_call = true;
c->is_changed = true;
update_channel(c, channel_id);
}
auto channel_full = get_channel_full_force(channel_id, "on_create_channel_group_call");
if (channel_full != nullptr && channel_full->active_group_call_id != group_call_id) {
channel_full->active_group_call_id = group_call_id;
channel_full->is_changed = true;
update_channel_full(channel_full, channel_id);
}
promise.set_value(std::move(group_call_id));
}
void ContactsManager::get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics,
Promise<DcId> &&promise) {
if (!dialog_id.is_valid()) {
@ -8775,11 +8719,6 @@ void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, s
auto photo = std::move(channel_full->photo);
on_update_channel_full_photo(channel_full, channel_id, std::move(photo));
if (!c->has_active_group_call && channel_full->active_group_call_id.is_valid()) {
channel_full->active_group_call_id = InputGroupCallId();
channel_full->expires_at = 0.0;
}
update_channel_full(channel_full, channel_id, true);
if (channel_full->expires_at == 0.0) {
@ -9220,7 +9159,7 @@ void ContactsManager::update_channel_full(ChannelFull *channel_full, ChannelId c
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateSupergroupFullInfo>(get_supergroup_id_object(channel_id, "update_channel_full"),
get_supergroup_full_info_object(channel_full)));
get_supergroup_full_info_object(channel_full, channel_id)));
channel_full->need_send_update = false;
}
if (channel_full->need_save_to_database) {
@ -9519,6 +9458,13 @@ void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&c
}
td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(
DialogId(chat_id), (chat_full->flags_ & CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0);
{
InputGroupCallId input_group_call_id;
if (chat_full->call_ != nullptr) {
input_group_call_id = InputGroupCallId(chat_full->call_);
}
td_->messages_manager_->on_update_dialog_group_call_id(DialogId(chat_id), input_group_call_id);
}
ChatFull *chat = add_chat_full(chat_id);
on_update_chat_full_invite_link(chat, std::move(chat_full->exported_invite_));
@ -9615,14 +9561,6 @@ void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&c
LOG(ERROR) << "Receive can_view_statistics == true, but invalid statistics DC ID in " << channel_id;
can_view_statistics = false;
}
InputGroupCallId group_call_id;
if (channel_full->call_ != nullptr) {
group_call_id = InputGroupCallId(channel_full->call_);
if (group_call_id.is_valid() && !c->is_megagroup) {
LOG(ERROR) << "Receive " << group_call_id << " in " << channel_id;
group_call_id = InputGroupCallId();
}
}
channel->repair_request_version = 0;
channel->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME;
@ -9659,19 +9597,6 @@ void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&c
channel->need_save_to_database = true;
}
if (channel->active_group_call_id != group_call_id) {
channel->active_group_call_id = group_call_id;
bool has_active_group_call = group_call_id.is_valid();
if (c->has_active_group_call != has_active_group_call) {
LOG(ERROR) << "Receive invalid has_active_group_call flag " << c->has_active_group_call << ", but have "
<< group_call_id << " in " << channel_id;
c->has_active_group_call = has_active_group_call;
c->is_changed = true;
update_channel(c, channel_id);
}
channel->is_changed = true;
}
on_update_channel_full_photo(
channel, channel_id,
get_photo(td_->file_manager_.get(), std::move(channel_full->chat_photo_), DialogId(channel_id)));
@ -9708,6 +9633,17 @@ void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&c
}
td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(
DialogId(channel_id), (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0);
{
InputGroupCallId input_group_call_id;
if (channel_full->call_ != nullptr) {
input_group_call_id = InputGroupCallId(channel_full->call_);
if (input_group_call_id.is_valid() && !c->is_megagroup) {
LOG(ERROR) << "Receive " << input_group_call_id << " in " << channel_id;
input_group_call_id = InputGroupCallId();
}
}
td_->messages_manager_->on_update_dialog_group_call_id(DialogId(channel_id), input_group_call_id);
}
if (participant_count >= 190) {
int32 online_member_count = 0;
@ -11047,8 +10983,7 @@ void ContactsManager::drop_channel_photos(ChannelId channel_id, bool is_empty, b
}
}
void ContactsManager::invalidate_channel_full(ChannelId channel_id, bool drop_invite_link, bool drop_slow_mode_delay,
bool drop_active_group_call_id) {
void ContactsManager::invalidate_channel_full(ChannelId channel_id, bool drop_invite_link, bool drop_slow_mode_delay) {
LOG(INFO) << "Invalidate supergroup full for " << channel_id;
// drop channel full cache
auto channel_full = get_channel_full_force(channel_id, "invalidate_channel_full");
@ -11063,10 +10998,6 @@ void ContactsManager::invalidate_channel_full(ChannelId channel_id, bool drop_in
channel_full->is_slow_mode_next_send_date_changed = true;
channel_full->is_changed = true;
}
if (drop_active_group_call_id && channel_full->active_group_call_id.is_valid()) {
channel_full->active_group_call_id = InputGroupCallId();
channel_full->is_changed = true;
}
update_channel_full(channel_full, channel_id);
}
if (drop_invite_link) {
@ -13898,6 +13829,10 @@ void ContactsManager::on_chat_update(telegram_api::chat &chat, const char *sourc
}
c->is_received_from_server = true;
update_chat(c, chat_id);
bool has_active_group_call = (chat.flags_ & CHAT_FLAG_HAS_ACTIVE_GROUP_CALL) != 0;
bool is_group_call_empty = (chat.flags_ & CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0;
td_->messages_manager_->on_update_dialog_group_call(DialogId(chat_id), has_active_group_call, is_group_call_empty);
}
void ContactsManager::on_chat_update(telegram_api::chatForbidden &chat, const char *source) {
@ -13954,7 +13889,6 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char
bool has_linked_channel = (channel.flags_ & CHANNEL_FLAG_HAS_LINKED_CHAT) != 0;
bool has_location = (channel.flags_ & CHANNEL_FLAG_HAS_LOCATION) != 0;
bool has_active_group_call = (channel.flags_ & CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL) != 0;
bool sign_messages = (channel.flags_ & CHANNEL_FLAG_SIGN_MESSAGES) != 0;
bool is_slow_mode_enabled = (channel.flags_ & CHANNEL_FLAG_IS_SLOW_MODE_ENABLED) != 0;
bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0;
@ -14058,24 +13992,25 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char
}
bool need_invalidate_channel_full = false;
bool need_drop_active_group_call_id = c->has_active_group_call != has_active_group_call;
if (c->has_linked_channel != has_linked_channel || c->has_location != has_location ||
c->has_active_group_call != has_active_group_call || c->sign_messages != sign_messages ||
c->is_megagroup != is_megagroup || c->is_verified != is_verified ||
c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup ||
c->restriction_reasons != restriction_reasons || c->is_scam != is_scam) {
c->has_linked_channel = has_linked_channel;
c->has_location = has_location;
c->has_active_group_call = has_active_group_call;
c->sign_messages = sign_messages;
c->is_slow_mode_enabled = is_slow_mode_enabled;
c->is_megagroup = is_megagroup;
c->is_verified = is_verified;
c->restriction_reasons = std::move(restriction_reasons);
c->is_scam = is_scam;
c->is_changed = true;
need_invalidate_channel_full = true;
}
if (c->is_verified != is_verified || c->sign_messages != sign_messages) {
c->is_verified = is_verified;
c->sign_messages = sign_messages;
c->is_changed = true;
}
if (c->cache_version != Channel::CACHE_VERSION) {
c->cache_version = Channel::CACHE_VERSION;
@ -14085,8 +14020,12 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel, const char
update_channel(c, channel_id);
if (need_invalidate_channel_full) {
invalidate_channel_full(channel_id, false, !c->is_slow_mode_enabled, need_drop_active_group_call_id);
invalidate_channel_full(channel_id, false, !c->is_slow_mode_enabled);
}
bool has_active_group_call = (channel.flags_ & CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL) != 0;
bool is_group_call_empty = (channel.flags_ & CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0;
td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), has_active_group_call, is_group_call_empty);
}
void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, const char *source) {
@ -14125,8 +14064,8 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co
// on_update_channel_username(c, channel_id, ""); // don't know if channel username is empty, so don't update it
tl_object_ptr<telegram_api::chatBannedRights> banned_rights; // == nullptr
on_update_channel_default_permissions(c, channel_id, get_restricted_rights(banned_rights));
td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false);
bool has_active_group_call = false;
bool sign_messages = false;
bool is_slow_mode_enabled = false;
bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0;
@ -14150,22 +14089,24 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, co
}
bool need_invalidate_channel_full = false;
if (c->has_active_group_call != has_active_group_call || c->sign_messages != sign_messages ||
c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup ||
c->is_verified != is_verified || !c->restriction_reasons.empty() || c->is_scam != is_scam) {
if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup ||
!c->restriction_reasons.empty() || c->is_scam != is_scam) {
// c->has_linked_channel = has_linked_channel;
// c->has_location = has_location;
c->has_active_group_call = has_active_group_call;
c->sign_messages = sign_messages;
c->is_slow_mode_enabled = is_slow_mode_enabled;
c->is_megagroup = is_megagroup;
c->is_verified = is_verified;
c->restriction_reasons.clear();
c->is_scam = is_scam;
c->is_changed = true;
need_invalidate_channel_full = true;
}
if (c->sign_messages != sign_messages || c->is_verified != is_verified) {
c->sign_messages = sign_messages;
c->is_verified = is_verified;
c->is_changed = true;
}
if (c->cache_version != Channel::CACHE_VERSION) {
c->cache_version = Channel::CACHE_VERSION;
@ -14381,7 +14322,7 @@ td_api::object_ptr<td_api::updateSupergroup> ContactsManager::get_update_unknown
ChannelId channel_id) {
return td_api::make_object<td_api::updateSupergroup>(td_api::make_object<td_api::supergroup>(
channel_id.get(), string(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, false,
false, false, false, false, true, false, "", false));
false, false, false, true, false, "", false));
}
int32 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const {
@ -14403,18 +14344,17 @@ tl_object_ptr<td_api::supergroup> ContactsManager::get_supergroup_object(Channel
}
return td_api::make_object<td_api::supergroup>(
channel_id.get(), c->username, c->date, get_channel_status(c).get_chat_member_status_object(),
c->participant_count, c->has_linked_channel, c->has_location, c->has_active_group_call, c->sign_messages,
c->is_slow_mode_enabled, !c->is_megagroup, c->is_verified,
get_restriction_reason_description(c->restriction_reasons), c->is_scam);
c->participant_count, c->has_linked_channel, c->has_location, c->sign_messages, c->is_slow_mode_enabled,
!c->is_megagroup, c->is_verified, get_restriction_reason_description(c->restriction_reasons), c->is_scam);
}
tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_supergroup_full_info_object(ChannelId channel_id) const {
return get_supergroup_full_info_object(get_channel_full(channel_id));
return get_supergroup_full_info_object(get_channel_full(channel_id), channel_id);
}
tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_supergroup_full_info_object(
const ChannelFull *channel_full) const {
if (channel_full == nullptr) {
const ChannelFull *channel_full, ChannelId channel_id) const {
if(channel_full == nullptr) {
return nullptr;
}
double slow_mode_delay_expires_in = 0;
@ -14424,11 +14364,11 @@ tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_supergroup_full_i
return td_api::make_object<td_api::supergroupFullInfo>(
get_chat_photo_object(td_->file_manager_.get(), channel_full->photo), channel_full->description,
channel_full->participant_count, channel_full->administrator_count, channel_full->restricted_count,
channel_full->banned_count, DialogId(channel_full->linked_channel_id).get(),
channel_full->active_group_call_id.get_group_call_id(), channel_full->slow_mode_delay, slow_mode_delay_expires_in,
channel_full->can_get_participants, channel_full->can_set_username, channel_full->can_set_sticker_set,
channel_full->can_set_location, channel_full->can_view_statistics, channel_full->is_all_history_available,
channel_full->sticker_set_id.get(), channel_full->location.get_chat_location_object(), channel_full->invite_link,
channel_full->banned_count, DialogId(channel_full->linked_channel_id).get(), channel_full->slow_mode_delay,
slow_mode_delay_expires_in, channel_full->can_get_participants, channel_full->can_set_username,
channel_full->can_set_sticker_set, channel_full->can_set_location, channel_full->can_view_statistics,
channel_full->is_all_history_available, channel_full->sticker_set_id.get(),
channel_full->location.get_chat_location_object(), channel_full->invite_link,
get_basic_group_id_object(channel_full->migrated_from_chat_id, "get_supergroup_full_info_object"),
channel_full->migrated_from_max_message_id.get());
}
@ -14648,7 +14588,7 @@ void ContactsManager::get_current_state(vector<td_api::object_ptr<td_api::Update
}
for (auto &it : channels_full_) {
updates.push_back(td_api::make_object<td_api::updateSupergroupFullInfo>(
it.first.get(), get_supergroup_full_info_object(it.second.get())));
it.first.get(), get_supergroup_full_info_object(it.second.get(), it.first)));
}
for (auto &it : chats_full_) {
updates.push_back(td_api::make_object<td_api::updateBasicGroupFullInfo>(

View File

@ -21,7 +21,6 @@
#include "td/telegram/files/FileSourceId.h"
#include "td/telegram/FolderId.h"
#include "td/telegram/FullMessageId.h"
#include "td/telegram/InputGroupCallId.h"
#include "td/telegram/Location.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/net/DcId.h"
@ -220,8 +219,7 @@ class ContactsManager : public Actor {
void speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me);
void invalidate_channel_full(ChannelId channel_id, bool drop_invite_link, bool drop_slow_mode_delay,
bool drop_active_group_call_id = false);
void invalidate_channel_full(ChannelId channel_id, bool drop_invite_link, bool drop_slow_mode_delay);
bool on_get_channel_error(ChannelId channel_id, const Status &status, const string &source);
@ -369,8 +367,6 @@ class ContactsManager : public Actor {
void set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise<Unit> &&promise);
void create_channel_group_call(DialogId dialog_id, Promise<InputGroupCallId> &&promise);
void report_channel_spam(ChannelId channel_id, UserId user_id, const vector<MessageId> &message_ids,
Promise<Unit> &&promise);
@ -797,7 +793,6 @@ class ContactsManager : public Actor {
bool has_linked_channel = false;
bool has_location = false;
bool has_active_group_call = false;
bool sign_messages = false;
bool is_slow_mode_enabled = false;
@ -856,8 +851,6 @@ class ContactsManager : public Actor {
DcId stats_dc_id;
InputGroupCallId active_group_call_id;
int32 slow_mode_delay = 0;
int32 slow_mode_next_send_date = 0;
@ -1004,10 +997,13 @@ class ContactsManager : public Actor {
// static constexpr int32 CHAT_FLAG_IS_ADMINISTRATOR = 1 << 4;
static constexpr int32 CHAT_FLAG_IS_DEACTIVATED = 1 << 5;
static constexpr int32 CHAT_FLAG_WAS_MIGRATED = 1 << 6;
static constexpr int32 CHAT_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23;
static constexpr int32 CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24;
static constexpr int32 CHAT_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 6;
static constexpr int32 CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 8;
static constexpr int32 CHAT_FULL_FLAG_HAS_FOLDER_ID = 1 << 11;
static constexpr int32 CHAT_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 12;
static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0;
static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2;
@ -1029,6 +1025,7 @@ class ContactsManager : public Actor {
static constexpr int32 CHANNEL_FLAG_HAS_LOCATION = 1 << 21;
static constexpr int32 CHANNEL_FLAG_IS_SLOW_MODE_ENABLED = 1 << 22;
static constexpr int32 CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23;
static constexpr int32 CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 0;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT = 1 << 1;
@ -1321,9 +1318,6 @@ class ContactsManager : public Actor {
void update_bot_info(BotInfo *bot_info, UserId user_id, bool send_update, bool from_database);
void on_create_channel_group_call(ChannelId channel_id, InputGroupCallId group_call_id,
Promise<InputGroupCallId> &&promise);
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) const;
@ -1414,7 +1408,8 @@ class ContactsManager : public Actor {
tl_object_ptr<td_api::supergroup> get_supergroup_object(ChannelId channel_id, const Channel *c) const;
tl_object_ptr<td_api::supergroupFullInfo> get_supergroup_full_info_object(const ChannelFull *channel_full) const;
tl_object_ptr<td_api::supergroupFullInfo> get_supergroup_full_info_object(const ChannelFull *channel_full,
ChannelId channel_id) const;
static tl_object_ptr<td_api::SecretChatState> get_secret_chat_state_object(SecretChatState state);

View File

@ -80,9 +80,6 @@ DialogAction::DialogAction(tl_object_ptr<td_api::ChatAction> &&action) {
init(Type::UploadingVideoNote, uploading_action->progress_);
break;
}
case td_api::chatActionSpeakingInCall::ID:
init(Type::SpeakingInCall);
break;
default:
UNREACHABLE();
break;
@ -141,7 +138,7 @@ DialogAction::DialogAction(tl_object_ptr<telegram_api::SendMessageAction> &&acti
break;
}
case telegram_api::speakingInGroupCallAction::ID:
init(Type::SpeakingInCall);
init(Type::SpeakingInVoiceChat);
break;
default:
UNREACHABLE();
@ -177,7 +174,7 @@ tl_object_ptr<telegram_api::SendMessageAction> DialogAction::get_input_send_mess
return make_tl_object<telegram_api::sendMessageRecordRoundAction>();
case Type::UploadingVideoNote:
return make_tl_object<telegram_api::sendMessageUploadRoundAction>(progress_);
case Type::SpeakingInCall:
case Type::SpeakingInVoiceChat:
return make_tl_object<telegram_api::speakingInGroupCallAction>();
default:
UNREACHABLE();
@ -213,7 +210,7 @@ tl_object_ptr<secret_api::SendMessageAction> DialogAction::get_secret_input_send
return make_tl_object<secret_api::sendMessageRecordRoundAction>();
case Type::UploadingVideoNote:
return make_tl_object<secret_api::sendMessageUploadRoundAction>();
case Type::SpeakingInCall:
case Type::SpeakingInVoiceChat:
return make_tl_object<secret_api::sendMessageTypingAction>();
default:
UNREACHABLE();
@ -249,8 +246,7 @@ tl_object_ptr<td_api::ChatAction> DialogAction::get_chat_action_object() const {
return td_api::make_object<td_api::chatActionRecordingVideoNote>();
case Type::UploadingVideoNote:
return td_api::make_object<td_api::chatActionUploadingVideoNote>(progress_);
case Type::SpeakingInCall:
return td_api::make_object<td_api::chatActionSpeakingInCall>();
case Type::SpeakingInVoiceChat:
default:
UNREACHABLE();
return td_api::make_object<td_api::chatActionCancel>();
@ -350,6 +346,10 @@ DialogAction DialogAction::get_typing_action() {
return DialogAction(Type::Typing, 0);
}
DialogAction DialogAction::get_speaking_action() {
return DialogAction(Type::SpeakingInVoiceChat, 0);
}
StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &action) {
string_builder << "ChatAction";
const char *type = [action_type = action.type_] {
@ -380,8 +380,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &act
return "RecordingVideoNote";
case DialogAction::Type::UploadingVideoNote:
return "UploadingVideoNote";
case DialogAction::Type::SpeakingInCall:
return "SpeakingInCall";
case DialogAction::Type::SpeakingInVoiceChat:
return "SpeakingInVoiceChat";
default:
UNREACHABLE();
return "Cancel";

View File

@ -31,7 +31,7 @@ class DialogAction {
StartPlayingGame,
RecordingVideoNote,
UploadingVideoNote,
SpeakingInCall
SpeakingInVoiceChat
};
Type type_ = Type::Cancel;
int32 progress_ = 0;
@ -61,6 +61,8 @@ class DialogAction {
static DialogAction get_typing_action();
static DialogAction get_speaking_action();
friend bool operator==(const DialogAction &lhs, const DialogAction &rhs) {
return lhs.type_ == rhs.type_ && lhs.progress_ == rhs.progress_;
}

View File

@ -92,7 +92,7 @@ DialogParticipantStatus DialogParticipantStatus::Banned(int32 banned_until_date)
}
DialogParticipantStatus DialogParticipantStatus::GroupAdministrator(bool is_creator) {
return Administrator(false, string(), is_creator, true, false, false, true, true, true, true, false, false);
return Administrator(false, string(), is_creator, true, false, false, true, true, true, true, false, true);
}
DialogParticipantStatus DialogParticipantStatus::ChannelAdministrator(bool is_creator, bool is_megagroup) {
@ -320,7 +320,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant
string_builder << "(promote)";
}
if (status.can_manage_calls()) {
string_builder << "(call)";
string_builder << "(voice chat)";
}
if (!status.rank_.empty()) {
string_builder << " [" << status.rank_ << "]";
@ -404,7 +404,7 @@ DialogParticipantStatus get_dialog_participant_status(const tl_object_ptr<td_api
return DialogParticipantStatus::Administrator(
st->is_anonymous_, st->custom_title_, true /*st->can_be_edited_*/, st->can_change_info_,
st->can_post_messages_, st->can_edit_messages_, st->can_delete_messages_, st->can_invite_users_,
st->can_restrict_members_, st->can_pin_messages_, st->can_promote_members_, st->can_manage_calls_);
st->can_restrict_members_, st->can_pin_messages_, st->can_promote_members_, st->can_manage_voice_chats_);
}
case td_api::chatMemberStatusMember::ID:
return DialogParticipantStatus::Member();

View File

@ -354,6 +354,10 @@ class DialogParticipantStatus {
}
type_ = static_cast<Type>(stored_flags >> TYPE_SHIFT);
flags_ = stored_flags & ((1 << TYPE_SHIFT) - 1);
if (is_creator()) {
flags_ |= ALL_ADMINISTRATOR_RIGHTS | ALL_PERMISSION_RIGHTS;
}
}
friend bool operator==(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs);

53
td/telegram/GroupCallId.h Normal file
View File

@ -0,0 +1,53 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
#include <functional>
#include <type_traits>
namespace td {
class GroupCallId {
public:
GroupCallId() = default;
explicit GroupCallId(int32 group_call_id) : id(group_call_id) {
}
template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
GroupCallId(T group_call_id) = delete;
bool is_valid() const {
return id > 0;
}
int32 get() const {
return id;
}
bool operator==(const GroupCallId &other) const {
return id == other.id;
}
private:
int32 id{0};
};
struct GroupCallIdHash {
std::size_t operator()(GroupCallId group_call_id) const {
return std::hash<int32>()(group_call_id.get());
}
};
inline StringBuilder &operator<<(StringBuilder &sb, const GroupCallId group_call_id) {
return sb << "group call " << group_call_id.get();
}
} // namespace td

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,19 @@
//
#pragma once
#include "td/telegram/ChannelId.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/GroupCallId.h"
#include "td/telegram/GroupCallParticipant.h"
#include "td/telegram/InputGroupCallId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/actor/Timeout.h"
#include "td/utils/Status.h"
#include <unordered_map>
@ -23,32 +29,169 @@ class Td;
class GroupCallManager : public Actor {
public:
GroupCallManager(Td *td, ActorShared<> parent);
GroupCallManager(const GroupCallManager &) = delete;
GroupCallManager &operator=(const GroupCallManager &) = delete;
GroupCallManager(GroupCallManager &&) = delete;
GroupCallManager &operator=(GroupCallManager &&) = delete;
~GroupCallManager() override;
void create_group_call(ChannelId channel_id, Promise<InputGroupCallId> &&promise);
GroupCallId get_group_call_id(InputGroupCallId input_group_call_id, DialogId dialog_id);
void leave_group_call(InputGroupCallId group_call_id, int32 source, Promise<Unit> &&promise);
void create_voice_chat(DialogId dialog_id, Promise<GroupCallId> &&promise);
void discard_group_call(InputGroupCallId group_call_id, Promise<Unit> &&promise);
void get_group_call(GroupCallId group_call_id, Promise<td_api::object_ptr<td_api::groupCall>> &&promise);
void on_update_group_call(tl_object_ptr<telegram_api::GroupCall> group_call_ptr);
void join_group_call(GroupCallId group_call_id, td_api::object_ptr<td_api::groupCallPayload> &&payload, int32 source,
bool is_muted, Promise<td_api::object_ptr<td_api::groupCallJoinResponse>> &&promise);
void toggle_group_call_mute_new_participants(GroupCallId group_call_id, bool mute_new_participants,
Promise<Unit> &&promise);
void invite_group_call_participants(GroupCallId group_call_id, vector<UserId> &&user_ids, Promise<Unit> &&promise);
void set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 source, bool is_speaking,
Promise<Unit> &&promise, int32 date = 0);
void toggle_group_call_participant_is_muted(GroupCallId group_call_id, UserId user_id, bool is_muted,
Promise<Unit> &&promise);
void check_group_call_is_joined(GroupCallId group_call_id, Promise<Unit> &&promise);
void load_group_call_participants(GroupCallId group_call_id, int32 limit, Promise<Unit> &&promise);
void leave_group_call(GroupCallId group_call_id, Promise<Unit> &&promise);
void discard_group_call(GroupCallId group_call_id, Promise<Unit> &&promise);
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, UserId user_id, int32 date, bool recursive = false);
void on_get_group_call_participants(InputGroupCallId input_group_call_id,
tl_object_ptr<telegram_api::phone_groupParticipants> &&participants, bool is_load,
const string &offset);
void on_update_group_call_participants(InputGroupCallId input_group_call_id,
vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants,
int32 version);
void process_join_group_call_response(InputGroupCallId input_group_call_id, uint64 generation,
tl_object_ptr<telegram_api::Updates> &&updates, Promise<Unit> &&promise);
private:
struct GroupCall;
struct GroupCallParticipants;
struct GroupCallRecentSpeakers;
struct PendingJoinRequest;
static constexpr int32 RECENT_SPEAKER_TIMEOUT = 5 * 60;
void tear_down() override;
InputGroupCallId update_group_call(const tl_object_ptr<telegram_api::GroupCall> &group_call_ptr);
static void on_pending_send_speaking_action_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
static tl_object_ptr<td_api::updateGroupCall> get_update_group_call_object(InputGroupCallId group_call_id,
const GroupCall *group_call);
void on_send_speaking_action_timeout(GroupCallId group_call_id);
static tl_object_ptr<td_api::groupCall> get_group_call_object(InputGroupCallId group_call_id,
const GroupCall *group_call);
static void on_recent_speaker_update_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
void on_recent_speaker_update_timeout(GroupCallId group_call_id);
static void on_sync_participants_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
void on_sync_participants_timeout(GroupCallId group_call_id);
Result<InputGroupCallId> get_input_group_call_id(GroupCallId group_call_id);
GroupCallId get_next_group_call_id(InputGroupCallId input_group_call_id);
GroupCall *add_group_call(InputGroupCallId input_group_call_id, DialogId dialog_id);
const GroupCall *get_group_call(InputGroupCallId input_group_call_id) const;
GroupCall *get_group_call(InputGroupCallId input_group_call_id);
void on_voice_chat_created(DialogId dialog_id, InputGroupCallId input_group_call_id, Promise<GroupCallId> &&promise);
void reload_group_call(InputGroupCallId input_group_call_id,
Promise<td_api::object_ptr<td_api::groupCall>> &&promise);
void finish_get_group_call(InputGroupCallId input_group_call_id,
Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result);
bool need_group_call_participants(InputGroupCallId input_group_call_id) const;
bool process_pending_group_call_participant_updates(InputGroupCallId input_group_call_id);
void sync_group_call_participants(InputGroupCallId input_group_call_id);
void process_group_call_participants(InputGroupCallId group_call_id,
vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants,
bool is_load);
int32 process_group_call_participants_from_updates(
InputGroupCallId group_call_id, vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants);
int process_group_call_participant(InputGroupCallId group_call_id, GroupCallParticipant &&participant);
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);
void on_group_call_left(InputGroupCallId input_group_call_id, int32 source);
InputGroupCallId update_group_call(const tl_object_ptr<telegram_api::GroupCall> &group_call_ptr, DialogId dialog_id);
void on_receive_group_call_version(InputGroupCallId input_group_call_id, int32 version);
void on_group_call_recent_speakers_updated(const GroupCall *group_call, GroupCallRecentSpeakers *recent_speakers);
UserId get_group_call_participant_by_source(InputGroupCallId input_group_call_id, int32 source);
static Result<td_api::object_ptr<td_api::groupCallJoinResponse>> get_group_call_join_response_object(
string json_response);
void try_clear_group_call_participants(InputGroupCallId input_group_call_id);
vector<int32> get_recent_speaker_user_ids(const GroupCall *group_call, bool for_update);
tl_object_ptr<td_api::updateGroupCall> get_update_group_call_object(const GroupCall *group_call,
vector<int32> recent_speaker_user_ids) const;
tl_object_ptr<td_api::groupCall> get_group_call_object(const GroupCall *group_call,
vector<int32> recent_speaker_user_ids) const;
tl_object_ptr<td_api::updateGroupCallParticipant> get_update_group_call_participant_object(
GroupCallId group_call_id, const GroupCallParticipant &participant);
void send_update_group_call(const GroupCall *group_call);
void send_update_group_call_participant(GroupCallId group_call_id, const GroupCallParticipant &participant);
void send_update_group_call_participant(InputGroupCallId input_group_call_id,
const GroupCallParticipant &participant);
Td *td_;
ActorShared<> parent_;
GroupCallId max_group_call_id_;
vector<InputGroupCallId> input_group_call_ids_;
std::unordered_map<InputGroupCallId, unique_ptr<GroupCall>, InputGroupCallIdHash> group_calls_;
std::unordered_map<InputGroupCallId, unique_ptr<GroupCallParticipants>, InputGroupCallIdHash>
group_call_participants_;
std::unordered_map<GroupCallId, unique_ptr<GroupCallRecentSpeakers>, GroupCallIdHash> group_call_recent_speakers_;
std::unordered_map<InputGroupCallId, vector<Promise<td_api::object_ptr<td_api::groupCall>>>, InputGroupCallIdHash>
load_group_call_queries_;
std::unordered_map<InputGroupCallId, unique_ptr<PendingJoinRequest>, InputGroupCallIdHash> pending_join_requests_;
uint64 join_group_request_generation_ = 0;
MultiTimeout pending_send_speaking_action_timeout_{"PendingSendSpeakingActionTimeout"};
MultiTimeout recent_speaker_update_timeout_{"RecentSpeakerUpdateTimeout"};
MultiTimeout sync_participants_timeout_{"SyncParticipantsTimeout"};
};
} // namespace td

View File

@ -0,0 +1,61 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/GroupCallParticipant.h"
#include "td/telegram/ContactsManager.h"
namespace td {
GroupCallParticipant::GroupCallParticipant(const tl_object_ptr<telegram_api::groupCallParticipant> &participant) {
CHECK(participant != nullptr);
user_id = UserId(participant->user_id_);
source = participant->source_;
is_muted = participant->muted_;
can_self_unmute = participant->can_self_unmute_;
if (!participant->left_) {
joined_date = participant->date_;
if ((participant->flags_ & telegram_api::groupCallParticipant::ACTIVE_DATE_MASK) != 0) {
active_date = participant->active_date_;
}
if (joined_date < 0 || active_date < 0) {
LOG(ERROR) << "Receive invalid " << to_string(participant);
joined_date = 0;
active_date = 0;
}
}
is_just_joined = participant->just_joined_;
}
td_api::object_ptr<td_api::groupCallParticipant> GroupCallParticipant::get_group_call_participant_object(
ContactsManager *contacts_manager) const {
if (!is_valid()) {
return nullptr;
}
return td_api::make_object<td_api::groupCallParticipant>(
contacts_manager->get_user_id_object(user_id, "get_group_call_participant_object"), source, is_speaking, is_muted,
can_self_unmute, order);
}
bool operator==(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) {
return lhs.user_id == rhs.user_id && lhs.source == rhs.source && lhs.is_muted == rhs.is_muted &&
lhs.can_self_unmute == rhs.can_self_unmute &&
max(lhs.active_date, lhs.local_active_date) == max(rhs.active_date, rhs.local_active_date) &&
lhs.joined_date == rhs.joined_date && lhs.is_speaking == rhs.is_speaking ||
lhs.order != rhs.order;
}
bool operator!=(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const GroupCallParticipant &group_call_participant) {
return string_builder << '[' << group_call_participant.user_id << " with source " << group_call_participant.source
<< " and order " << group_call_participant.order << ']';
}
} // namespace td

View File

@ -0,0 +1,55 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
namespace td {
class ContactsManager;
struct GroupCallParticipant {
UserId user_id;
int32 source = 0;
bool is_muted = false;
bool can_self_unmute = false;
int32 joined_date = 0;
int32 active_date = 0;
bool is_just_joined = false;
bool is_speaking = false;
int32 local_active_date = 0;
int64 order = 0;
int64 get_real_order() const {
return (static_cast<int64>(max(active_date, local_active_date)) << 32) + joined_date;
}
bool is_valid() const {
return user_id.is_valid();
}
GroupCallParticipant() = default;
explicit GroupCallParticipant(const tl_object_ptr<telegram_api::groupCallParticipant> &participant);
td_api::object_ptr<td_api::groupCallParticipant> get_group_call_participant_object(
ContactsManager *contacts_manager) const;
};
bool operator==(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs);
bool operator!=(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const GroupCallParticipant &group_call_participant);
} // namespace td

View File

@ -66,8 +66,10 @@ class GetInlineBotResultsQuery : public Td::ResultHandler {
explicit GetInlineBotResultsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
NetQueryRef send(UserId bot_user_id, tl_object_ptr<telegram_api::InputUser> bot_input_user, DialogId dialog_id,
Location user_location, const string &query, const string &offset, uint64 query_hash) {
NetQueryRef send(UserId bot_user_id, tl_object_ptr<telegram_api::InputUser> bot_input_user,
tl_object_ptr<telegram_api::InputPeer> input_peer, Location user_location, const string &query,
const string &offset, uint64 query_hash) {
CHECK(input_peer != nullptr);
bot_user_id_ = bot_user_id;
query_hash_ = query_hash;
int32 flags = 0;
@ -75,11 +77,6 @@ class GetInlineBotResultsQuery : public Td::ResultHandler {
flags |= GET_INLINE_BOT_RESULTS_FLAG_HAS_LOCATION;
}
auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
input_peer = make_tl_object<telegram_api::inputPeerEmpty>();
}
auto net_query = G()->net_query_creator().create(telegram_api::messages_getInlineBotResults(
flags, std::move(bot_input_user), std::move(input_peer),
user_location.empty() ? nullptr : user_location.get_input_geo_point(), query, offset));
@ -215,7 +212,7 @@ tl_object_ptr<telegram_api::inputBotInlineMessageID> InlineQueriesManager::get_i
string InlineQueriesManager::get_inline_message_id(
tl_object_ptr<telegram_api::inputBotInlineMessageID> &&input_bot_inline_message_id) {
if (input_bot_inline_message_id == nullptr) {
return "";
return string();
}
LOG(INFO) << "Got inline message id: " << to_string(input_bot_inline_message_id);
@ -760,13 +757,34 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial
return 0;
}
bool is_broadcast_channel =
dialog_id.get_type() == DialogType::Channel &&
td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast;
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
input_peer = make_tl_object<telegram_api::inputPeerEmpty>();
}
auto peer_type = [&] {
switch (input_peer->get_id()) {
case telegram_api::inputPeerEmpty::ID:
return 0;
case telegram_api::inputPeerSelf::ID:
return 1;
case telegram_api::inputPeerChat::ID:
return 2;
case telegram_api::inputPeerUser::ID:
case telegram_api::inputPeerUserFromMessage::ID:
return dialog_id == DialogId(bot_user_id) ? 3 : 4;
case telegram_api::inputPeerChannel::ID:
case telegram_api::inputPeerChannelFromMessage::ID:
return 5 + static_cast<int>(td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()));
default:
UNREACHABLE();
return -1;
}
}();
uint64 query_hash = std::hash<std::string>()(trim(query));
query_hash = query_hash * 2023654985u + bot_user_id.get();
query_hash = query_hash * 2023654985u + static_cast<uint64>(is_broadcast_channel);
query_hash = query_hash * 2023654985u + static_cast<uint64>(peer_type);
query_hash = query_hash * 2023654985u + std::hash<std::string>()(offset);
if (r_bot_data.ok().need_location) {
query_hash = query_hash * 2023654985u + static_cast<uint64>(user_location.get_latitude() * 1e4);
@ -791,8 +809,8 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial
pending_inline_query_->promise.set_error(Status::Error(406, "Request cancelled"));
}
pending_inline_query_ = make_unique<PendingInlineQuery>(
PendingInlineQuery{query_hash, bot_user_id, dialog_id, user_location, query, offset, std::move(promise)});
pending_inline_query_ = make_unique<PendingInlineQuery>(PendingInlineQuery{
query_hash, bot_user_id, std::move(input_peer), user_location, query, offset, std::move(promise)});
loop();
@ -816,9 +834,9 @@ void InlineQueriesManager::loop() {
}
sent_query_ =
td_->create_handler<GetInlineBotResultsQuery>(std::move(pending_inline_query_->promise))
->send(pending_inline_query_->bot_user_id, std::move(bot_input_user), pending_inline_query_->dialog_id,
pending_inline_query_->user_location, pending_inline_query_->query, pending_inline_query_->offset,
pending_inline_query_->query_hash);
->send(pending_inline_query_->bot_user_id, std::move(bot_input_user),
std::move(pending_inline_query_->input_peer), pending_inline_query_->user_location,
pending_inline_query_->query, pending_inline_query_->offset, pending_inline_query_->query_hash);
next_inline_query_time_ = now + INLINE_QUERY_DELAY_MS * 1e-3;
}
@ -877,6 +895,10 @@ tl_object_ptr<td_api::photoSize> copy(const td_api::photoSize &obj) {
vector<int32>(obj.progressive_sizes_));
}
static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_api::photoSize> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::thumbnail> copy(const td_api::thumbnail &obj) {
auto format = [&]() -> td_api::object_ptr<td_api::ThumbnailFormat> {
@ -902,10 +924,6 @@ tl_object_ptr<td_api::thumbnail> copy(const td_api::thumbnail &obj) {
return make_tl_object<td_api::thumbnail>(std::move(format), obj.width_, obj.height_, copy(obj.file_));
}
static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_api::photoSize> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::MaskPoint> copy(const td_api::MaskPoint &obj) {
switch (obj.get_id()) {
@ -928,6 +946,44 @@ tl_object_ptr<td_api::maskPosition> copy(const td_api::maskPosition &obj) {
return make_tl_object<td_api::maskPosition>(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_);
}
template <>
tl_object_ptr<td_api::point> copy(const td_api::point &obj) {
return make_tl_object<td_api::point>(obj.x_, obj.y_);
}
template <>
tl_object_ptr<td_api::VectorPathCommand> copy(const td_api::VectorPathCommand &obj) {
switch (obj.get_id()) {
case td_api::vectorPathCommandLine::ID: {
auto &command = static_cast<const td_api::vectorPathCommandLine &>(obj);
return make_tl_object<td_api::vectorPathCommandLine>(copy(command.end_point_));
}
case td_api::vectorPathCommandCubicBezierCurve::ID: {
auto &command = static_cast<const td_api::vectorPathCommandCubicBezierCurve &>(obj);
return make_tl_object<td_api::vectorPathCommandCubicBezierCurve>(
copy(command.start_control_point_), copy(command.end_control_point_), copy(command.end_point_));
}
default:
UNREACHABLE();
return nullptr;
}
}
static tl_object_ptr<td_api::VectorPathCommand> copy_vector_path_command(
const tl_object_ptr<td_api::VectorPathCommand> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::closedVectorPath> copy(const td_api::closedVectorPath &obj) {
return make_tl_object<td_api::closedVectorPath>(transform(obj.commands_, copy_vector_path_command));
}
static tl_object_ptr<td_api::closedVectorPath> copy_closed_vector_path(
const tl_object_ptr<td_api::closedVectorPath> &obj) {
return copy(obj);
}
template <>
tl_object_ptr<td_api::animation> copy(const td_api::animation &obj) {
return make_tl_object<td_api::animation>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
@ -956,9 +1012,9 @@ tl_object_ptr<td_api::photo> copy(const td_api::photo &obj) {
template <>
tl_object_ptr<td_api::sticker> copy(const td_api::sticker &obj) {
return make_tl_object<td_api::sticker>(obj.set_id_, obj.width_, obj.height_, obj.emoji_, obj.is_animated_,
obj.is_mask_, copy(obj.mask_position_), obj.cover_, copy(obj.thumbnail_),
copy(obj.sticker_));
return make_tl_object<td_api::sticker>(
obj.set_id_, obj.width_, obj.height_, obj.emoji_, obj.is_animated_, obj.is_mask_, copy(obj.mask_position_),
transform(obj.outline_, copy_closed_vector_path), copy(obj.thumbnail_), copy(obj.sticker_));
}
template <>
@ -1696,7 +1752,8 @@ tl_object_ptr<td_api::inlineQueryResults> InlineQueriesManager::get_inline_query
}
void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, Location user_location,
const string &query, const string &offset) {
tl_object_ptr<telegram_api::InlineQueryPeerType> peer_type, const string &query,
const string &offset) {
if (!sender_user_id.is_valid()) {
LOG(ERROR) << "Receive new inline query from invalid " << sender_user_id;
return;
@ -1706,10 +1763,31 @@ void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, L
LOG(ERROR) << "Receive new inline query";
return;
}
auto chat_type = [&]() -> td_api::object_ptr<td_api::ChatType> {
if (peer_type == nullptr) {
return nullptr;
}
switch (peer_type->get_id()) {
case telegram_api::inlineQueryPeerTypeSameBotPM::ID:
return td_api::make_object<td_api::chatTypePrivate>(sender_user_id.get());
case telegram_api::inlineQueryPeerTypePM::ID:
return td_api::make_object<td_api::chatTypePrivate>(0);
case telegram_api::inlineQueryPeerTypeChat::ID:
return td_api::make_object<td_api::chatTypeBasicGroup>(0);
case telegram_api::inlineQueryPeerTypeMegagroup::ID:
return td_api::make_object<td_api::chatTypeSupergroup>(0, false);
case telegram_api::inlineQueryPeerTypeBroadcast::ID:
return td_api::make_object<td_api::chatTypeSupergroup>(0, true);
default:
UNREACHABLE();
return nullptr;
}
}();
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateNewInlineQuery>(
query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineQuery"),
user_location.get_location_object(), query, offset));
user_location.get_location_object(), std::move(chat_type), query, offset));
}
void InlineQueriesManager::on_chosen_result(

View File

@ -62,7 +62,8 @@ class InlineQueriesManager : public Actor {
tl_object_ptr<td_api::inlineQueryResults> get_inline_query_results_object(uint64 query_hash);
void on_new_query(int64 query_id, UserId sender_user_id, Location user_location, const string &query,
void on_new_query(int64 query_id, UserId sender_user_id, Location user_location,
tl_object_ptr<telegram_api::InlineQueryPeerType> peer_type, const string &query,
const string &offset);
void on_chosen_result(UserId user_id, Location user_location, const string &query, const string &result_id,
@ -119,7 +120,7 @@ class InlineQueriesManager : public Actor {
struct PendingInlineQuery {
uint64 query_hash;
UserId bot_user_id;
DialogId dialog_id;
tl_object_ptr<telegram_api::InputPeer> input_peer;
Location user_location;
string query;
string offset;

View File

@ -15,37 +15,12 @@ InputGroupCallId::InputGroupCallId(const tl_object_ptr<telegram_api::inputGroupC
: group_call_id(input_group_call->id_), access_hash(input_group_call->access_hash_) {
}
Result<InputGroupCallId> InputGroupCallId::from_group_call_id(const string &group_call_id, bool allow_empty) {
if (group_call_id.empty()) {
if (!allow_empty) {
return Status::Error(400, "Empty group call identifier specified");
}
return InputGroupCallId();
}
auto splitted = split(group_call_id, '_');
auto r_group_call_id = to_integer_safe<int64>(splitted.first);
auto r_access_hash = to_integer_safe<int64>(splitted.second);
if (r_group_call_id.is_error() || r_access_hash.is_error()) {
return Status::Error(400, "Invalid group call identifier specified");
}
return InputGroupCallId{r_group_call_id.ok(), r_access_hash.ok()};
}
string InputGroupCallId::get_group_call_id() const {
if (is_valid()) {
return PSTRING() << group_call_id << '_' << access_hash;
}
return string();
}
tl_object_ptr<telegram_api::inputGroupCall> InputGroupCallId::get_input_group_call() const {
return make_tl_object<telegram_api::inputGroupCall>(group_call_id, access_hash);
}
StringBuilder &operator<<(StringBuilder &string_builder, InputGroupCallId input_group_call_id) {
return string_builder << "group call " << input_group_call_id.group_call_id;
return string_builder << "input group call " << input_group_call_id.group_call_id;
}
} // namespace td

View File

@ -9,7 +9,6 @@
#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include <functional>
@ -28,10 +27,6 @@ class InputGroupCallId {
InputGroupCallId(int64 group_call_id, int64 access_hash) : group_call_id(group_call_id), access_hash(access_hash) {
}
static Result<InputGroupCallId> from_group_call_id(const string &group_call_id, bool allow_empty = false);
string get_group_call_id() const;
bool operator==(const InputGroupCallId &other) const {
return group_call_id == other.group_call_id;
}

View File

@ -27,6 +27,8 @@
#include "td/telegram/Game.h"
#include "td/telegram/Game.hpp"
#include "td/telegram/Global.h"
#include "td/telegram/GroupCallId.h"
#include "td/telegram/GroupCallManager.h"
#include "td/telegram/HashtagHints.h"
#include "td/telegram/InputGroupCallId.h"
#include "td/telegram/InputMessageText.h"
@ -709,11 +711,12 @@ class MessageProximityAlertTriggered : public MessageContent {
class MessageGroupCall : public MessageContent {
public:
InputGroupCallId group_call_id;
InputGroupCallId input_group_call_id;
int32 duration = -1;
MessageGroupCall() = default;
MessageGroupCall(InputGroupCallId group_call_id, int32 duration) : group_call_id(group_call_id), duration(duration) {
MessageGroupCall(InputGroupCallId input_group_call_id, int32 duration)
: input_group_call_id(input_group_call_id), duration(duration) {
}
MessageContentType get_type() const override {
@ -723,12 +726,12 @@ class MessageGroupCall : public MessageContent {
class MessageInviteToGroupCall : public MessageContent {
public:
InputGroupCallId group_call_id;
UserId user_id;
InputGroupCallId input_group_call_id;
vector<UserId> user_ids;
MessageInviteToGroupCall() = default;
MessageInviteToGroupCall(InputGroupCallId group_call_id, UserId user_id)
: group_call_id(group_call_id), user_id(user_id) {
MessageInviteToGroupCall(InputGroupCallId input_group_call_id, vector<UserId> &&user_ids)
: input_group_call_id(input_group_call_id), user_ids(std::move(user_ids)) {
}
MessageContentType get_type() const override {
@ -1014,7 +1017,7 @@ static void store(const MessageContent *content, StorerT &storer) {
BEGIN_STORE_FLAGS();
STORE_FLAG(has_duration);
END_STORE_FLAGS();
store(m->group_call_id, storer);
store(m->input_group_call_id, storer);
if (has_duration) {
store(m->duration, storer);
}
@ -1022,8 +1025,8 @@ static void store(const MessageContent *content, StorerT &storer) {
}
case MessageContentType::InviteToGroupCall: {
auto m = static_cast<const MessageInviteToGroupCall *>(content);
store(m->group_call_id, storer);
store(m->user_id, storer);
store(m->input_group_call_id, storer);
store(m->user_ids, storer);
break;
}
default:
@ -1406,7 +1409,7 @@ static void parse(unique_ptr<MessageContent> &content, ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_duration);
END_PARSE_FLAGS();
parse(m->group_call_id, parser);
parse(m->input_group_call_id, parser);
if (has_duration) {
parse(m->duration, parser);
}
@ -1415,8 +1418,8 @@ static void parse(unique_ptr<MessageContent> &content, ParserT &parser) {
}
case MessageContentType::InviteToGroupCall: {
auto m = make_unique<MessageInviteToGroupCall>();
parse(m->group_call_id, parser);
parse(m->user_id, parser);
parse(m->input_group_call_id, parser);
parse(m->user_ids, parser);
content = std::move(m);
break;
}
@ -3300,10 +3303,10 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
case MessageContentType::GroupCall: {
auto old_ = static_cast<const MessageGroupCall *>(old_content);
auto new_ = static_cast<const MessageGroupCall *>(new_content);
if (old_->group_call_id != new_->group_call_id || old_->duration != new_->duration) {
if (old_->input_group_call_id != new_->input_group_call_id || old_->duration != new_->duration) {
need_update = true;
}
if (!old_->group_call_id.is_identical(new_->group_call_id)) {
if (!old_->input_group_call_id.is_identical(new_->input_group_call_id)) {
is_content_changed = true;
}
break;
@ -3311,10 +3314,10 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo
case MessageContentType::InviteToGroupCall: {
auto old_ = static_cast<const MessageInviteToGroupCall *>(old_content);
auto new_ = static_cast<const MessageInviteToGroupCall *>(new_content);
if (old_->group_call_id != new_->group_call_id || old_->user_id != new_->user_id) {
if (old_->input_group_call_id != new_->input_group_call_id || old_->user_ids != new_->user_ids) {
need_update = true;
}
if (!old_->group_call_id.is_identical(new_->group_call_id)) {
if (!old_->input_group_call_id.is_identical(new_->input_group_call_id)) {
is_content_changed = true;
}
break;
@ -4557,13 +4560,20 @@ unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<tele
}
case telegram_api::messageActionInviteToGroupCall::ID: {
auto invite_to_group_call = move_tl_object_as<telegram_api::messageActionInviteToGroupCall>(action);
UserId user_id(invite_to_group_call->user_id_);
if (!user_id.is_valid()) {
vector<UserId> user_ids;
user_ids.reserve(invite_to_group_call->users_.size());
for (auto &user : invite_to_group_call->users_) {
UserId user_id(user);
if (user_id.is_valid()) {
user_ids.push_back(user_id);
} else {
LOG(ERROR) << "Receive messageActionInviteToGroupCall with invalid " << user_id << " in " << owner_dialog_id;
break;
}
}
return make_unique<MessageInviteToGroupCall>(InputGroupCallId(invite_to_group_call->call_), user_id);
return td::make_unique<MessageInviteToGroupCall>(InputGroupCallId(invite_to_group_call->call_),
std::move(user_ids));
}
default:
UNREACHABLE();
@ -4776,14 +4786,18 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
}
case MessageContentType::GroupCall: {
auto *m = static_cast<const MessageGroupCall *>(content);
return make_tl_object<td_api::messageGroupCall>(m->group_call_id.get_group_call_id(),
m->duration >= 0 ? max(m->duration, 1) : 0);
if (m->duration >= 0) {
return make_tl_object<td_api::messageVoiceChatEnded>(m->duration);
} else {
return make_tl_object<td_api::messageVoiceChatStarted>(
td->group_call_manager_->get_group_call_id(m->input_group_call_id, DialogId()).get());
}
}
case MessageContentType::InviteToGroupCall: {
auto *m = static_cast<const MessageInviteToGroupCall *>(content);
return make_tl_object<td_api::messageInviteToGroupCall>(
m->group_call_id.get_group_call_id(),
td->contacts_manager_->get_user_id_object(m->user_id, "MessageInviteToGroupCall"));
return make_tl_object<td_api::messageInviteVoiceChatParticipants>(
td->group_call_manager_->get_group_call_id(m->input_group_call_id, DialogId()).get(),
td->contacts_manager_->get_user_ids_object(m->user_ids, "MessageInviteToGroupCall"));
}
default:
UNREACHABLE();
@ -5302,7 +5316,7 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC
break;
case MessageContentType::InviteToGroupCall: {
auto content = static_cast<const MessageInviteToGroupCall *>(message_content);
dependencies.user_ids.insert(content->user_id);
dependencies.user_ids.insert(content->user_ids.begin(), content->user_ids.end());
break;
}
default:

View File

@ -22,6 +22,8 @@
#include "td/telegram/files/FileManager.h"
#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
#include "td/telegram/GroupCallManager.h"
#include "td/telegram/GroupCallParticipant.h"
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/InputMessageText.h"
#include "td/telegram/Location.h"
@ -4581,6 +4583,7 @@ void MessagesManager::Message::store(StorerT &storer) const {
bool has_thread_draft_message = thread_draft_message != nullptr;
bool has_local_thread_message_ids = !local_thread_message_ids.empty();
bool has_linked_top_thread_message_id = linked_top_thread_message_id.is_valid();
bool has_interaction_info_update_date = interaction_info_update_date != 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_channel_post);
STORE_FLAG(is_outgoing);
@ -4638,6 +4641,7 @@ void MessagesManager::Message::store(StorerT &storer) const {
STORE_FLAG(has_local_thread_message_ids);
STORE_FLAG(has_linked_top_thread_message_id);
STORE_FLAG(is_pinned);
STORE_FLAG(has_interaction_info_update_date);
END_STORE_FLAGS();
}
@ -4740,6 +4744,9 @@ void MessagesManager::Message::store(StorerT &storer) const {
if (has_linked_top_thread_message_id) {
store(linked_top_thread_message_id, storer);
}
if (has_interaction_info_update_date) {
store(interaction_info_update_date, storer);
}
store_message_content(content.get(), storer);
if (has_reply_markup) {
store(reply_markup, storer);
@ -4781,6 +4788,7 @@ void MessagesManager::Message::parse(ParserT &parser) {
bool has_thread_draft_message = false;
bool has_local_thread_message_ids = false;
bool has_linked_top_thread_message_id = false;
bool has_interaction_info_update_date = false;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_channel_post);
PARSE_FLAG(is_outgoing);
@ -4838,6 +4846,7 @@ void MessagesManager::Message::parse(ParserT &parser) {
PARSE_FLAG(has_local_thread_message_ids);
PARSE_FLAG(has_linked_top_thread_message_id);
PARSE_FLAG(is_pinned);
PARSE_FLAG(has_interaction_info_update_date);
END_PARSE_FLAGS();
}
@ -4946,6 +4955,9 @@ void MessagesManager::Message::parse(ParserT &parser) {
if (has_linked_top_thread_message_id) {
parse(linked_top_thread_message_id, parser);
}
if (has_interaction_info_update_date) {
parse(interaction_info_update_date, parser);
}
parse_message_content(content, parser);
if (has_reply_markup) {
parse(reply_markup, parser);
@ -5014,6 +5026,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
bool has_pending_read_channel_inbox = pending_read_channel_inbox_pts != 0;
bool has_distance = distance >= 0;
bool has_last_yet_unsent_message = last_message_id.is_valid() && last_message_id.is_yet_unsent();
bool has_active_group_call_id = active_group_call_id.is_valid();
BEGIN_STORE_FLAGS();
STORE_FLAG(has_draft_message);
STORE_FLAG(has_last_database_message);
@ -5069,6 +5082,9 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
STORE_FLAG(has_last_yet_unsent_message);
STORE_FLAG(is_blocked);
STORE_FLAG(is_is_blocked_inited);
STORE_FLAG(has_active_group_call);
STORE_FLAG(is_group_call_empty);
STORE_FLAG(has_active_group_call_id);
END_STORE_FLAGS();
}
@ -5153,6 +5169,9 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
if (has_distance) {
store(distance, storer);
}
if (has_active_group_call_id) {
store(active_group_call_id, storer);
}
}
// do not forget to resolve dialog dependencies including dependencies of last_message
@ -5182,6 +5201,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
bool has_folder_id = false;
bool has_pending_read_channel_inbox = false;
bool has_distance = false;
bool has_active_group_call_id = false;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_draft_message);
PARSE_FLAG(has_last_database_message);
@ -5237,6 +5257,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
PARSE_FLAG(had_last_yet_unsent_message);
PARSE_FLAG(is_blocked);
PARSE_FLAG(is_is_blocked_inited);
PARSE_FLAG(has_active_group_call);
PARSE_FLAG(is_group_call_empty);
PARSE_FLAG(has_active_group_call_id);
END_PARSE_FLAGS();
} else {
is_folder_id_inited = false;
@ -5253,6 +5276,8 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
had_last_yet_unsent_message = false;
is_blocked = false;
is_is_blocked_inited = false;
has_active_group_call = false;
is_group_call_empty = false;
}
parse(last_new_message_id, parser);
@ -5369,6 +5394,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
if (has_distance) {
parse(distance, parser);
}
if (has_active_group_call_id) {
parse(active_group_call_id, parser);
}
}
template <class StorerT>
@ -5830,11 +5858,13 @@ void MessagesManager::update_reply_count_by_message(Dialog *d, int diff, const M
auto replier_dialog_id =
has_message_sender_user_id(d->dialog_id, m) ? DialogId(m->sender_user_id) : m->sender_dialog_id;
update_message_reply_count(d, m->top_thread_message_id, replier_dialog_id, m->message_id, diff);
update_message_reply_count(d, m->top_thread_message_id, replier_dialog_id, m->message_id,
diff < 0 ? G()->unix_time() : m->date, diff);
}
void MessagesManager::update_message_reply_count(Dialog *d, MessageId message_id, DialogId replier_dialog_id,
MessageId reply_message_id, int diff, bool is_recursive) {
MessageId reply_message_id, int32 update_date, int diff,
bool is_recursive) {
if (d == nullptr) {
return;
}
@ -5845,14 +5875,15 @@ void MessagesManager::update_message_reply_count(Dialog *d, MessageId message_id
}
LOG(INFO) << "Update reply count to " << message_id << " in " << d->dialog_id << " by " << diff << " from "
<< reply_message_id << " sent by " << replier_dialog_id;
if (m->reply_info.add_reply(replier_dialog_id, reply_message_id, diff)) {
if (m->interaction_info_update_date < update_date &&
m->reply_info.add_reply(replier_dialog_id, reply_message_id, diff)) {
on_message_reply_info_changed(d->dialog_id, m);
on_message_changed(d, m, true, "update_message_reply_count_by_message");
}
if (!is_recursive && is_discussion_message(d->dialog_id, m)) {
update_message_reply_count(get_dialog(m->forward_info->from_dialog_id), m->forward_info->from_message_id,
replier_dialog_id, reply_message_id, diff, true);
replier_dialog_id, reply_message_id, update_date, diff, true);
}
}
@ -6844,6 +6875,7 @@ bool MessagesManager::update_message_interaction_info(DialogId dialog_id, Messag
int32 forward_count, bool has_reply_info,
MessageReplyInfo &&reply_info) {
CHECK(m != nullptr);
m->interaction_info_update_date = G()->unix_time(); // doesn't force message saving
if (m->message_id.is_valid_scheduled()) {
has_reply_info = false;
}
@ -7110,16 +7142,31 @@ void MessagesManager::on_user_dialog_action(DialogId dialog_id, MessageId top_th
if (td_->auth_manager_->is_bot() || !user_id.is_valid() || is_broadcast_channel(dialog_id)) {
return;
}
if (!have_dialog(dialog_id)) {
LOG(DEBUG) << "Ignore typing in unknown " << dialog_id;
if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) {
LOG(ERROR) << "Ignore typing in the message thread of " << top_thread_message_id;
return;
}
auto dialog_type = dialog_id.get_type();
if (action == DialogAction::get_speaking_action()) {
if ((dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) || top_thread_message_id.is_valid()) {
LOG(ERROR) << "Receive " << action << " in thread of " << top_thread_message_id << " in " << dialog_id;
return;
}
const Dialog *d = get_dialog_force(dialog_id);
if (d != nullptr && d->active_group_call_id.is_valid()) {
auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, dialog_id);
td_->group_call_manager_->on_user_speaking_in_group_call(group_call_id, user_id, date);
}
return;
}
if (!td_->contacts_manager_->have_min_user(user_id)) {
LOG(DEBUG) << "Ignore typing of unknown " << user_id;
return;
}
if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) {
LOG(ERROR) << "Ignore typing in the message thread of " << top_thread_message_id;
if (!have_dialog(dialog_id)) {
LOG(DEBUG) << "Ignore typing in unknown " << dialog_id;
return;
}
@ -7128,7 +7175,6 @@ void MessagesManager::on_user_dialog_action(DialogId dialog_id, MessageId top_th
td_->contacts_manager_->on_update_user_local_was_online(user_id, date);
}
auto dialog_type = dialog_id.get_type();
if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) &&
!get_dialog(dialog_id)->is_opened && !is_canceled) {
@ -8062,6 +8108,30 @@ void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise<Unit>
change_dialog_report_spam_state_on_server(dialog_id, false, 0, std::move(promise));
}
void MessagesManager::repair_dialog_active_group_call_id(DialogId dialog_id) {
if (have_input_peer(dialog_id, AccessRights::Read)) {
create_actor<SleepActor>("RepairChatActiveVoiceChatId", 1.0,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::do_repair_dialog_active_group_call_id,
dialog_id);
}))
.release();
}
}
void MessagesManager::do_repair_dialog_active_group_call_id(DialogId dialog_id) {
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (!d->has_active_group_call || d->active_group_call_id.is_valid()) {
return;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
return;
}
reload_dialog_info_full(dialog_id);
}
class MessagesManager::ChangeDialogReportSpamStateOnServerLogEvent {
public:
DialogId dialog_id_;
@ -19552,6 +19622,8 @@ td_api::object_ptr<td_api::chat> MessagesManager::get_chat_object(const Dialog *
// TODO hide/show draft message when can_send_message(dialog_id) changes
auto draft_message = can_send_message(d->dialog_id).is_ok() ? get_draft_message_object(d->draft_message) : nullptr;
auto active_group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, d->dialog_id);
return make_tl_object<td_api::chat>(
d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id),
get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)),
@ -19562,6 +19634,7 @@ td_api::object_ptr<td_api::chat> MessagesManager::get_chat_object(const Dialog *
d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(),
d->last_read_outbox_message_id.get(), d->unread_mention_count,
get_chat_notification_settings_object(&d->notification_settings), get_chat_action_bar_object(d),
active_group_call_id.get(), active_group_call_id.is_valid() ? d->is_group_call_empty : true,
d->reply_markup_message_id.get(), std::move(draft_message), d->client_data);
}
@ -25889,6 +25962,7 @@ Result<vector<MessageId>> MessagesManager::forward_messages(DialogId to_dialog_i
!(m->message_id.is_scheduled() && is_broadcast_channel(to_dialog_id))) {
m->view_count = forwarded_message->view_count;
m->forward_count = forwarded_message->forward_count;
m->interaction_info_update_date = G()->unix_time();
}
if (is_game) {
@ -28052,6 +28126,20 @@ void MessagesManager::send_update_chat_action_bar(const Dialog *d) {
send_update_secret_chats_with_user_action_bar(d);
}
void MessagesManager::send_update_chat_voice_chat(const Dialog *d) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(d != nullptr);
LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_voice_chat";
on_dialog_updated(d->dialog_id, "send_update_chat_voice_chat");
auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, d->dialog_id);
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updateChatVoiceChat>(d->dialog_id.get(), group_call_id.get(),
d->is_group_call_empty));
}
void MessagesManager::send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion) {
if (td_->auth_manager_->is_bot()) {
return;
@ -29204,6 +29292,71 @@ void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) {
on_dialog_updated(d->dialog_id, "do_set_dialog_folder_id");
}
void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call,
bool is_group_call_empty) {
if (td_->auth_manager_->is_bot()) {
return;
}
CHECK(dialog_id.is_valid());
Dialog *d = get_dialog(dialog_id); // must not create the Dialog, because is called from on_get_chat
if (d == nullptr) {
pending_dialog_group_call_updates_[dialog_id] = {has_active_group_call, is_group_call_empty};
return;
}
if (!has_active_group_call) {
is_group_call_empty = false;
}
if (d->has_active_group_call == has_active_group_call && d->is_group_call_empty == is_group_call_empty) {
return;
}
if (d->has_active_group_call && !has_active_group_call && d->active_group_call_id.is_valid()) {
d->active_group_call_id = InputGroupCallId();
d->has_active_group_call = false;
d->is_group_call_empty = false;
send_update_chat_voice_chat(d);
} else if (d->has_active_group_call && has_active_group_call) {
d->is_group_call_empty = is_group_call_empty;
send_update_chat_voice_chat(d);
} else {
d->has_active_group_call = has_active_group_call;
d->is_group_call_empty = is_group_call_empty;
on_dialog_updated(dialog_id, "on_update_dialog_group_call");
if (has_active_group_call && !d->active_group_call_id.is_valid()) {
repair_dialog_active_group_call_id(dialog_id);
}
}
}
void MessagesManager::on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
auto d = get_dialog_force(dialog_id);
if (d == nullptr) {
// nothing to do
return;
}
if (d->active_group_call_id != input_group_call_id) {
d->active_group_call_id = input_group_call_id;
bool has_active_group_call = input_group_call_id.is_valid();
if (has_active_group_call != d->has_active_group_call) {
LOG(ERROR) << "Receive invalid has_active_group_call flag " << d->has_active_group_call << ", but have "
<< input_group_call_id << " in " << dialog_id;
d->has_active_group_call = has_active_group_call;
if (!has_active_group_call) {
d->is_group_call_empty = false;
}
}
send_update_chat_voice_chat(d);
}
}
void MessagesManager::on_update_dialog_filters() {
if (td_->auth_manager_->is_bot()) {
// just in case
@ -30820,11 +30973,14 @@ tl_object_ptr<telegram_api::channelAdminLogEventsFilter> MessagesManager::get_ch
if (filters->setting_changes_) {
flags |= telegram_api::channelAdminLogEventsFilter::SETTINGS_MASK;
}
if (filters->voice_chat_changes_) {
flags |= telegram_api::channelAdminLogEventsFilter::GROUP_CALL_MASK;
}
return make_tl_object<telegram_api::channelAdminLogEventsFilter>(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
false /*ignored*/, false /*ignored*/, false /*ignored*/);
false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/);
}
int64 MessagesManager::get_dialog_event_log(DialogId dialog_id, const string &query, int64 from_event_id, int32 limit,
@ -31074,6 +31230,46 @@ tl_object_ptr<td_api::ChatEventAction> MessagesManager::get_chat_event_action_ob
auto new_slow_mode_delay = clamp(action->new_value_, 0, 86400 * 366);
return make_tl_object<td_api::chatEventSlowModeDelayChanged>(old_slow_mode_delay, new_slow_mode_delay);
}
case telegram_api::channelAdminLogEventActionStartGroupCall::ID: {
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionStartGroupCall>(action_ptr);
auto input_group_call_id = InputGroupCallId(action->call_);
if (!input_group_call_id.is_valid()) {
return nullptr;
}
return make_tl_object<td_api::chatEventVoiceChatCreated>(
td_->group_call_manager_->get_group_call_id(input_group_call_id, DialogId(channel_id)).get());
}
case telegram_api::channelAdminLogEventActionDiscardGroupCall::ID: {
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionDiscardGroupCall>(action_ptr);
auto input_group_call_id = InputGroupCallId(action->call_);
if (!input_group_call_id.is_valid()) {
return nullptr;
}
return make_tl_object<td_api::chatEventVoiceChatDiscarded>(
td_->group_call_manager_->get_group_call_id(input_group_call_id, DialogId(channel_id)).get());
}
case telegram_api::channelAdminLogEventActionParticipantMute::ID: {
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantMute>(action_ptr);
GroupCallParticipant participant(std::move(action->participant_));
if (!participant.is_valid()) {
return nullptr;
}
return make_tl_object<td_api::chatEventVoiceChatParticipantIsMutedToggled>(
td_->contacts_manager_->get_user_id_object(participant.user_id, "LogEventActionParticipantMute"), true);
}
case telegram_api::channelAdminLogEventActionParticipantUnmute::ID: {
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantUnmute>(action_ptr);
GroupCallParticipant participant(std::move(action->participant_));
if (!participant.is_valid()) {
return nullptr;
}
return make_tl_object<td_api::chatEventVoiceChatParticipantIsMutedToggled>(
td_->contacts_manager_->get_user_id_object(participant.user_id, "LogEventActionParticipantUnmute"), false);
}
case telegram_api::channelAdminLogEventActionToggleGroupCallSetting::ID: {
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleGroupCallSetting>(action_ptr);
return make_tl_object<td_api::chatEventVoiceChatMuteNewParticipantsToggled>(action->join_muted_);
}
default:
UNREACHABLE();
return nullptr;
@ -32095,7 +32291,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
m->forward_info->sender_dialog_id.is_valid() && m->forward_info->message_id.is_valid() &&
(!is_discussion_message(dialog_id, m) || m->forward_info->sender_dialog_id != m->forward_info->from_dialog_id ||
m->forward_info->message_id != m->forward_info->from_message_id)) {
update_forward_count(m->forward_info->sender_dialog_id, m->forward_info->message_id);
update_forward_count(m->forward_info->sender_dialog_id, m->forward_info->message_id, m->date);
}
return result_message;
@ -33306,6 +33502,20 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr<Dialog> &&d,
if (d->mention_notification_group.group_id.is_valid()) {
notification_group_id_to_dialog_id_.emplace(d->mention_notification_group.group_id, d->dialog_id);
}
if (pending_dialog_group_call_updates_.count(dialog_id) > 0) {
auto it = pending_dialog_group_call_updates_.find(dialog_id);
bool has_active_group_call = it->second.first;
bool is_group_call_empty = it->second.second;
pending_dialog_group_call_updates_.erase(it);
if (d->has_active_group_call != has_active_group_call || d->is_group_call_empty != is_group_call_empty) {
if (!has_active_group_call) {
d->active_group_call_id = InputGroupCallId();
}
d->has_active_group_call = has_active_group_call;
d->is_group_call_empty = is_group_call_empty;
on_dialog_updated(dialog_id, "pending update_dialog_group_call");
}
}
if (!is_loaded_from_database) {
CHECK(order == DEFAULT_ORDER);
@ -33371,6 +33581,9 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_datab
// asynchronously get action bar from the server
reget_dialog_action_bar(dialog_id, "fix_new_dialog");
}
if (d->has_active_group_call && !d->active_group_call_id.is_valid()) {
repair_dialog_active_group_call_id(dialog_id);
}
if (d->notification_settings.is_synchronized && !d->notification_settings.is_use_default_fixed &&
have_input_peer(dialog_id, AccessRights::Read) && !td_->auth_manager_->is_bot()) {
@ -35423,7 +35636,7 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) {
}
}
void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message_id) {
void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date) {
CHECK(!td_->auth_manager_->is_bot());
Dialog *d = get_dialog(dialog_id);
if (d == nullptr) {
@ -35431,7 +35644,8 @@ void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message
return;
}
Message *m = get_message_force(d, message_id, "update_forward_count");
if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0) {
if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0 &&
m->interaction_info_update_date < update_date) {
if (m->forward_count == 0) {
m->forward_count++;
send_update_message_interaction_info(dialog_id, m);

View File

@ -25,6 +25,7 @@
#include "td/telegram/FullMessageId.h"
#include "td/telegram/Global.h"
#include "td/telegram/InputDialogId.h"
#include "td/telegram/InputGroupCallId.h"
#include "td/telegram/logevent/LogEventHelper.h"
#include "td/telegram/MessageContentType.h"
#include "td/telegram/MessageCopyOptions.h"
@ -336,6 +337,10 @@ class MessagesManager : public Actor {
void on_update_dialog_folder_id(DialogId dialog_id, FolderId folder_id);
void on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call, bool is_group_call_empty);
void on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id);
void on_update_dialog_filters();
void on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update,
@ -1110,6 +1115,7 @@ class MessagesManager : public Actor {
int32 forward_count = 0;
MessageReplyInfo reply_info;
unique_ptr<DraftMessage> thread_draft_message;
int32 interaction_info_update_date = 0;
int32 legacy_layer = 0;
@ -1195,6 +1201,7 @@ class MessagesManager : public Actor {
std::unordered_map<int64, LogEventIdWithGeneration> read_history_log_event_ids;
std::unordered_set<MessageId, MessageIdHash> updated_read_history_message_ids;
LogEventIdWithGeneration set_folder_id_log_event_id;
InputGroupCallId active_group_call_id;
FolderId folder_id;
vector<DialogListId> dialog_list_ids; // TODO replace with mask
@ -1266,6 +1273,8 @@ class MessagesManager : public Actor {
bool has_loaded_scheduled_messages_from_database = false;
bool sent_scheduled_messages = false;
bool had_last_yet_unsent_message = false; // whether the dialog was stored to database without last message
bool has_active_group_call = false;
bool is_group_call_empty = false;
bool increment_view_counter = false;
@ -2090,7 +2099,7 @@ class MessagesManager : public Actor {
void update_reply_count_by_message(Dialog *d, int diff, const Message *m);
void update_message_reply_count(Dialog *d, MessageId message_id, DialogId replier_dialog_id,
MessageId reply_message_id, int diff, bool is_recursive = false);
MessageId reply_message_id, int32 update_date, int diff, bool is_recursive = false);
Message *add_message_to_dialog(DialogId dialog_id, unique_ptr<Message> message, bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source);
@ -2247,6 +2256,8 @@ class MessagesManager : public Actor {
void send_update_chat_action_bar(const Dialog *d);
void send_update_chat_voice_chat(const Dialog *d);
void send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion);
void send_update_user_chat_action(DialogId dialog_id, MessageId top_thread_message_id, UserId user_id,
@ -2256,6 +2267,10 @@ class MessagesManager : public Actor {
void hide_dialog_action_bar(Dialog *d);
void repair_dialog_active_group_call_id(DialogId dialog_id);
void do_repair_dialog_active_group_call_id(DialogId dialog_id);
static Result<int32> get_message_schedule_date(td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state);
tl_object_ptr<td_api::MessageSendingState> get_message_sending_state_object(const Message *m) const;
@ -2895,7 +2910,7 @@ class MessagesManager : public Actor {
void update_top_dialogs(DialogId dialog_id, const Message *m);
void update_forward_count(DialogId dialog_id, MessageId message_id);
void update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date);
void try_hide_distance(DialogId dialog_id, const Message *m);
@ -3281,6 +3296,8 @@ class MessagesManager : public Actor {
std::unordered_map<DialogId, OnlineMemberCountInfo, DialogIdHash> dialog_online_member_counts_;
std::unordered_map<DialogId, std::pair<bool, bool>, DialogIdHash> pending_dialog_group_call_updates_;
std::unordered_map<string, int32> auth_notification_id_date_;
std::unordered_map<DialogId, MessageId, DialogIdHash> previous_repaired_read_inbox_max_message_id_;

View File

@ -1164,7 +1164,7 @@ void StickersManager::init() {
}
dice_emojis_str_ =
G()->shared_config().get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01\x01⚽️\x01🎰");
G()->shared_config().get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01\x01⚽️\x01🎰\x01🎳");
dice_emojis_ = full_split(dice_emojis_str_, '\x01');
for (auto &dice_emoji : dice_emojis_) {
auto &animated_dice_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(dice_emoji));
@ -1336,22 +1336,15 @@ tl_object_ptr<td_api::MaskPoint> StickersManager::get_mask_point_object(int32 po
}
}
string StickersManager::get_sticker_minithumbnail(const string &path) {
vector<td_api::object_ptr<td_api::closedVectorPath>> StickersManager::get_sticker_minithumbnail(CSlice path) {
if (path.empty()) {
return string();
return {};
}
const auto prefix = Slice(
"<?xml version=\"1.0\" encoding=\"utf-8\"?><svg version=\"1.1\" id=\"Layer_1\" "
"xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" "
"viewBox=\"0 0 512 512\" style=\"enable-background:new 0 0 512 512;\" xml:space=\"preserve\"><style "
"type=\"text/css\">.st0{fill:#E8E8E8;}</style><path class=\"st0\" d=\"M");
const auto suffix = Slice("z\"/></svg>");
auto buf = StackAllocator::alloc(1 << 7);
auto buf = StackAllocator::alloc(1 << 9);
StringBuilder sb(buf.as_slice(), true);
sb << prefix;
sb << 'M';
for (unsigned char c : path) {
if (c >= 128 + 64) {
sb << "AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64];
@ -1364,10 +1357,214 @@ string StickersManager::get_sticker_minithumbnail(const string &path) {
sb << (c & 63);
}
}
sb << suffix;
sb << 'z';
CHECK(!sb.is_error());
return sb.as_cslice().str();
path = sb.as_cslice();
LOG(DEBUG) << "Transform SVG path " << path;
size_t pos = 0;
auto skip_commas = [&path, &pos] {
while (path[pos] == ',') {
pos++;
}
};
auto get_number = [&] {
skip_commas();
int sign = 1;
if (path[pos] == '-') {
sign = -1;
pos++;
}
double res = 0;
while (is_digit(path[pos])) {
res = res * 10 + path[pos++] - '0';
}
if (path[pos] == '.') {
pos++;
double mul = 0.1;
while (is_digit(path[pos])) {
res += (path[pos] - '0') * mul;
mul *= 0.1;
pos++;
}
}
return sign * res;
};
auto make_point = [](double x, double y) {
return td_api::make_object<td_api::point>(x, y);
};
vector<td_api::object_ptr<td_api::closedVectorPath>> result;
double x = 0;
double y = 0;
while (path[pos] != '\0') {
skip_commas();
if (path[pos] == '\0') {
break;
}
while (path[pos] == 'm' || path[pos] == 'M') {
auto command = path[pos++];
do {
if (command == 'm') {
x += get_number();
y += get_number();
} else {
x = get_number();
y = get_number();
}
skip_commas();
} while (path[pos] != '\0' && !is_alpha(path[pos]));
}
double start_x = x;
double start_y = y;
vector<td_api::object_ptr<td_api::VectorPathCommand>> commands;
bool have_last_end_control_point = false;
double last_end_control_point_x = 0;
double last_end_control_point_y = 0;
bool is_closed = false;
char command = '-';
while (!is_closed) {
skip_commas();
if (path[pos] == '\0') {
LOG(ERROR) << "Receive unclosed path " << path;
return {};
}
if (is_alpha(path[pos])) {
command = path[pos++];
}
switch (command) {
case 'l':
case 'L':
case 'h':
case 'H':
case 'v':
case 'V':
if (command == 'l' || command == 'h') {
x += get_number();
} else if (command == 'L' || command == 'H') {
x = get_number();
}
if (command == 'l' || command == 'v') {
y += get_number();
} else if (command == 'L' || command == 'V') {
y = get_number();
}
commands.push_back(td_api::make_object<td_api::vectorPathCommandLine>(make_point(x, y)));
have_last_end_control_point = false;
break;
case 'C':
case 'c':
case 'S':
case 's': {
double start_control_point_x;
double start_control_point_y;
if (command == 'S' || command == 's') {
if (have_last_end_control_point) {
start_control_point_x = 2 * x - last_end_control_point_x;
start_control_point_y = 2 * y - last_end_control_point_y;
} else {
start_control_point_x = x;
start_control_point_y = y;
}
} else {
start_control_point_x = get_number();
start_control_point_y = get_number();
if (command == 'c') {
start_control_point_x += x;
start_control_point_y += y;
}
}
last_end_control_point_x = get_number();
last_end_control_point_y = get_number();
if (command == 'c' || command == 's') {
last_end_control_point_x += x;
last_end_control_point_y += y;
}
have_last_end_control_point = true;
if (command == 'c' || command == 's') {
x += get_number();
y += get_number();
} else {
x = get_number();
y = get_number();
}
commands.push_back(td_api::make_object<td_api::vectorPathCommandCubicBezierCurve>(
make_point(start_control_point_x, start_control_point_y),
make_point(last_end_control_point_x, last_end_control_point_y), make_point(x, y)));
break;
}
case 'z':
case 'Z':
if (x != start_x || y != start_y) {
x = start_x;
y = start_y;
commands.push_back(td_api::make_object<td_api::vectorPathCommandLine>(make_point(x, y)));
}
if (!commands.empty()) {
result.push_back(td_api::make_object<td_api::closedVectorPath>(std::move(commands)));
}
is_closed = true;
break;
default:
LOG(ERROR) << "Receive invalid command " << command << " at pos " << pos << " in " << path;
return {};
}
}
}
/*
string svg;
for (const auto &vector_path : result) {
CHECK(!vector_path->commands_.empty());
svg += 'M';
auto add_point = [&](const td_api::object_ptr<td_api::point> &p) {
svg += to_string(static_cast<int>(p->x_));
svg += ',';
svg += to_string(static_cast<int>(p->y_));
svg += ',';
};
auto last_command = vector_path->commands_.back().get();
switch (last_command->get_id()) {
case td_api::vectorPathCommandLine::ID:
add_point(static_cast<const td_api::vectorPathCommandLine *>(last_command)->end_point_);
break;
case td_api::vectorPathCommandCubicBezierCurve::ID:
add_point(static_cast<const td_api::vectorPathCommandCubicBezierCurve *>(last_command)->end_point_);
break;
default:
UNREACHABLE();
}
for (auto &command : vector_path->commands_) {
switch (command->get_id()) {
case td_api::vectorPathCommandLine::ID: {
auto line = static_cast<const td_api::vectorPathCommandLine *>(command.get());
svg += 'L';
add_point(line->end_point_);
break;
}
case td_api::vectorPathCommandCubicBezierCurve::ID: {
auto curve = static_cast<const td_api::vectorPathCommandCubicBezierCurve *>(command.get());
svg += 'C';
add_point(curve->start_control_point_);
add_point(curve->end_control_point_);
add_point(curve->end_point_);
break;
}
default:
UNREACHABLE();
}
}
svg += 'z';
}
*/
return result;
}
tl_object_ptr<td_api::sticker> StickersManager::get_sticker_object(FileId file_id) const {
@ -1513,8 +1710,9 @@ tl_object_ptr<td_api::stickerSet> StickersManager::get_sticker_set_object(Sticke
sticker_set->is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp);
return make_tl_object<td_api::stickerSet>(
sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail),
sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official,
sticker_set->is_animated, sticker_set->is_masks, sticker_set->is_viewed, std::move(stickers), std::move(emojis));
get_sticker_minithumbnail(sticker_set->minithumbnail), sticker_set->is_installed && !sticker_set->is_archived,
sticker_set->is_archived, sticker_set->is_official, sticker_set->is_animated, sticker_set->is_masks,
sticker_set->is_viewed, std::move(stickers), std::move(emojis));
}
tl_object_ptr<td_api::stickerSets> StickersManager::get_sticker_sets_object(int32 total_count,
@ -1558,8 +1756,9 @@ tl_object_ptr<td_api::stickerSetInfo> StickersManager::get_sticker_set_info_obje
sticker_set->is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp);
return make_tl_object<td_api::stickerSetInfo>(
sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail),
sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official,
sticker_set->is_animated, sticker_set->is_masks, sticker_set->is_viewed,
get_sticker_minithumbnail(sticker_set->minithumbnail), sticker_set->is_installed && !sticker_set->is_archived,
sticker_set->is_archived, sticker_set->is_official, sticker_set->is_animated, sticker_set->is_masks,
sticker_set->is_viewed,
sticker_set->was_loaded ? narrow_cast<int32>(sticker_set->sticker_ids.size()) : sticker_set->sticker_count,
std::move(stickers));
}
@ -2231,9 +2430,9 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
PhotoSize thumbnail;
string minithumbnail;
if (set->thumb_ != nullptr) {
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(set->thumb_),
DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb),
is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp, false);
if (photo_size.get_offset() == 0) {
if (!thumbnail.file_id.is_valid()) {
@ -3562,7 +3761,7 @@ void StickersManager::on_update_dice_emojis() {
}
auto dice_emojis_str =
G()->shared_config().get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01\x01⚽️\x01🎰");
G()->shared_config().get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01\x01⚽️\x01🎰\x01🎳");
if (dice_emojis_str == dice_emojis_str_) {
return;
}
@ -3597,7 +3796,7 @@ void StickersManager::on_update_dice_success_values() {
}
auto dice_success_values_str =
G()->shared_config().get_option_string("dice_success_values", "0,6:62,5:110,5:110,5:110,64:110");
G()->shared_config().get_option_string("dice_success_values", "0,6:62,5:110,5:110,5:110,64:110,6:110");
if (dice_success_values_str == dice_success_values_str_) {
return;
}

View File

@ -397,7 +397,7 @@ class StickersManager : public Actor {
class UploadStickerFileCallback;
static string get_sticker_minithumbnail(const string &path);
static vector<td_api::object_ptr<td_api::closedVectorPath>> get_sticker_minithumbnail(CSlice path);
static tl_object_ptr<td_api::MaskPoint> get_mask_point_object(int32 point);

View File

@ -6067,34 +6067,87 @@ void Td::on_request(uint64 id, td_api::sendCallDebugInformation &request) {
std::move(request.debug_information_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::createChatGroupCall &request) {
void Td::on_request(uint64 id, const td_api::createVoiceChat &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<InputGroupCallId> result) mutable {
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<GroupCallId> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(td_api::make_object<td_api::groupCallId>(result.ok().get_group_call_id()));
promise.set_value(td_api::make_object<td_api::groupCallId>(result.ok().get()));
}
});
group_call_manager_->create_voice_chat(DialogId(request.chat_id_), std::move(query_promise));
}
contacts_manager_->create_channel_group_call(DialogId(request.chat_id_), std::move(query_promise));
void Td::on_request(uint64 id, const td_api::getGroupCall &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
group_call_manager_->get_group_call(GroupCallId(request.group_call_id_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::joinGroupCall &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
group_call_manager_->join_group_call(GroupCallId(request.group_call_id_), std::move(request.payload_),
request.source_, request.is_muted_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::toggleGroupCallMuteNewParticipants &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->toggle_group_call_mute_new_participants(GroupCallId(request.group_call_id_),
request.mute_new_participants_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::inviteGroupCallParticipants &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
vector<UserId> user_ids;
for (auto &user_id : request.user_ids_) {
user_ids.emplace_back(user_id);
}
group_call_manager_->invite_group_call_participants(GroupCallId(request.group_call_id_), std::move(user_ids),
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));
}
void Td::on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->toggle_group_call_participant_is_muted(
GroupCallId(request.group_call_id_), UserId(request.user_id_), request.is_muted_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::checkGroupCallIsJoined &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->check_group_call_is_joined(GroupCallId(request.group_call_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::loadGroupCallParticipants &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
group_call_manager_->load_group_call_participants(GroupCallId(request.group_call_id_), request.limit_,
std::move(promise));
}
void Td::on_request(uint64 id, const td_api::leaveGroupCall &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
TRY_RESULT_PROMISE(promise, group_call_id, InputGroupCallId::from_group_call_id(request.group_call_id_));
group_call_manager_->leave_group_call(group_call_id, request.source_, std::move(promise));
group_call_manager_->leave_group_call(GroupCallId(request.group_call_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::discardGroupCall &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
TRY_RESULT_PROMISE(promise, group_call_id, InputGroupCallId::from_group_call_id(request.group_call_id_));
group_call_manager_->discard_group_call(group_call_id, std::move(promise));
group_call_manager_->discard_group_call(GroupCallId(request.group_call_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::upgradeBasicGroupChatToSupergroupChat &request) {

View File

@ -705,7 +705,23 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::sendCallDebugInformation &request);
void on_request(uint64 id, const td_api::createChatGroupCall &request);
void on_request(uint64 id, const td_api::createVoiceChat &request);
void on_request(uint64 id, const td_api::getGroupCall &request);
void on_request(uint64 id, td_api::joinGroupCall &request);
void on_request(uint64 id, const td_api::toggleGroupCallMuteNewParticipants &request);
void on_request(uint64 id, const td_api::inviteGroupCallParticipants &request);
void on_request(uint64 id, const td_api::setGroupCallParticipantIsSpeaking &request);
void on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request);
void on_request(uint64 id, const td_api::checkGroupCallIsJoined &request);
void on_request(uint64 id, const td_api::loadGroupCallParticipants &request);
void on_request(uint64 id, const td_api::leaveGroupCall &request);

View File

@ -618,9 +618,11 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
}
case telegram_api::messageActionInviteToGroupCall::ID: {
auto invite_to_group_call = static_cast<const telegram_api::messageActionInviteToGroupCall *>(action);
if (!is_acceptable_user(UserId(invite_to_group_call->user_id_))) {
for (auto &user : invite_to_group_call->users_) {
if (!is_acceptable_user(UserId(user))) {
return false;
}
}
break;
}
default:
@ -902,27 +904,25 @@ vector<const tl_object_ptr<telegram_api::Message> *> UpdatesManager::get_new_mes
}
vector<InputGroupCallId> UpdatesManager::get_update_new_group_call_ids(const telegram_api::Updates *updates_ptr) {
vector<InputGroupCallId> group_call_ids;
vector<InputGroupCallId> input_group_call_ids;
auto updates = get_updates(updates_ptr);
if (updates != nullptr) {
for (auto &update : *updates) {
InputGroupCallId group_call_id;
InputGroupCallId input_group_call_id;
if (update->get_id() == telegram_api::updateGroupCall::ID) {
auto group_call_ptr = static_cast<const telegram_api::updateGroupCall *>(update.get())->call_.get();
if (group_call_ptr->get_id() == telegram_api::groupCall::ID) {
auto group_call = static_cast<const telegram_api::groupCall *>(group_call_ptr);
group_call_id = InputGroupCallId(group_call->id_, group_call->access_hash_);
input_group_call_id = InputGroupCallId(group_call->id_, group_call->access_hash_);
}
}
if (group_call_id.is_valid()) {
group_call_ids.push_back(group_call_id);
} else {
LOG(ERROR) << "Receive unexpected " << to_string(update);
if (input_group_call_id.is_valid()) {
input_group_call_ids.push_back(input_group_call_id);
}
}
}
return group_call_ids;
return input_group_call_ids;
}
vector<DialogId> UpdatesManager::get_update_notify_settings_dialog_ids(const telegram_api::Updates *updates_ptr) {
@ -1704,6 +1704,10 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateServiceNotifica
td_->messages_manager_->on_update_service_notification(std::move(update), true, Promise<Unit>());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChat> update, bool /*force_apply*/) {
// nothing to do
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelInbox> update, bool /*force_apply*/) {
CHECK(update != nullptr);
td_->messages_manager_->on_update_read_channel_inbox(std::move(update));
@ -2018,7 +2022,7 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDcOptions> upda
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotInlineQuery> update, bool /*force_apply*/) {
td_->inline_queries_manager_->on_new_query(update->query_id_, UserId(update->user_id_), Location(update->geo_),
update->query_, update->offset_);
std::move(update->peer_type_), update->query_, update->offset_);
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotInlineSend> update, bool /*force_apply*/) {
@ -2148,7 +2152,19 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePhoneCallSignal
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCall> update, bool /*force_apply*/) {
send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call, std::move(update->call_));
DialogId dialog_id(ChatId(update->chat_id_));
if (!td_->messages_manager_->have_dialog(dialog_id)) {
dialog_id = DialogId(ChannelId(update->chat_id_));
if (!td_->messages_manager_->have_dialog(dialog_id)) {
dialog_id = DialogId();
}
}
send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call, std::move(update->call_), dialog_id);
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update, bool /*force_apply*/) {
send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call_participants,
InputGroupCallId(update->call_), std::move(update->participants_), update->version_);
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateContactsReset> update, bool /*force_apply*/) {
@ -2210,7 +2226,4 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelParticip
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateTheme> update, bool /*force_apply*/) {
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update, bool /*force_apply*/) {
}
} // namespace td

View File

@ -238,6 +238,8 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updateDcOptions> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateChat> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateReadChannelInbox> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateReadChannelOutbox> update, bool /*force_apply*/);
@ -245,16 +247,16 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updateChannelTooLong> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updateChannel> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updateEditChannelMessage> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateDeleteChannelMessages> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updateChannelMessageViews> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updateChannelMessageForwards> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updateDeleteChannelMessages> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateChannelMessageViews> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateChannelMessageForwards> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateChannelAvailableMessages> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateReadChannelDiscussionInbox> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateReadChannelDiscussionOutbox> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updatePinnedMessages> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updatePinnedChannelMessages> update, bool force_apply);
void on_update(tl_object_ptr<telegram_api::updatePinnedChannelMessages> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateDraftMessage> update, bool /*force_apply*/);
@ -302,6 +304,7 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updatePhoneCallSignalingData> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateGroupCall> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateContactsReset> update, bool /*force_apply*/);
@ -323,8 +326,6 @@ class UpdatesManager : public Actor {
// unsupported updates
void on_update(tl_object_ptr<telegram_api::updateTheme> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update, bool /*force_apply*/);
};
} // namespace td

View File

@ -633,6 +633,10 @@ class CliClient final : public Actor {
return to_integer<int32>(trim(std::move(str)));
}
static int32 as_group_call_id(string str) {
return to_integer<int32>(trim(std::move(str)));
}
static int32 as_proxy_id(string str) {
return to_integer<int32>(trim(std::move(str)));
}
@ -1196,9 +1200,6 @@ class CliClient final : public Actor {
if (action == "uvn" || action == "upload_video_note") {
return td_api::make_object<td_api::chatActionUploadingVideoNote>(50);
}
if (action == "sic" || action == "speaking_in_call") {
return td_api::make_object<td_api::chatActionSpeakingInCall>();
}
return td_api::make_object<td_api::chatActionTyping>();
}
@ -2837,12 +2838,56 @@ class CliClient final : public Actor {
as_call_id(call_id), to_integer<int32>(rating), "Wow, such good call! (TDLib test)", std::move(problems)));
} else if (op == "scdi" || op == "SendCallDebugInformation") {
send_request(td_api::make_object<td_api::sendCallDebugInformation>(as_call_id(args), "{}"));
} else if (op == "ccgc") {
send_request(td_api::make_object<td_api::createChatGroupCall>(as_chat_id(args)));
} else if (op == "cvc") {
send_request(td_api::make_object<td_api::createVoiceChat>(as_chat_id(args)));
} else if (op == "ggc") {
send_request(td_api::make_object<td_api::getGroupCall>(as_group_call_id(args)));
} else if (op == "jgc") {
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(args),
td_api::make_object<td_api::groupCallPayload>("ufrag", "pwd", std::move(fingerprints)), 123, true));
} else if (op == "jgcc") {
send_request(td_api::make_object<td_api::joinGroupCall>(as_group_call_id(args), nullptr, 123, true));
} else if (op == "tgcmnp" || op == "tgcmnpe") {
send_request(
td_api::make_object<td_api::toggleGroupCallMuteNewParticipants>(as_group_call_id(args), op == "tgcmnme"));
} else if (op == "sgcpis") {
string group_call_id;
string source;
string is_speaking;
std::tie(group_call_id, args) = split(args);
std::tie(source, is_speaking) = split(args);
send_request(td_api::make_object<td_api::setGroupCallParticipantIsSpeaking>(
as_group_call_id(group_call_id), to_integer<int32>(source), as_bool(is_speaking)));
} else if (op == "igcp") {
string group_call_id;
string user_ids;
std::tie(group_call_id, user_ids) = split(args);
send_request(td_api::make_object<td_api::inviteGroupCallParticipants>(as_group_call_id(group_call_id),
as_user_ids(user_ids)));
} else if (op == "tgcpim") {
string group_call_id;
string user_id;
string is_muted;
std::tie(group_call_id, args) = split(args);
std::tie(user_id, is_muted) = split(args);
send_request(td_api::make_object<td_api::toggleGroupCallParticipantIsMuted>(
as_group_call_id(group_call_id), as_user_id(user_id), as_bool(is_muted)));
} else if (op == "cgcij") {
send_request(td_api::make_object<td_api::checkGroupCallIsJoined>(as_group_call_id(args)));
} else if (op == "lgcp") {
string group_call_id;
string limit;
std::tie(group_call_id, limit) = split(args);
send_request(td_api::make_object<td_api::loadGroupCallParticipants>(as_group_call_id(group_call_id),
to_integer<int32>(limit)));
} else if (op == "lgc") {
send_request(td_api::make_object<td_api::leaveGroupCall>(args, 123));
send_request(td_api::make_object<td_api::leaveGroupCall>(as_group_call_id(args)));
} else if (op == "dgc") {
send_request(td_api::make_object<td_api::discardGroupCall>(args));
send_request(td_api::make_object<td_api::discardGroupCall>(as_group_call_id(args)));
} else if (op == "gcil") {
send_request(td_api::make_object<td_api::generateChatInviteLink>(as_chat_id(args)));
} else if (op == "ccil") {
@ -3838,6 +3883,8 @@ class CliClient final : public Actor {
status = td_api::make_object<td_api::chatMemberStatusBanned>(std::numeric_limits<int32>::max());
} else if (status_str == "creator") {
status = td_api::make_object<td_api::chatMemberStatusCreator>("", false, true);
} else if (status_str == "creatortitle") {
status = td_api::make_object<td_api::chatMemberStatusCreator>("owner", false, true);
} else if (status_str == "creatoranon") {
status = td_api::make_object<td_api::chatMemberStatusCreator>("", true, true);
} else if (status_str == "uncreator") {

View File

@ -43,6 +43,15 @@ class PromiseInterface {
set_error(result.move_as_error());
}
}
void operator()(T &&value) {
set_value(std::move(value));
}
void operator()(Status &&error) {
set_error(std::move(error));
}
void operator()(Result<T> &&result) {
set_result(std::move(result));
}
virtual bool is_cancellable() const {
return false;
}
@ -56,10 +65,184 @@ class PromiseInterface {
}
};
namespace detail {
template <typename T>
struct GetArg : public GetArg<decltype(&T::operator())> {};
template <class C, class R, class Arg>
class GetArg<R (C::*)(Arg)> {
public:
using type = Arg;
};
template <class C, class R, class Arg>
class GetArg<R (C::*)(Arg) const> {
public:
using type = Arg;
};
template <class T>
using get_arg_t = std::decay_t<typename GetArg<T>::type>;
template <class T>
struct DropResult {
using type = T;
};
template <class T>
struct DropResult<Result<T>> {
using type = T;
};
template <class T>
using drop_result_t = typename DropResult<T>::type;
struct Ignore {
void operator()(Status &&error) {
error.ignore();
}
};
template <class ValueT, class FunctionOkT, class FunctionFailT = Ignore>
class LambdaPromise : public PromiseInterface<ValueT> {
enum class OnFail { None, Ok, Fail };
public:
void set_value(ValueT &&value) override {
CHECK(has_lambda_.get());
do_ok(ok_, std::move(value));
on_fail_ = OnFail::None;
}
void set_error(Status &&error) override {
CHECK(has_lambda_.get());
do_error(std::move(error));
}
LambdaPromise(const LambdaPromise &other) = delete;
LambdaPromise &operator=(const LambdaPromise &other) = delete;
LambdaPromise(LambdaPromise &&other) = default;
LambdaPromise &operator=(LambdaPromise &&other) = default;
~LambdaPromise() override {
if (has_lambda_.get()) {
do_error(Status::Error("Lost promise"));
}
}
template <class FromOkT, class FromFailT>
LambdaPromise(FromOkT &&ok, FromFailT &&fail, bool use_ok_as_fail)
: ok_(std::forward<FromOkT>(ok))
, fail_(std::forward<FromFailT>(fail))
, on_fail_(use_ok_as_fail ? OnFail::Ok : OnFail::Fail)
, has_lambda_(true) {
}
template <class FromOkT>
LambdaPromise(FromOkT &&ok) : LambdaPromise(std::move(ok), Ignore(), true) {
}
private:
FunctionOkT ok_;
FunctionFailT fail_;
OnFail on_fail_ = OnFail::None;
MovableValue<bool> has_lambda_{false};
void do_error(Status &&error) {
switch (on_fail_) {
case OnFail::None:
break;
case OnFail::Ok:
do_error(ok_, std::move(error));
break;
case OnFail::Fail:
do_error(fail_, std::move(error));
break;
}
on_fail_ = OnFail::None;
}
template <class F>
std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_error(F &&f, Status &&status) {
f(Result<ValueT>(std::move(status)));
}
template <class Y, class F>
std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_error(F &&f, Y &&status) {
f(Auto());
}
template <class F>
std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_ok(F &&f, ValueT &&result) {
f(Result<ValueT>(std::move(result)));
}
template <class F>
std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_ok(F &&f, ValueT &&result) {
f(std::move(result));
}
};
} // namespace detail
template <class T>
class SafePromise;
template <class T = Unit>
class Promise;
constexpr std::false_type is_promise_interface(...) {
return {};
}
template <class T>
constexpr std::true_type is_promise_interface(const PromiseInterface<T> &promise) {
return {};
}
template <class T>
constexpr std::true_type is_promise_interface(const Promise<T> &promise) {
return {};
}
template <class F>
constexpr bool is_promise_interface() {
return decltype(is_promise_interface(std::declval<F>()))::value;
}
constexpr std::false_type is_promise_interface_ptr(...) {
return {};
}
template <class T>
constexpr std::true_type is_promise_interface_ptr(const unique_ptr<T> &promise) {
return {};
}
template <class F>
constexpr bool is_promise_interface_ptr() {
return decltype(is_promise_interface_ptr(std::declval<F>()))::value;
}
template <class T = void, class F = void, std::enable_if_t<std::is_same<T, void>::value, bool> has_t = false>
auto lambda_promise(F &&f) {
return detail::LambdaPromise<detail::drop_result_t<detail::get_arg_t<std::decay_t<F>>>, std::decay_t<F>>(
std::forward<F>(f));
}
template <class T = void, class F = void, std::enable_if_t<!std::is_same<T, void>::value, bool> has_t = true>
auto lambda_promise(F &&f) {
return detail::LambdaPromise<T, std::decay_t<F>>(std::forward<F>(f));
}
template <class T, class F, std::enable_if_t<is_promise_interface<F>(), bool> from_promise_interface = true>
auto &&promise_interface(F &&f) {
return std::forward<F>(f);
}
template <class T, class F, std::enable_if_t<!is_promise_interface<F>(), bool> from_promise_interface = false>
auto promise_interface(F &&f) {
return lambda_promise<T>(std::forward<F>(f));
}
template <class T, class F, std::enable_if_t<is_promise_interface_ptr<F>(), bool> from_promise_interface = true>
auto promise_interface_ptr(F &&f) {
return std::forward<F>(f);
}
template <class T, class F, std::enable_if_t<!is_promise_interface_ptr<F>(), bool> from_promise_interface = false>
auto promise_interface_ptr(F &&f) {
return td::make_unique<std::decay_t<decltype(promise_interface<T>(std::forward<F>(f)))>>(
promise_interface<T>(std::forward<F>(f)));
}
template <class T>
class Promise {
public:
void set_value(T &&value) {
@ -83,6 +266,14 @@ class Promise {
promise_->set_result(std::move(result));
promise_.reset();
}
template <class S>
void operator()(S &&result) {
if (!promise_) {
return;
}
promise_->operator()(std::forward<S>(result));
promise_.reset();
}
void reset() {
promise_.reset();
}
@ -117,8 +308,13 @@ class Promise {
Promise() = default;
explicit Promise(unique_ptr<PromiseInterface<T>> promise) : promise_(std::move(promise)) {
}
Promise(Auto) {
}
Promise(SafePromise<T> &&other);
Promise &operator=(SafePromise<T> &&other);
template <class F>
Promise(F &&f) : promise_(promise_interface_ptr<T>(std::forward<F>(f))) {
}
explicit operator bool() {
return static_cast<bool>(promise_);
@ -209,36 +405,6 @@ class EventPromise : public PromiseInterface<Unit> {
}
};
template <typename T>
struct GetArg : public GetArg<decltype(&T::operator())> {};
template <class C, class R, class Arg>
class GetArg<R (C::*)(Arg)> {
public:
using type = Arg;
};
template <class C, class R, class Arg>
class GetArg<R (C::*)(Arg) const> {
public:
using type = Arg;
};
template <class T>
using get_arg_t = std::decay_t<typename GetArg<T>::type>;
template <class T>
struct DropResult {
using type = T;
};
template <class T>
struct DropResult<Result<T>> {
using type = T;
};
template <class T>
using drop_result_t = typename DropResult<T>::type;
template <class PromiseT>
class CancellablePromise : public PromiseT {
public:
@ -257,63 +423,6 @@ class CancellablePromise : public PromiseT {
CancellationToken cancellation_token_;
};
template <class ValueT, class FunctionOkT, class FunctionFailT>
class LambdaPromise : public PromiseInterface<ValueT> {
enum class OnFail { None, Ok, Fail };
public:
void set_value(ValueT &&value) override {
ok_(std::move(value));
on_fail_ = OnFail::None;
}
void set_error(Status &&error) override {
do_error(std::move(error));
}
LambdaPromise(const LambdaPromise &other) = delete;
LambdaPromise &operator=(const LambdaPromise &other) = delete;
LambdaPromise(LambdaPromise &&other) = delete;
LambdaPromise &operator=(LambdaPromise &&other) = delete;
~LambdaPromise() override {
do_error(Status::Error("Lost promise"));
}
template <class FromOkT, class FromFailT>
LambdaPromise(FromOkT &&ok, FromFailT &&fail, bool use_ok_as_fail)
: ok_(std::forward<FromOkT>(ok))
, fail_(std::forward<FromFailT>(fail))
, on_fail_(use_ok_as_fail ? OnFail::Ok : OnFail::Fail) {
}
private:
FunctionOkT ok_;
FunctionFailT fail_;
OnFail on_fail_ = OnFail::None;
template <class FuncT, class ArgT = detail::get_arg_t<FuncT>>
std::enable_if_t<std::is_assignable<ArgT, Status>::value> do_error_impl(FuncT &func, Status &&status) {
func(std::move(status));
}
template <class FuncT, class ArgT = detail::get_arg_t<FuncT>>
std::enable_if_t<!std::is_assignable<ArgT, Status>::value> do_error_impl(FuncT &func, Status &&status) {
func(Auto());
}
void do_error(Status &&error) {
switch (on_fail_) {
case OnFail::None:
break;
case OnFail::Ok:
do_error_impl(ok_, std::move(error));
break;
case OnFail::Fail:
fail_(std::move(error));
break;
}
on_fail_ = OnFail::None;
}
};
template <class... ArgsT>
class JoinPromise : public PromiseInterface<Unit> {
public:
@ -331,6 +440,30 @@ class JoinPromise : public PromiseInterface<Unit> {
};
} // namespace detail
class SendClosure {
public:
template <class... ArgsT>
void operator()(ArgsT &&... args) const {
send_closure(std::forward<ArgsT>(args)...);
}
};
//template <class T>
//template <class... ArgsT>
//auto Promise<T>::send_closure(ArgsT &&... args) {
// return [promise = std::move(*this), t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&r_res) mutable {
// TRY_RESULT_PROMISE(promise, res, std::move(r_res));
// td2::call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res), std::move(promise))));
// };
//}
template <class... ArgsT>
auto promise_send_closure(ArgsT &&... args) {
return [t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&res) mutable {
call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res))));
};
}
/*** FutureActor and PromiseActor ***/
template <class T>
class FutureActor;
@ -559,16 +692,11 @@ FutureActor<T> send_promise(ActorId<ActorAT> actor_id, ResultT (ActorBT::*func)(
class PromiseCreator {
public:
struct Ignore {
void operator()(Status &&error) {
error.ignore();
}
};
using Ignore = detail::Ignore;
template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
static Promise<ArgT> lambda(OkT &&ok) {
return Promise<ArgT>(
td::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>, Ignore>>(std::forward<OkT>(ok), Ignore(), true));
return Promise<ArgT>(td::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>(std::forward<OkT>(ok)));
}
template <class OkT, class FailT, class ArgT = detail::get_arg_t<OkT>>
@ -579,9 +707,8 @@ class PromiseCreator {
template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
static auto cancellable_lambda(CancellationToken cancellation_token, OkT &&ok) {
return Promise<ArgT>(
td::make_unique<detail::CancellablePromise<detail::LambdaPromise<ArgT, std::decay_t<OkT>, Ignore>>>(
std::move(cancellation_token), std::forward<OkT>(ok), Ignore(), true));
return Promise<ArgT>(td::make_unique<detail::CancellablePromise<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>>(
std::move(cancellation_token), std::forward<OkT>(ok)));
}
static Promise<> event(EventFull &&ok) {

View File

@ -33,16 +33,19 @@ set(TESTS_MAIN
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
#Tests
add_executable(test-tdutils EXCLUDE_FROM_ALL ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE})
add_executable(test-online EXCLUDE_FROM_ALL online.cpp)
add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN AND NOT (CMAKE_HOST_SYSTEM_NAME MATCHES "OpenBSD"))
target_compile_options(test-tdutils PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
target_compile_options(run_all_tests PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
target_compile_options(test-online PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize=vptr")
endif()
target_include_directories(run_all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_include_directories(test-tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_link_libraries(test-tdutils PRIVATE tdutils)
target_link_libraries(run_all_tests PRIVATE tdcore tdclient)
target_link_libraries(test-online PRIVATE tdcore tdclient tdutils tdactor)
if (CLANG)
# add_executable(fuzz_url fuzz_url.cpp)

628
test/online.cpp Normal file
View File

@ -0,0 +1,628 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <tdutils/td/utils/FileLog.h>
#include <tdutils/td/utils/OptionParser.h>
#include <tdutils/td/utils/port/path.h>
#include <td/telegram/ClientActor.h>
#include <tdutils/td/utils/filesystem.h>
#include "td/telegram/TdCallback.h"
#include "td/utils/port/signals.h"
#include "td/telegram/Log.h"
#include "td/utils/crypto.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/actor/PromiseFuture.h"
#include "td/actor/MultiPromise.h"
#include "td/telegram/td_api_json.h"
namespace td {
template <class T>
static void check_td_error(T &result) {
LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result);
}
class TestClient : public Actor {
public:
explicit TestClient(td::string name) : name_(std::move(name)) {
}
struct Update {
td::uint64 id;
td::tl_object_ptr<td::td_api::Object> object;
Update(td::uint64 id, td::tl_object_ptr<td::td_api::Object> object) : id(id), object(std::move(object)) {
}
};
class Listener {
public:
Listener() = default;
Listener(const Listener &) = delete;
Listener &operator=(const Listener &) = delete;
Listener(Listener &&) = delete;
Listener &operator=(Listener &&) = delete;
virtual ~Listener() = default;
virtual void start_listen(TestClient *client) {
}
virtual void stop_listen() {
}
virtual void on_update(std::shared_ptr<Update> update) = 0;
};
struct RemoveListener {
void operator()(Listener *listener) {
send_closure(self, &TestClient::remove_listener, listener);
}
ActorId<TestClient> self;
};
using ListenerToken = std::unique_ptr<Listener, RemoveListener>;
void close(td::Promise<> close_promise) {
close_promise_ = std::move(close_promise);
td_client_.reset();
}
td::unique_ptr<td::TdCallback> make_td_callback() {
class TdCallbackImpl : public td::TdCallback {
public:
explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
}
void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) override {
send_closure(client_, &TestClient::on_result, id, std::move(result));
}
void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) override {
send_closure(client_, &TestClient::on_error, id, std::move(error));
}
TdCallbackImpl(const TdCallbackImpl &) = delete;
TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
TdCallbackImpl(TdCallbackImpl &&) = delete;
TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
~TdCallbackImpl() override {
send_closure(client_, &TestClient::on_closed);
}
private:
td::ActorId<TestClient> client_;
};
return td::make_unique<TdCallbackImpl>(actor_id(this));
}
void add_listener(td::unique_ptr<Listener> listener) {
auto *ptr = listener.get();
listeners_.push_back(std::move(listener));
ptr->start_listen(this);
}
void remove_listener(Listener *listener) {
pending_remove_.push_back(listener);
}
void do_pending_remove_listeners() {
for (auto listener : pending_remove_) {
do_remove_listener(listener);
}
pending_remove_.clear();
}
void do_remove_listener(Listener *listener) {
for (size_t i = 0; i < listeners_.size(); i++) {
if (listeners_[i].get() == listener) {
listener->stop_listen();
listeners_.erase(listeners_.begin() + i);
break;
}
}
}
void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) {
on_update(std::make_shared<Update>(id, std::move(result)));
}
void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) {
on_update(std::make_shared<Update>(id, std::move(error)));
}
void on_update(std::shared_ptr<Update> update) {
for (auto &listener : listeners_) {
listener->on_update(update);
}
do_pending_remove_listeners();
}
void on_closed() {
stop();
}
void start_up() override {
auto old_context = set_context(std::make_shared<td::ActorContext>());
set_tag(name_);
LOG(INFO) << "START UP!";
td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
}
td::ActorOwn<td::ClientActor> td_client_;
td::string name_;
private:
td::vector<td::unique_ptr<Listener>> listeners_;
td::vector<Listener *> pending_remove_;
td::Promise<> close_promise_;
};
class Task : public TestClient::Listener {
public:
void on_update(std::shared_ptr<TestClient::Update> update) override {
auto it = sent_queries_.find(update->id);
if (it != sent_queries_.end()) {
it->second(std::move(update->object));
sent_queries_.erase(it);
}
process_update(update);
}
void start_listen(TestClient *client) override {
client_ = client;
start_up();
}
virtual void process_update(std::shared_ptr<TestClient::Update> update) {
}
template <class FunctionT, class CallbackT>
void send_query(td::tl_object_ptr<FunctionT> function, CallbackT callback) {
auto id = current_query_id_++;
using ResultT = typename FunctionT::ReturnType;
sent_queries_[id] =
[callback = Promise<ResultT>(std::move(callback))](Result<tl_object_ptr<td_api::Object>> r_obj) mutable {
TRY_RESULT_PROMISE(callback, obj, std::move(r_obj));
if (obj->get_id() == td::td_api::error::ID) {
auto err = move_tl_object_as<td_api::error>(std::move(obj));
callback.set_error(Status::Error(err->code_, err->message_));
return;
}
callback.set_value(move_tl_object_as<typename ResultT::element_type>(std::move(obj)));
};
send_closure(client_->td_client_, &td::ClientActor::request, id, std::move(function));
}
protected:
std::map<td::uint64, Promise<td::tl_object_ptr<td::td_api::Object>>> sent_queries_;
TestClient *client_ = nullptr;
td::uint64 current_query_id_ = 1;
virtual void start_up() {
}
void stop() {
client_->remove_listener(this);
client_ = nullptr;
}
bool is_alive() const {
return client_ != nullptr;
}
};
class InitTask : public Task {
public:
struct Options {
string name;
int32 api_id;
string api_hash;
};
InitTask(Options options, td::Promise<> promise) : options_(std::move(options)), promise_(std::move(promise)) {
}
private:
Options options_;
td::Promise<> promise_;
bool start_flag_{false};
void start_up() override {
send_query(td::make_tl_object<td::td_api::getAuthorizationState>(),
[this](auto res) { this->process_authorization_state(res.move_as_ok()); });
}
void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
start_flag_ = true;
td::tl_object_ptr<td::td_api::Function> function;
switch (authorization_state->get_id()) {
case td::td_api::authorizationStateWaitEncryptionKey::ID:
send(td::make_tl_object<td::td_api::checkDatabaseEncryptionKey>());
break;
case td::td_api::authorizationStateReady::ID:
promise_.set_value({});
stop();
break;
case td::td_api::authorizationStateWaitTdlibParameters::ID: {
auto parameters = td::td_api::make_object<td::td_api::tdlibParameters>();
parameters->use_test_dc_ = true;
parameters->database_directory_ = options_.name + TD_DIR_SLASH;
parameters->use_message_database_ = true;
parameters->use_secret_chats_ = true;
parameters->api_id_ = options_.api_id;
parameters->api_hash_ = options_.api_hash;
parameters->system_language_code_ = "en";
parameters->device_model_ = "Desktop";
parameters->application_version_ = "tdclient-test";
parameters->ignore_file_names_ = false;
parameters->enable_storage_optimizer_ = true;
send(td::td_api::make_object<td::td_api::setTdlibParameters>(std::move(parameters)));
break;
}
default:
LOG(ERROR) << "???";
promise_.set_error(
Status::Error(PSLICE() << "Unexpected authorization state " << to_string(authorization_state)));
stop();
break;
}
}
template <class T>
void send(T &&query) {
send_query(std::move(query), [this](auto res) {
if (is_alive()) {
res.ensure();
}
});
}
void process_update(std::shared_ptr<TestClient::Update> update) override {
if (!start_flag_) {
return;
}
if (!update->object) {
return;
}
if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
process_authorization_state(std::move(update_authorization_state->authorization_state_));
}
}
};
class GetMe : public Task {
public:
struct Result {
int32 user_id;
int64 chat_id;
};
GetMe(Promise<Result> promise) : promise_(std::move(promise)) {
}
void start_up() override {
send_query(td::make_tl_object<td::td_api::getMe>(), [this](auto res) { with_user_id(res.move_as_ok()->id_); });
}
private:
Promise<Result> promise_;
Result result_;
void with_user_id(int32 user_id) {
result_.user_id = user_id;
send_query(td::make_tl_object<td::td_api::createPrivateChat>(user_id, false), [this](auto res) { with_chat_id(res.move_as_ok()->id_); });
}
void with_chat_id(int64 chat_id) {
result_.chat_id = chat_id;
promise_.set_value(std::move(result_));
stop();
}
};
class UploadFile : public Task {
public:
struct Result {
std::string content;
std::string remote_id;
};
UploadFile(std::string dir, std::string content, int64 chat_id, Promise<Result> promise) : dir_(std::move(dir)), content_(std::move(content)), chat_id_(std::move(chat_id)), promise_(std::move(promise)) {
}
void start_up() override {
auto hash = hex_encode(sha256(content_)).substr(0, 10);
content_path_ = dir_ + TD_DIR_SLASH + hash + ".data";
id_path_ = dir_ + TD_DIR_SLASH + hash + ".id";
auto r_id = read_file(id_path_);
if (r_id.is_ok() && r_id.ok().size() > 10) {
auto id = r_id.move_as_ok();
LOG(ERROR) << "Got file from cache";
Result res;
res.content = std::move(content_);
res.remote_id = id.as_slice().str();
promise_.set_value(std::move(res));
stop();
return;
}
write_file(content_path_, content_).ensure();
send_query(td::make_tl_object<td::td_api::sendMessage>(
chat_id_, 0, 0, nullptr, nullptr,
td::make_tl_object<td::td_api::inputMessageDocument>(
td::make_tl_object<td::td_api::inputFileLocal>(content_path_), nullptr, true,
td::make_tl_object<td::td_api::formattedText>("tag", td::Auto()))),
[this](auto res) { with_message(res.move_as_ok()); });
}
private:
std::string dir_;
std::string content_path_;
std::string id_path_;
std::string content_;
int64 chat_id_;
Promise<Result> promise_;
int64 file_id_{0};
void with_message(td::tl_object_ptr<td_api::message> message) {
CHECK(message->content_->get_id() == td::td_api::messageDocument::ID);
auto messageDocument = td::move_tl_object_as<td::td_api::messageDocument>(message->content_);
on_file(*messageDocument->document_->document_, true);
}
void on_file(const td_api::file &file, bool force = false) {
if (force) {
file_id_ = file.id_;
}
if (file.id_ != file_id_) {
return;
}
if (file.remote_->is_uploading_completed_) {
Result res;
res.content = std::move(content_);
res.remote_id = file.remote_->id_;
unlink(content_path_).ignore();
atomic_write_file(id_path_, res.remote_id).ignore();
promise_.set_value(std::move(res));
stop();
}
}
void process_update(std::shared_ptr<TestClient::Update> update) override {
if (!update->object) {
return;
}
if (update->object->get_id() == td::td_api::updateFile::ID) {
auto updateFile = td::move_tl_object_as<td::td_api::updateFile>(update->object);
on_file(*updateFile->file_);
}
}
};
class TestDownloadFile : public Task {
public:
TestDownloadFile(std::string remote_id, std::string content, Promise<Unit> promise) : remote_id_(std::move(remote_id)), content_(std::move(content)), promise_(std::move(promise)) {
}
void start_up() override {
send_query(td::make_tl_object<td::td_api::getRemoteFile>(
remote_id_, nullptr
), [this](auto res) { start_file(*res.ok()); });
}
private:
std::string remote_id_;
std::string content_;
Promise<Unit> promise_;
struct Range {
size_t begin;
size_t end;
};
int32 file_id_{0};
std::vector<Range> ranges_;
void start_file(const td_api::file &file) {
LOG(ERROR) << "Start";
file_id_ = file.id_;
// CHECK(!file.local_->is_downloading_active_);
// CHECK(!file.local_->is_downloading_completed_);
// CHECK(file.local_->download_offset_ == 0);
if (!file.local_->path_.empty()) {
unlink(file.local_->path_).ignore();
}
size_t size = file.size_;
Random::Xorshift128plus rnd(123);
size_t begin = 0;
while (begin + 128u < size) {
auto chunk_size = rnd.fast(128, 3096);
auto end = begin + chunk_size;
if (end > size) {
end = size;
}
ranges_.push_back({begin, end});
begin = end;
}
random_shuffle(as_mutable_span(ranges_), rnd);
start_chunk();
}
void got_chunk(const td_api::file &file) {
LOG(ERROR) << "Got chunk";
auto range = ranges_.back();
std::string got_chunk(range.end - range.begin, '\0');
FileFd::open(file.local_->path_, FileFd::Flags::Read).move_as_ok().pread(got_chunk, range.begin).ensure();
CHECK(got_chunk == as_slice(content_).substr(range.begin, range.end - range.begin));
ranges_.pop_back();
if (ranges_.empty()) {
promise_.set_value(Unit{});
return stop();
}
start_chunk();
}
void start_chunk() {
send_query(td::make_tl_object<td::td_api::downloadFile>(
file_id_, 1, int(ranges_.back().begin), int(ranges_.back().end - ranges_.back().begin), true
), [this](auto res) { got_chunk(*res.ok()); });
}
};
std::string gen_readable_file(size_t block_size, size_t block_count) {
std::string content;
for (size_t block_id = 0; block_id < block_count; block_id++) {
std::string block;
for (size_t line = 0; block.size() < block_size; line++) {
block += PSTRING() << "\nblock=" << block_id << ", line=" << line;
}
block.resize(block_size);
content += block;
}
return content;
}
class TestTd : public Actor {
public:
struct Options {
std::string alice_dir = "alice";
std::string bob_dir = "bob";
int32 api_id{0};
string api_hash;
};
TestTd(Options options) : options_(std::move(options)) {
}
private:
Options options_;
ActorOwn<TestClient> alice_;
GetMe::Result alice_id_;
std::string alice_cache_dir_;
ActorOwn<TestClient> bob_;
void start_up() override {
alice_ = create_actor<TestClient>("Alice", "Alice");
bob_ = create_actor<TestClient>("Bob", "Bob");
MultiPromiseActorSafe mp("init");
mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_init));
InitTask::Options options;
options.api_id = options_.api_id;
options.api_hash = options_.api_hash;
options.name = options_.alice_dir;
td::send_closure(alice_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
options.name = options_.bob_dir;
td::send_closure(bob_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
}
void check_init(Result<Unit> res) {
LOG_IF(FATAL, res.is_error()) << res.error();
alice_cache_dir_ = options_.alice_dir + TD_DIR_SLASH + "cache";
mkdir(alice_cache_dir_).ignore();
td::send_closure(alice_, &TestClient::add_listener,
td::make_unique<GetMe>(promise_send_closure(actor_id(this), &TestTd::with_alice_id)));
//close();
}
void with_alice_id(Result<GetMe::Result> alice_id) {
alice_id_ = alice_id.move_as_ok();
LOG(ERROR) << "Alice user_id=" << alice_id_.user_id << ", chat_id=" << alice_id_.chat_id;
auto content = gen_readable_file(65536, 20);
send_closure(alice_, &TestClient::add_listener,
td::make_unique<UploadFile>(alice_cache_dir_, std::move(content), alice_id_.chat_id, promise_send_closure(actor_id(this), &TestTd::with_file)));
}
void with_file(Result<UploadFile::Result> r_result) {
auto result = r_result.move_as_ok();
send_closure(alice_, &TestClient::add_listener,
td::make_unique<TestDownloadFile>(result.remote_id, std::move(result.content), promise_send_closure(actor_id(this), &TestTd::after_test_download_file)));
}
void after_test_download_file(Result<Unit>) {
close();
}
void close() {
MultiPromiseActorSafe mp("close");
mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_close));
td::send_closure(alice_, &TestClient::close, mp.get_promise());
td::send_closure(bob_, &TestClient::close, mp.get_promise());
}
void check_close(Result<Unit> res) {
Scheduler::instance()->finish();
stop();
}
};
static void fail_signal(int sig) {
signal_safe_write_signal_number(sig);
while (true) {
// spin forever to allow debugger to attach
}
}
static void on_fatal_error(const char *error) {
std::cerr << "Fatal error: " << error << std::endl;
}
int main(int argc, char **argv) {
ignore_signal(SignalType::HangUp).ensure();
ignore_signal(SignalType::Pipe).ensure();
set_signal_handler(SignalType::Error, fail_signal).ensure();
set_signal_handler(SignalType::Abort, fail_signal).ensure();
Log::set_fatal_error_callback(on_fatal_error);
init_openssl_threads();
TestTd::Options test_options;
test_options.api_id = [](auto x) -> int32 {
if (x) {
return to_integer<int32>(Slice(x));
}
return 0;
}(std::getenv("TD_API_ID"));
test_options.api_hash = [](auto x) -> std::string {
if (x) {
return x;
}
return std::string();
}(std::getenv("TD_API_HASH"));
int new_verbosity_level = VERBOSITY_NAME(INFO);
OptionParser options;
options.set_description("TDLib experimental tester");
options.add_option('v', "verbosity", "Set verbosity level", [&](Slice level) {
int new_verbosity = 1;
while (begins_with(level, "v")) {
new_verbosity++;
level.remove_prefix(1);
}
if (!level.empty()) {
new_verbosity += to_integer<int>(level) - (new_verbosity == 1);
}
new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
});
options.add_check([&] {
if (test_options.api_id == 0 || test_options.api_hash.empty()) {
return Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
}
return Status::OK();
});
auto r_non_options = options.run(argc, argv, 0);
if (r_non_options.is_error()) {
LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
LOG(PLAIN) << options;
return 1;
}
SET_VERBOSITY_LEVEL(new_verbosity_level);
td::ConcurrentScheduler sched;
sched.init(4);
sched.create_actor_unsafe<TestTd>(0, "TestTd", std::move(test_options)).release();
sched.start();
while (sched.run_main(10)) {
}
sched.finish();
return 0;
}
} // namespace td
int main(int argc, char **argv) {
return td::main(argc, argv);
}