Merge remote-tracking branch 'levlam/master' into fork

This commit is contained in:
Andrea Cavalli 2020-01-27 12:51:29 +01:00
commit 871725db4c
92 changed files with 1727 additions and 715 deletions

View File

@ -54,6 +54,18 @@ Changes in 1.6.0:
- Added the field `version` to the method `textParseModeMarkdown`. Versions 0 and 1 correspond to Bot API Markdown
parse mode, version 2 to Bot API MarkdownV2 parse mode with underline, strikethrough and nested entities support.
- The new entity types and nested entities are supported in secret chats also if its layer is at least 101.
* Added support for native non-anonymous, multiple answer, and quiz-style polls:
- Added support for quiz-style polls, which has exactly one correct answer option and can be answered only once.
- Added support for regular polls, which allows multiple answers.
- Added the classes `pollTypeRegular` and `pollTypeQuiz`, representing the possible types of a poll.
- Added the field `type` to the classes `poll` and `inputMessagePoll`.
- Added support for non-anonymous polls with visible votes by adding the field `is_anonymous` to the classes `poll`
and `inputMessagePoll`.
- Added the method `getPollVoters`, returning users that voted for the specified option in a non-anonymous poll.
- Added the new reply markup keyboard button `keyboardButtonTypeRequestPoll`.
- Added the field `is_regular` to the class `pushMessageContentPoll`.
- Added the update `updatePollAnswer` for bots only.
- Added the field `is_closed` to the class `inputMessagePoll`, which can be used by bots to send a closed poll.
* Clarified in the documentation that file remote ID is guaranteed to be usable only if the corresponding file is
still accessible to the user and is known to TDLib. For example, if the file is from a message, then the message
must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with
@ -70,7 +82,7 @@ Changes in 1.6.0:
* Improved support for chat backgrounds:
- Added the classes `backgroundFillSolid` for solid color backgrounds and `backgroundFillGradient` for
gradient backgrounds.
- Added support for TGV (gzipped subset of SVG with mime-type "application/x-tgwallpattern") background patterns
- Added support for TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") background patterns
in addition to PNG patterns. Background pattern thumbnails are still always in PNG format.
- Replaced the field `color` in the class `backgroundTypePattern` with the field `fill` of type `BackgroundFill`.
- Replaced the class `backgroundTypeSolid` with the class `backgroundTypeFill`.
@ -142,6 +154,7 @@ Changes in 1.6.0:
- Added the method `confirmQrCodeAuthentication` for authentication confirmation from another device.
* Added the update `updateMessageLiveLocationViewed`, which is supposed to trigger an edit of the corresponding
live location.
* Added the parameter `input_language_code` to the method `searchEmojis`.
* Added the method `getInactiveSupergroupChats`, to be used when the user receives a CHANNELS_TOO_MUCH error after
reaching the limit on the number of joined supergroup and channel chats.
* Added the field `unique_id` to the class `remoteFile`, which can be used to identify the same file for
@ -155,17 +168,7 @@ Changes in 1.6.0:
the appropriate mark.
* Added the field `video_upload_bitrate` to the class `autoDownloadSettings`.
* Disallowed to call `setChatNotificationSettings` method on the chat with self, which never worked.
* Added support for integration with TON Blockchain. For a complete integration use `tonlib` from
https://github.com/ton-blockchain/ton:
- Added the option `default_ton_blockchain_config`, containing the default TON Blockchain config. If empty,
TON integration is disabled, otherwise the config needs to be passed to tonlib.
- Added the option `default_ton_blockchain_name`, containing the default TON Blockchain name.
The blockchain name needs to be passed to tonlib.
- Added the class `tonLiteServerResponse` and the method `sendTonLiteServerRequest`, which allows to send requests to
a TON Blockchain Lite Server through Telegram servers.
- Added the class `tonWalletPasswordSalt` and the method `getTonWalletPasswordSalt`, which can be used
to harden protection of the locally stored TON Blockchain private key.
- Added support for `ton://` URLs in messages and inline keyboard buttons.
* Added support for `ton://` URLs in messages and inline keyboard buttons.
-----------------------------------------------------------------------------------------------------------------------

View File

@ -1,6 +1,10 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
project(TDLib VERSION 1.5.4 LANGUAGES CXX C)
project(TDLib VERSION 1.5.5 LANGUAGES CXX C)
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
set(CMAKE_INSTALL_LIBDIR "lib")
endif(NOT DEFINED CMAKE_INSTALL_LIBDIR)
if (POLICY CMP0054)
# do not expand quoted arguments
@ -910,8 +914,8 @@ add_library(Td::TdJson ALIAS TdJson)
add_library(Td::TdJsonStatic ALIAS TdJsonStatic)
install(TARGETS tdjson TdJson tdjson_static TdJsonStatic tdjson_private tdclient tdcore TdStatic EXPORT TdTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
@ -919,7 +923,7 @@ install(TARGETS tdjson TdJson tdjson_static TdJsonStatic tdjson_private tdclient
install(EXPORT TdTargets
FILE TdTargets.cmake
NAMESPACE Td::
DESTINATION lib/cmake/Td
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Td
)
install(FILES ${TD_JSON_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/td/telegram/tdjson_export.h DESTINATION include/td/telegram)
@ -936,7 +940,7 @@ write_basic_package_version_file("TdConfigVersion.cmake"
COMPATIBILITY ExactVersion
)
install(FILES "TdConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/TdConfigVersion.cmake"
DESTINATION lib/cmake/Td
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Td
)
# Add SOVERSION to shared libraries

View File

@ -146,7 +146,7 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic)
Or you could install `TDLib` and then reference it in your CMakeLists.txt like this:
```
find_package(Td 1.5.4 REQUIRED)
find_package(Td 1.5.5 REQUIRED)
target_link_libraries(YourTarget PRIVATE Td::TdStatic)
```
See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/tree/master/example/cpp/CMakeLists.txt).

View File

@ -224,6 +224,7 @@ function split_file($file, $chunks, $undo) {
'std::reverse' => 'algorithm',
'std::rotate' => 'algorithm',
'std::sort' => 'algorithm',
'std::abs' => 'cmath',
'std::numeric_limits' => 'limits',
'std::make_shared' => 'memory',
'std::shared_ptr' => 'memory',

View File

@ -666,9 +666,9 @@ function onOptionsChanged() {
}
}
commands.push('git clone https://github.com/tdlib/td.git');
commands.push('git checkout v1.5.0');
commands.push('cd td');
commands.push('git checkout v1.5.0');
if (use_vcpkg) {
commands.push('git clone https://github.com/Microsoft/vcpkg.git');

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(TdExample VERSION 1.0 LANGUAGES CXX)
find_package(Td 1.5.4 REQUIRED)
find_package(Td 1.5.5 REQUIRED)
add_executable(tdjson_example tdjson_example.cpp)
target_link_libraries(tdjson_example PRIVATE Td::TdJson)

View File

@ -1,6 +1,6 @@
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
<Metadata>
<Identity Id="Telegram.Td.UWP" Version="1.5.4" Language="en-US" Publisher="Telegram LLC" />
<Identity Id="Telegram.Td.UWP" Version="1.5.5" Language="en-US" Publisher="Telegram LLC" />
<DisplayName>TDLib for Universal Windows Platform</DisplayName>
<Description>TDLib is a library for building Telegram clients</Description>
<MoreInfo>https://core.telegram.org/tdlib</MoreInfo>

View File

@ -981,7 +981,7 @@ class TdClient {
}
prepareFile(file) {
const pid = file.remote.id;
const pid = file.remote.unique_id ? file.remote.unique_id : file.remote.id;
if (!pid) {
return file;
}

View File

@ -63,7 +63,7 @@ authenticationCodeInfo phone_number:string type:AuthenticationCodeType next_type
emailAddressAuthenticationCodeInfo email_address_pattern:string length:int32 = EmailAddressAuthenticationCodeInfo;
//@description Represents a part of the text that needs to be formatted in some unusual way @offset Offset of the entity in UTF-16 code points @length Length of the entity, in UTF-16 code points @type Type of the entity
//@description Represents a part of the text that needs to be formatted in some unusual way @offset Offset of the entity in UTF-16 code units @length Length of the entity, in UTF-16 code units @type Type of the entity
textEntity offset:int32 length:int32 type:TextEntityType = TextEntity;
//@description Contains a list of text entities @entities List of text entities
@ -211,6 +211,15 @@ maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPo
pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption;
//@class PollType @description Describes the type of a poll
//@description A regular poll @allow_multiple_answers True, if multiple answer options can be chosen simultaneously
pollTypeRegular allow_multiple_answers:Bool = PollType;
//@description A poll in quiz mode, which has exactly one correct answer option and can be answered only once @correct_option_id 0-based identifier of the correct answer option; -1 for a yet unanswered poll
pollTypeQuiz correct_option_id:int32 = PollType;
//@description Describes an animation file. The animation must be encoded in GIF or MPEG4 format @duration Duration of the animation, in seconds; as defined by the sender @width Width of the animation @height Height of the animation
//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file, usually "image/gif" or "video/mp4"
//@minithumbnail Animation minithumbnail; may be null @thumbnail Animation thumbnail; may be null @animation File containing the animation
@ -257,8 +266,10 @@ venue location:location title:string address:string provider:string id:string ty
//@param_description Game description @photo Game photo @animation Game animation; may be null
game id:int64 short_name:string title:string text:formattedText description:string photo:photo animation:animation = Game;
//@description Describes a poll @id Unique poll identifier @question Poll question, 1-255 characters @options List of poll answer options @total_voter_count Total number of voters, participating in the poll @is_closed True, if the poll is closed
poll id:int64 question:string options:vector<pollOption> total_voter_count:int32 is_closed:Bool = Poll;
//@description Describes a poll @id Unique poll identifier @question Poll question, 1-255 characters @options List of poll answer options
//@total_voter_count Total number of voters, participating in the poll @recent_voter_user_ids User identifiers of recent voters, if the poll is non-anonymous
//@is_anonymous True, if the poll is anonymous @type Type of the poll @is_closed True, if the poll is closed
poll id:int64 question:string options:vector<pollOption> total_voter_count:int32 recent_voter_user_ids:vector<int32> is_anonymous:Bool type:PollType is_closed:Bool = Poll;
//@description Describes a user profile photo @id Photo identifier; 0 for an empty photo. Can be used to find a photo in a list of userProfilePhotos
@ -269,7 +280,7 @@ profilePhoto id:int64 small:file big:file = ProfilePhoto;
chatPhoto small:file big:file = ChatPhoto;
//@class UserType @description Represents the type of the user. The following types are possible: regular users, deleted users and bots
//@class UserType @description Represents the type of a user. The following types are possible: regular users, deleted users and bots
//@description A regular user
userTypeRegular = UserType;
@ -501,8 +512,8 @@ secretChatStateClosed = SecretChatState;
//@state State of the secret chat
//@is_outbound True, if the chat was created by the current user; otherwise false
//@ttl Current message Time To Live setting (self-destruct timer) for the chat, in seconds
//@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 bytes, which must be used to make a 12x12 square image with a color depth of 4. The first 16 bytes should be used to make a central 8x8 square, while the remaining 20 bytes should be used to construct a 2-pixel-wide border around that square.
//-Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers
//@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 little-endian bytes, which must be split into groups of 2 bits, each denoting a pixel of one of 4 colors FFFFFF, D5E6F3, 2D5775, and 2F99C9.
//-The pixels must be used to make a 12x12 square image filled from left to right, top to bottom. Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers
//@layer Secret chat layer; determines features supported by the other client. Video notes are supported if the layer >= 66; nested text entities and underline and strikethrough entities are supported if the layer >= 101
secretChat id:int32 user_id:int32 state:SecretChatState is_outbound:Bool ttl:int32 key_hash:bytes layer:int32 = SecretChat;
@ -724,6 +735,9 @@ keyboardButtonTypeRequestPhoneNumber = KeyboardButtonType;
//@description A button that sends the user's location when pressed; available only in private chats
keyboardButtonTypeRequestLocation = KeyboardButtonType;
//@description A button that allows the user to create and send a poll when pressed; available only in private chats @force_regular If true, only regular polls must be allowed to create @force_quiz If true, only polls in quiz mode must be allowed to create
keyboardButtonTypeRequestPoll force_regular:Bool force_quiz:Bool = KeyboardButtonType;
//@description Represents a single button in a bot keyboard @text Text of the button @type Type of the button
keyboardButton text:string type:KeyboardButtonType = KeyboardButton;
@ -972,9 +986,13 @@ pageBlockMap location:location zoom:int32 width:int32 height:int32 caption:pageB
webPageInstantView page_blocks:vector<PageBlock> version:int32 url:string is_rtl:Bool is_full:Bool = WebPageInstantView;
//@description Describes a web page preview @url Original URL of the link @display_url URL to display
//@description Describes a web page preview
//@url Original URL of the link
//@display_url URL to display
//@type Type of the web page. Can be: article, photo, audio, video, document, profile, app, or something else
//@site_name Short name of the site (e.g., Google Docs, App Store) @title Title of the content @param_description Description of the content
//@site_name Short name of the site (e.g., Google Docs, App Store)
//@title Title of the content
//@param_description Description of the content
//@photo Image representing the content; may be null
//@embed_url URL to show in the embedded preview
//@embed_type MIME type of the embedded preview, (e.g., text/html or video/mp4)
@ -1302,50 +1320,50 @@ inputPassportElementError type:PassportElementType message:string source:InputPa
//@description A text message @text Text of the message @web_page A preview of the web page that's mentioned in the text; may be null
messageText text:formattedText web_page:webPage = MessageContent;
//@description An animation message (GIF-style). @animation Message content @caption Animation caption @is_secret True, if the animation thumbnail must be blurred and the animation must be shown only while tapped
//@description An animation message (GIF-style). @animation The animation description @caption Animation caption @is_secret True, if the animation thumbnail must be blurred and the animation must be shown only while tapped
messageAnimation animation:animation caption:formattedText is_secret:Bool = MessageContent;
//@description An audio message @audio Message content @caption Audio caption
//@description An audio message @audio The audio description @caption Audio caption
messageAudio audio:audio caption:formattedText = MessageContent;
//@description A document message (general file) @document Message content @caption Document caption
//@description A document message (general file) @document The document description @caption Document caption
messageDocument document:document caption:formattedText = MessageContent;
//@description A photo message @photo Message content @caption Photo caption @is_secret True, if the photo must be blurred and must be shown only while tapped
//@description A photo message @photo The photo description @caption Photo caption @is_secret True, if the photo must be blurred and must be shown only while tapped
messagePhoto photo:photo caption:formattedText is_secret:Bool = MessageContent;
//@description An expired photo message (self-destructed after TTL has elapsed)
messageExpiredPhoto = MessageContent;
//@description A sticker message @sticker Message content
//@description A sticker message @sticker The sticker description
messageSticker sticker:sticker = MessageContent;
//@description A video message @video Message content @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped
//@description A video message @video The video description @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped
messageVideo video:video caption:formattedText is_secret:Bool = MessageContent;
//@description An expired video message (self-destructed after TTL has elapsed)
messageExpiredVideo = MessageContent;
//@description A video note message @video_note Message content @is_viewed True, if at least one of the recipients has viewed the video note @is_secret True, if the video note thumbnail must be blurred and the video note must be shown only while tapped
//@description A video note message @video_note The video note description @is_viewed True, if at least one of the recipients has viewed the video note @is_secret True, if the video note thumbnail must be blurred and the video note must be shown only while tapped
messageVideoNote video_note:videoNote is_viewed:Bool is_secret:Bool = MessageContent;
//@description A voice note message @voice_note Message content @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note
//@description A voice note message @voice_note The voice note description @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note
messageVoiceNote voice_note:voiceNote caption:formattedText is_listened:Bool = MessageContent;
//@description A message with a location @location Message content @live_period Time relative to the message sent date until which the location can be updated, in seconds
//@description A message with a location @location The location description @live_period Time relative to the message sent date until which the location can be updated, in seconds
//@expires_in Left time for which the location can be updated, in seconds. updateMessageContent is not sent when this field changes
messageLocation location:location live_period:int32 expires_in:int32 = MessageContent;
//@description A message with information about a venue @venue Message content
//@description A message with information about a venue @venue The venue description
messageVenue venue:venue = MessageContent;
//@description A message with a user contact @contact Message content
//@description A message with a user contact @contact The contact description
messageContact contact:contact = MessageContent;
//@description A message with a game @game Game
//@description A message with a game @game The game description
messageGame game:game = MessageContent;
//@description A message with a poll @poll Poll
//@description A message with a poll @poll The poll description
messagePoll poll:poll = MessageContent;
//@description A message with an invoice from a bot @title Product title @param_description Product description @photo Product photo; may be null @currency Currency for the product price @total_amount Product total price in the minimal quantity of the currency
@ -1531,7 +1549,7 @@ inputMessageVideoNote video_note:InputFile thumbnail:inputThumbnail duration:int
//@description A voice note message @voice_note Voice note to be sent @duration Duration of the voice note, in seconds @waveform Waveform representation of the voice note, in 5-bit format @caption Voice note caption; 0-GetOption("message_caption_length_max") characters
inputMessageVoiceNote voice_note:InputFile duration:int32 waveform:bytes caption:formattedText = InputMessageContent;
//@description A message with a location @location Location to be sent @live_period Period for which the location can be updated, in seconds; should bebetween 60 and 86400 for a live location and 0 otherwise
//@description A message with a location @location Location to be sent @live_period Period for which the location can be updated, in seconds; should be between 60 and 86400 for a live location and 0 otherwise
inputMessageLocation location:location live_period:int32 = InputMessageContent;
//@description A message with information about a venue @venue Venue to send
@ -1547,8 +1565,9 @@ inputMessageGame bot_user_id:int32 game_short_name:string = InputMessageContent;
//@payload The invoice payload @provider_token Payment provider token @provider_data JSON-encoded data about the invoice, which will be shared with the payment provider @start_parameter Unique invoice bot start_parameter for the generation of this invoice
inputMessageInvoice invoice:invoice title:string description:string photo_url:string photo_size:int32 photo_width:int32 photo_height:int32 payload:bytes provider_token:string provider_data:string start_parameter:string = InputMessageContent;
//@description A message with a poll. Polls can't be sent to private or secret chats @question Poll question, 1-255 characters @options List of poll answer options, 2-10 strings 1-100 characters each
inputMessagePoll question:string options:vector<string> = InputMessageContent;
//@description A message with a poll. Polls can't be sent to secret chats. Polls can be sent only to a private chat with a bot @question Poll question, 1-255 characters @options List of poll answer options, 2-10 strings 1-100 characters each
//@is_anonymous True, if the poll voters are anonymous. Non-anonymous polls can't be sent or forwarded to channels @type Type of the poll @is_closed True, if the poll needs to be sent already closed; for bots only
inputMessagePoll question:string options:vector<string> is_anonymous:Bool type:PollType is_closed:Bool = InputMessageContent;
//@description A forwarded message @from_chat_id Identifier for the chat this forwarded message came from @message_id Identifier of the message to forward
//@in_game_share True, if a game message should be shared within a launched game; applies only to game messages
@ -1926,13 +1945,6 @@ gameHighScore position:int32 user_id:int32 score:int32 = GameHighScore;
gameHighScores scores:vector<gameHighScore> = GameHighScores;
//@description Contains the response of a request to TON lite server @response The response
tonLiteServerResponse response:bytes = TonLiteServerResponse;
//@description Contains the salt to be used with locally stored password to access a local TON-based wallet @salt The salt
tonWalletPasswordSalt salt:bytes = TonWalletPasswordSalt;
//@class ChatEventAction @description Represents a chat event
//@description A message was edited @old_message The original message before the edit @new_message The message after it was edited
@ -2106,14 +2118,14 @@ backgroundFillSolid color:int32 = BackgroundFill;
backgroundFillGradient top_color:int32 bottom_color:int32 rotation_angle:int32 = BackgroundFill;
//@class BackgroundType @description Describes a type of a background
//@class BackgroundType @description Describes the type of a background
//@description A wallpaper in JPEG format
//@is_blurred True, if the wallpaper must be downscaled to fit in 450x450 square and then box-blurred with radius 12
//@is_moving True, if the background needs to be slightly moved when device is tilted
backgroundTypeWallpaper is_blurred:Bool is_moving:Bool = BackgroundType;
//@description A PNG or TGV (gzipped subset of SVG with mime-type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user
//@description A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user
//@fill Description of the background fill
//@intensity Intensity of the pattern when it is shown above the filled background, 0-100
//@is_moving True, if the background needs to be slightly moved when device is tilted
@ -2139,7 +2151,7 @@ backgrounds backgrounds:vector<background> = Backgrounds;
//@class InputBackground @description Contains information about background to set
//@description A background from a local file
//@background Background file to use. Only inputFileLocal and inputFileGenerated are supported. The file nust be in JPEG format for wallpapers and in PNG format for patterns
//@background Background file to use. Only inputFileLocal and inputFileGenerated are supported. The file must be in JPEG format for wallpapers and in PNG format for patterns
inputBackgroundLocal background:InputFile = InputBackground;
//@description A background from the server @background_id The background identifier
@ -2218,8 +2230,8 @@ pushMessageContentLocation is_live:Bool is_pinned:Bool = PushMessageContent;
//@description A photo message @photo Message content; may be null @caption Photo caption @is_secret True, if the photo is secret @is_pinned True, if the message is a pinned message with the specified content
pushMessageContentPhoto photo:photo caption:string is_secret:Bool is_pinned:Bool = PushMessageContent;
//@description A message with a poll @question Poll question @is_pinned True, if the message is a pinned message with the specified content
pushMessageContentPoll question:string is_pinned:Bool = PushMessageContent;
//@description A message with a poll @question Poll question @is_regular True, if the poll is regular and not in quiz mode @is_pinned True, if the message is a pinned message with the specified content
pushMessageContentPoll question:string is_regular:Bool is_pinned:Bool = PushMessageContent;
//@description A screenshot of a message in the chat has been taken
pushMessageContentScreenshotTaken = PushMessageContent;
@ -2283,7 +2295,7 @@ notificationTypeNewCall call_id:int32 = NotificationType;
notificationTypeNewPushMessage message_id:int53 sender_user_id:int32 content:PushMessageContent = NotificationType;
//@class NotificationGroupType @description Describes type of notifications in the group
//@class NotificationGroupType @description Describes the type of notifications in a notification group
//@description A group containing notifications of type notificationTypeNewMessage and notificationTypeNewPushMessage with ordinary unread messages
notificationGroupTypeMessages = NotificationGroupType;
@ -2683,7 +2695,7 @@ textParseModeMarkdown version:int32 = TextParseMode;
textParseModeHTML = TextParseMode;
//@class ProxyType @description Describes the type of the proxy server
//@class ProxyType @description Describes the type of a proxy server
//@description A SOCKS5 proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty
proxyTypeSocks5 username:string password:string = ProxyType;
@ -2962,9 +2974,12 @@ updateNewCustomEvent event:string = Update;
//@description A new incoming query; for bots only @id The query identifier @data JSON-serialized query data @timeout Query timeout
updateNewCustomQuery id:int64 data:string timeout:int32 = Update;
//@description Information about a poll was updated; for bots only @poll New data about the poll
//@description A poll was updated; for bots only @poll New data about the poll
updatePoll poll:poll = Update;
//@description A user changed the answer to a poll; for bots only @poll_id Unique poll identifier @user_id The user, who changed the answer to the poll @option_ids 0-based identifiers of answer options, chosen by the user
updatePollAnswer poll_id:int64 user_id:int32 option_ids:vector<int32> = Update;
//@description Contains a list of updates @updates List of updates
updates updates:vector<Update> = Updates;
@ -3017,7 +3032,7 @@ setTdlibParameters parameters:tdlibParameters = Ok;
checkDatabaseEncryptionKey encryption_key:bytes = Ok;
//@description Sets the phone number of the user and sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitPhoneNumber,
//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode or authorizationStateWaitPassword
//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
//@phone_number The phone number of the user, in international format @settings Settings for the authentication of the user's phone number
setAuthenticationPhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = Ok;
@ -3060,7 +3075,7 @@ destroy = Ok;
confirmQrCodeAuthentication link:string = Session;
//@description Returns all updates needed to restore current TDLib state, i.e. all actual UpdateAuthorizationState/UpdateUser/UpdateNewChat and others. This is especially usefull if TDLib is run in a separate process. This is an offline method. Can be called before authorization
//@description Returns all updates needed to restore current TDLib state, i.e. all actual UpdateAuthorizationState/UpdateUser/UpdateNewChat and others. This is especially useful if TDLib is run in a separate process. This is an offline method. Can be called before authorization
getCurrentState = Updates;
@ -3411,10 +3426,18 @@ getJsonValue json:string = JsonValue;
getJsonString json_value:JsonValue = Text;
//@description Changes user answer to a poll @chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_ids 0-based identifiers of options, chosen by the user. Currently user can't choose more than 1 option
//@description Changes the user answer to a poll. A poll in quiz mode can be answered only once
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_ids 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers
setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
//@description Returns users voted for the specified option in a non-anonymous polls. For the optimal performance the number of returned users is chosen by the library
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@option_id 0-based identifier of the answer option
//@offset Number of users to skip in the result; must be non-negative
//@limit The maximum number of users to be returned; must be positive and can't be greater than 50. Fewer users may be returned than specified by the limit, even if the end of the voter list has not been reached
getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = Users;
//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll @reply_markup The new message reply markup; for bots only
stopPoll chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = Ok;
@ -3658,7 +3681,7 @@ cancelUploadFile file_id:int32 = Ok;
//@generation_id The identifier of the generation process @offset The offset from which to write the data to the file @data The data to write
writeGeneratedFilePart generation_id:int64 offset:int32 data:bytes = Ok;
//@description Informs TDLib on a file generation prograss
//@description Informs TDLib on a file generation progress
//@generation_id The identifier of the generation process
//@expected_size Expected size of the generated file, in bytes; 0 if unknown
//@local_prefix_size The number of bytes already generated
@ -3817,8 +3840,8 @@ removeFavoriteSticker sticker:InputFile = Ok;
//@description Returns emoji corresponding to a sticker. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object @sticker Sticker file identifier
getStickerEmojis sticker:InputFile = Emojis;
//@description Searches for emojis by keywords. Supported only if the file database is enabled @text Text to search for @exact_match True, if only emojis, which exactly match text needs to be returned
searchEmojis text:string exact_match:Bool = Emojis;
//@description Searches for emojis by keywords. Supported only if the file database is enabled @text Text to search for @exact_match True, if only emojis, which exactly match text needs to be returned @input_language_code IETF language tag of the user's input language; may be empty if unknown
searchEmojis text:string exact_match:Bool input_language_code:string = Emojis;
//@description Returns an HTTP URL which can be used to automatically log in to the translation platform and suggest new emoji replacements. The URL will be valid for 30 seconds after generation @language_code Language code for which the emoji replacements will be suggested
getEmojiSuggestionsUrl language_code:string = HttpUrl;
@ -3922,7 +3945,7 @@ getSupergroupMembers supergroup_id:int32 filter:SupergroupMembersFilter offset:i
deleteSupergroup supergroup_id:int32 = Ok;
//@description Closes a secret chat, effectively transfering its state to secretChatStateClosed @secret_chat_id Secret chat identifier
//@description Closes a secret chat, effectively transferring its state to secretChatStateClosed @secret_chat_id Secret chat identifier
closeSecretChat secret_chat_id:int32 = Ok;
@ -3974,7 +3997,7 @@ searchBackground name:string = Background;
//@for_dark_theme True, if the background is chosen for dark theme
setBackground background:InputBackground type:BackgroundType for_dark_theme:Bool = Background;
//@description Removes background from the list of installed backgrounds @background_id The background indentifier
//@description Removes background from the list of installed backgrounds @background_id The background identifier
removeBackground background_id:int64 = Ok;
//@description Resets list of installed backgrounds to its default value
@ -4201,13 +4224,6 @@ sendCustomRequest method:string parameters:string = CustomRequestResult;
answerCustomQuery custom_query_id:int64 data:string = Ok;
//@description Sends a request to TON lite server through Telegram servers. Can be called before authorization @request The request
sendTonLiteServerRequest request:bytes = TonLiteServerResponse;
//@description Returns a salt to be used with locally stored password to access a local TON-based wallet
getTonWalletPasswordSalt = TonWalletPasswordSalt;
//@description Succeeds after a specified amount of time has passed. Can be called before authorization. Can be called before initialization @seconds Number of seconds before the function returns
setAlarm seconds:double = Ok;

Binary file not shown.

View File

@ -58,7 +58,7 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int =
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
inputMediaPoll#6b3765b poll:Poll = InputMedia;
inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
@ -338,6 +338,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
updateTheme#8216fba3 theme:Theme = Update;
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
updateLoginToken#564fe691 = Update;
updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -487,7 +488,7 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess
webPageEmpty#eb1477e8 id:long = WebPage;
webPagePending#c586da1c id:long date:int = WebPage;
webPage#fa64e172 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document documents:flags.11?Vector<Document> cached_page:flags.10?Page = WebPage;
webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector<WebPageAttribute> = WebPage;
webPageNotModified#85849473 = WebPage;
authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
@ -533,6 +534,7 @@ keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
@ -1002,11 +1004,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector<PollAnswer> = Poll;
poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> = Poll;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true option:bytes voters:int = PollAnswerVoters;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int = PollResults;
pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> = PollResults;
chatOnlines#f041e250 onlines:int = ChatOnlines;
@ -1069,10 +1071,6 @@ theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_h
account.themesNotModified#f41eb622 = account.Themes;
account.themes#7f676421 hash:int themes:Vector<Theme> = account.Themes;
wallet.liteResponse#764386d7 response:bytes = wallet.LiteResponse;
wallet.secretSalt#dd484d64 salt:bytes = wallet.KeySecretSalt;
auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
@ -1091,6 +1089,14 @@ inputThemeSettings#bd507cd1 flags:# base_theme:BaseTheme accent_color:int messag
themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?WallPaper = ThemeSettings;
webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote;
messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote;
messageUserVoteMultiple#e8fe0de user_id:int options:Vector<bytes> date:int = MessageUserVote;
messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1327,6 +1333,8 @@ messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Message
messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector<InputStickerSet> = Bool;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@ -1434,6 +1442,3 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
folders.deleteFolder#1c295881 folder_id:int = Updates;
wallet.sendLiteRequest#e2c9d33e body:bytes = wallet.LiteResponse;
wallet.getKeySecretSalt#b57f346 revoke:Bool = wallet.KeySecretSalt;

Binary file not shown.

View File

@ -60,6 +60,9 @@ class AuthData {
void set_main_auth_key(AuthKey auth_key) {
main_auth_key_ = std::move(auth_key);
}
void break_main_auth_key() {
main_auth_key_.break_key();
}
const AuthKey &get_main_auth_key() const {
// CHECK(has_main_auth_key());
return main_auth_key_;

View File

@ -17,6 +17,10 @@ class AuthKey {
AuthKey() = default;
AuthKey(uint64 auth_key_id, string &&auth_key) : auth_key_id_(auth_key_id), auth_key_(auth_key) {
}
void break_key() {
auth_key_id_++;
auth_key_[0]++;
}
bool empty() const {
return auth_key_.empty();
@ -47,21 +51,33 @@ class AuthKey {
double expires_at() const {
return expires_at_;
}
double created_at() const {
return created_at_;
}
void set_expires_at(double expires_at) {
expires_at_ = expires_at;
// expires_at_ = Time::now() + 60 * 60 + 10 * 60;
}
void set_created_at(double created_at) {
created_at_ = created_at;
}
void clear() {
auth_key_.clear();
}
enum : int32 { AUTH_FLAG = 1, WAS_AUTH_FLAG = 2 };
enum : int32 { AUTH_FLAG = 1, WAS_AUTH_FLAG = 2, HAS_CREATED_AT = 4 };
template <class StorerT>
void store(StorerT &storer) const {
storer.store_binary(auth_key_id_);
storer.store_binary(static_cast<int32>((auth_flag_ ? AUTH_FLAG : 0) | (was_auth_flag_ ? WAS_AUTH_FLAG : 0)));
bool has_created_at = created_at_ != 0;
storer.store_binary(static_cast<int32>((auth_flag_ ? AUTH_FLAG : 0) | (was_auth_flag_ ? WAS_AUTH_FLAG : 0) |
(has_created_at ? HAS_CREATED_AT : 0)));
storer.store_string(auth_key_);
if (has_created_at) {
storer.store_binary(created_at_);
}
}
template <class ParserT>
@ -71,17 +87,21 @@ class AuthKey {
auth_flag_ = (flags & AUTH_FLAG) != 0;
was_auth_flag_ = (flags & WAS_AUTH_FLAG) != 0 || auth_flag_;
auth_key_ = parser.template fetch_string<string>();
if ((flags & HAS_CREATED_AT) != 0) {
created_at_ = parser.fetch_double();
}
// just in case
need_header_ = true;
}
private:
uint64 auth_key_id_ = 0;
uint64 auth_key_id_{0};
string auth_key_;
bool auth_flag_ = false;
bool was_auth_flag_ = false;
bool need_header_ = true;
double expires_at_ = 0;
bool auth_flag_{false};
bool was_auth_flag_{false};
bool need_header_{true};
double expires_at_{0};
double created_at_{0};
};
} // namespace mtproto

View File

@ -182,7 +182,7 @@ Status AuthKeyHandshake::on_server_dh_params(Slice message, Callback *connection
return Status::Error("Server nonce mismatch");
}
server_time_diff = dh_inner_data.server_time_ - Time::now();
server_time_diff_ = dh_inner_data.server_time_ - Time::now();
DhHandshake handshake;
handshake.set_config(dh_inner_data.g_, dh_inner_data.dh_prime_);
@ -209,12 +209,13 @@ Status AuthKeyHandshake::on_server_dh_params(Slice message, Callback *connection
mtproto_api::set_client_DH_params set_client_dh_params(nonce, server_nonce, encrypted_data);
send(connection, create_storer(set_client_dh_params));
auth_key = AuthKey(auth_key_params.first, std::move(auth_key_params.second));
auth_key_ = AuthKey(auth_key_params.first, std::move(auth_key_params.second));
if (mode_ == Mode::Temp) {
auth_key.set_expires_at(expires_at_);
auth_key_.set_expires_at(expires_at_);
}
auth_key_.set_created_at(dh_inner_data.server_time_);
server_salt = as<int64>(new_nonce.raw) ^ as<int64>(server_nonce.raw);
server_salt_ = as<int64>(new_nonce.raw) ^ as<int64>(server_nonce.raw);
state_ = DHGenResponse;
return Status::OK();
@ -288,7 +289,7 @@ Status AuthKeyHandshake::on_start(Callback *connection) {
return Status::OK();
}
Status AuthKeyHandshake::on_message(Slice message, Callback *connection, Context *context) {
Status AuthKeyHandshake::on_message(Slice message, Callback *connection, AuthKeyHandshakeContext *context) {
Status status = [&] {
switch (state_) {
case ResPQ:

View File

@ -27,6 +27,8 @@ class AuthKeyHandshakeContext {
};
class AuthKeyHandshake {
enum class Mode { Unknown, Main, Temp };
public:
class Callback {
public:
@ -36,20 +38,6 @@ class AuthKeyHandshake {
virtual ~Callback() = default;
virtual void send_no_crypto(const Storer &storer) = 0;
};
using Context = AuthKeyHandshakeContext;
enum class Mode { Unknown, Main, Temp };
AuthKey auth_key;
double server_time_diff = 0;
uint64 server_salt = 0;
bool is_ready_for_start() const;
Status start_main(Callback *connection) TD_WARN_UNUSED_RESULT;
Status start_tmp(Callback *connection, int32 expires_in) TD_WARN_UNUSED_RESULT;
bool is_ready_for_message(const UInt128 &message_nonce) const;
bool is_ready_for_finish() const;
void on_finish();
AuthKeyHandshake(int32 dc_id, int32 expires_in) {
dc_id_ = dc_id;
@ -60,22 +48,49 @@ class AuthKeyHandshake {
expires_in_ = expires_in;
}
}
bool is_ready_for_start() const;
Status start_main(Callback *connection) TD_WARN_UNUSED_RESULT;
Status start_tmp(Callback *connection, int32 expires_in) TD_WARN_UNUSED_RESULT;
bool is_ready_for_message(const UInt128 &message_nonce) const;
bool is_ready_for_finish() const;
void on_finish();
void init_main() {
clear();
mode_ = Mode::Main;
}
void init_temp(int32 expires_in) {
clear();
mode_ = Mode::Temp;
expires_in_ = expires_in;
}
void resume(Callback *connection);
Status on_message(Slice message, Callback *connection, Context *context) TD_WARN_UNUSED_RESULT;
Status on_message(Slice message, Callback *connection, AuthKeyHandshakeContext *context) TD_WARN_UNUSED_RESULT;
bool is_ready() const {
return is_ready_for_finish();
}
void clear();
AuthKey release_auth_key() {
return std::move(auth_key_);
}
double get_server_time_diff() const {
return server_time_diff_;
}
uint64 get_server_salt() const {
return server_salt_;
}
private:
using State = enum { Start, ResPQ, ServerDHParams, DHGenResponse, Finish };
State state_ = Start;
@ -84,6 +99,10 @@ class AuthKeyHandshake {
int32 expires_in_ = 0;
double expires_at_ = 0;
AuthKey auth_key_;
double server_time_diff_ = 0;
uint64 server_salt_ = 0;
UInt128 nonce;
UInt128 server_nonce;
UInt256 new_nonce;

View File

@ -331,7 +331,7 @@ Status SessionConnection::on_packet(const MsgInfo &info,
InvalidContainer = 64
};
Slice common = " BUG! CALL FOR A DEVELOPER! Session will be closed";
Slice common = ". BUG! CALL FOR A DEVELOPER! Session will be closed";
switch (bad_msg_notification.error_code_) {
case MsgIdTooLow: {
LOG(WARNING) << bad_info << ": MessageId is too low. Message will be re-sent";
@ -340,18 +340,18 @@ Status SessionConnection::on_packet(const MsgInfo &info,
break;
}
case MsgIdTooHigh: {
LOG(ERROR) << bad_info << ": MessageId is too high. Session will be closed";
LOG(WARNING) << bad_info << ": MessageId is too high. Session will be closed";
// All this queries will be re-sent by parent
to_send_.clear();
callback_->on_session_failed(Status::Error("MessageId is too high"));
return Status::Error("MessageId is too high");
}
case MsgIdMod4: {
LOG(ERROR) << bad_info << ": MessageId is not divisible by 4." << common;
LOG(ERROR) << bad_info << ": MessageId is not divisible by 4" << common;
return Status::Error("MessageId is not divisible by 4");
}
case MsgIdCollision: {
LOG(ERROR) << bad_info << ": Container and older message MessageId collision." << common;
LOG(ERROR) << bad_info << ": Container and older message MessageId collision" << common;
return Status::Error("Container and older message MessageId collision");
}
@ -362,29 +362,29 @@ Status SessionConnection::on_packet(const MsgInfo &info,
}
case SeqNoTooLow: {
LOG(ERROR) << bad_info << ": SeqNo is too low." << common;
LOG(ERROR) << bad_info << ": SeqNo is too low" << common;
return Status::Error("SeqNo is too low");
}
case SeqNoTooHigh: {
LOG(ERROR) << bad_info << ": SeqNo is too high." << common;
LOG(ERROR) << bad_info << ": SeqNo is too high" << common;
return Status::Error("SeqNo is too high");
}
case SeqNoNotEven: {
LOG(ERROR) << bad_info << ": SeqNo is not even for an irrelevant message." << common;
LOG(ERROR) << bad_info << ": SeqNo is not even for an irrelevant message" << common;
return Status::Error("SeqNo is not even for an irrelevant message");
}
case SeqNoNotOdd: {
LOG(ERROR) << bad_info << ": SeqNo is not odd for an irrelevant message." << common;
LOG(ERROR) << bad_info << ": SeqNo is not odd for an irrelevant message" << common;
return Status::Error("SeqNo is not odd for an irrelevant message");
}
case InvalidContainer: {
LOG(ERROR) << bad_info << ": Invalid Contailer." << common;
LOG(ERROR) << bad_info << ": Invalid Contailer" << common;
return Status::Error("Invalid Contailer");
}
default: {
LOG(ERROR) << bad_info << ": Unknown error [code:" << bad_msg_notification.error_code_ << "]." << common;
LOG(ERROR) << bad_info << ": Unknown error [code:" << bad_msg_notification.error_code_ << "]" << common;
return Status::Error("Unknown error code");
}
}

View File

@ -31,8 +31,8 @@
#include "td/mtproto/RawConnection.h"
#include "td/mtproto/TransportType.h"
#if !TD_EMSCRIPTEN //FIXME
#include "td/net/HttpQuery.h"
#if !TD_EMSCRIPTEN //FIXME
#include "td/net/SslStream.h"
#include "td/net/Wget.h"
#endif
@ -200,10 +200,9 @@ Result<SimpleConfig> decode_config(Slice input) {
return std::move(config);
}
template <class F>
static ActorOwn<> get_simple_config_impl(Promise<SimpleConfigResult> promise, int32 scheduler_id, string url,
string host, std::vector<std::pair<string, string>> headers, bool prefer_ipv6,
F &&get_config,
std::function<Result<string>(HttpQuery &)> get_config,
string content = string(), string content_type = string()) {
VLOG(config_recoverer) << "Request simple config from " << url;
#if TD_EMSCRIPTEN // FIXME
@ -243,7 +242,8 @@ ActorOwn<> get_simple_config_azure(Promise<SimpleConfigResult> promise, const Co
<< "v2/config.txt";
const bool prefer_ipv6 = shared_config == nullptr ? false : shared_config->get_option_boolean("prefer_ipv6");
return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net", {},
prefer_ipv6, [](auto &http_query) -> Result<string> { return http_query.content_.str(); });
prefer_ipv6,
[](HttpQuery &http_query) -> Result<string> { return http_query.content_.str(); });
}
static ActorOwn<> get_simple_config_dns(Slice address, Slice host, Promise<SimpleConfigResult> promise,
@ -253,7 +253,7 @@ static ActorOwn<> get_simple_config_dns(Slice address, Slice host, Promise<Simpl
if (name.empty()) {
name = is_test ? "tapv3.stel.com" : "apv3.stel.com";
}
auto get_config = [](auto &http_query) -> Result<string> {
auto get_config = [](HttpQuery &http_query) -> Result<string> {
TRY_RESULT(json, json_decode(http_query.content_));
if (json.type() != JsonValue::Type::Object) {
return Status::Error("Expected JSON object");
@ -321,7 +321,7 @@ ActorOwn<> get_simple_config_firebase_remote_config(Promise<SimpleConfigResult>
"https://firebaseremoteconfig.googleapis.com/v1/projects/peak-vista-421/namespaces/"
"firebase:fetch?key=AIzaSyC2-kAkpDsroixRXw-sTw-Wfqo4NxjMwwM";
const bool prefer_ipv6 = shared_config == nullptr ? false : shared_config->get_option_boolean("prefer_ipv6");
auto get_config = [](auto &http_query) -> Result<string> {
auto get_config = [](HttpQuery &http_query) -> Result<string> {
TRY_RESULT(json, json_decode(http_query.get_arg("entries")));
if (json.type() != JsonValue::Type::Object) {
return Status::Error("Expected JSON object");
@ -343,7 +343,7 @@ ActorOwn<> get_simple_config_firebase_realtime(Promise<SimpleConfigResult> promi
string url = "https://reserve-5a846.firebaseio.com/ipconfigv3.json";
const bool prefer_ipv6 = shared_config == nullptr ? false : shared_config->get_option_boolean("prefer_ipv6");
auto get_config = [](auto &http_query) -> Result<string> {
auto get_config = [](HttpQuery &http_query) -> Result<string> {
return http_query.get_arg("content").str();
};
return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "reserve-5a846.firebaseio.com", {},
@ -359,7 +359,7 @@ ActorOwn<> get_simple_config_firebase_firestore(Promise<SimpleConfigResult> prom
string url = "https://www.google.com/v1/projects/reserve-5a846/databases/(default)/documents/ipconfig/v3";
const bool prefer_ipv6 = shared_config == nullptr ? false : shared_config->get_option_boolean("prefer_ipv6");
auto get_config = [](auto &http_query) -> Result<string> {
auto get_config = [](HttpQuery &http_query) -> Result<string> {
TRY_RESULT(json, json_decode(http_query.get_arg("fields")));
if (json.type() != JsonValue::Type::Object) {
return Status::Error("Expected JSON object");

View File

@ -2975,6 +2975,8 @@ void ContactsManager::User::parse(ParserT &parser) {
}
if (have_access_hash) {
parse(access_hash, parser);
} else {
is_min_access_hash = true;
}
if (has_photo) {
parse(photo, parser);
@ -5796,8 +5798,7 @@ void ContactsManager::export_channel_invite_link(ChannelId channel_id, Promise<U
}
void ContactsManager::check_dialog_invite_link(const string &invite_link, Promise<Unit> &&promise) const {
auto it = invite_link_infos_.find(invite_link);
if (it != invite_link_infos_.end()) {
if (invite_link_infos_.count(invite_link) > 0) {
return promise.set_value(Unit());
}
@ -6419,7 +6420,7 @@ void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr,
User *u = add_user(user_id, "on_get_user");
if (have_access_hash) { // access_hash must be updated before photo
auto access_hash = user->access_hash_;
bool is_min_access_hash = !is_received && ((flags & USER_FLAG_HAS_PHONE_NUMBER) == 0);
bool is_min_access_hash = !is_received && !((flags & USER_FLAG_HAS_PHONE_NUMBER) != 0 && user->phone_.empty());
if (u->access_hash != access_hash && (!is_min_access_hash || u->is_min_access_hash || u->access_hash == -1)) {
LOG(DEBUG) << "Access hash has changed for " << user_id << " from " << u->access_hash << "/"
<< u->is_min_access_hash << " to " << access_hash << "/" << is_min_access_hash;
@ -6428,7 +6429,7 @@ void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr,
u->need_save_to_database = true;
}
}
if (is_received) {
if (is_received || !user->phone_.empty()) {
on_update_user_phone_number(u, user_id, std::move(user->phone_));
}
on_update_user_photo(u, user_id, std::move(user->photo_), source);
@ -6521,7 +6522,7 @@ void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr,
if (is_deleted != u->is_deleted) {
u->is_deleted = is_deleted;
LOG(DEBUG) << "User.is_deleted has changed for " << user_id;
LOG(DEBUG) << "User.is_deleted has changed for " << user_id << " to " << u->is_deleted;
u->is_is_deleted_changed = true;
u->is_changed = true;
}
@ -6532,7 +6533,7 @@ void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr,
if (u->language_code != user->lang_code_ && !user->lang_code_.empty()) {
u->language_code = user->lang_code_;
LOG(DEBUG) << "Language code has changed for " << user_id;
LOG(DEBUG) << "Language code has changed for " << user_id << " to " << u->language_code;
u->is_changed = true;
}
@ -9441,6 +9442,7 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s
c->has_location = false;
update_channel(c, channel_id);
}
on_update_channel_linked_channel_id(channel_id, ChannelId());
}
invalidate_channel_full(channel_id, false, !c->is_slow_mode_enabled);
LOG_IF(ERROR, have_input_peer_channel(c, channel_id, AccessRights::Read))
@ -10023,11 +10025,6 @@ void ContactsManager::on_update_channel_full_slow_mode_next_send_date(ChannelFul
void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link,
tl_object_ptr<telegram_api::ChatInvite> &&chat_invite_ptr) {
auto &invite_link_info = invite_link_infos_[invite_link];
if (invite_link_info == nullptr) {
invite_link_info = make_unique<InviteLinkInfo>();
}
CHECK(chat_invite_ptr != nullptr);
switch (chat_invite_ptr->get_id()) {
case telegram_api::chatInviteAlready::ID: {
@ -10045,7 +10042,10 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link,
on_get_chat(std::move(chat_invite_already->chat_), "chatInviteAlready");
CHECK(chat_id == ChatId() || channel_id == ChannelId());
CHECK(invite_link_info != nullptr);
auto &invite_link_info = invite_link_infos_[invite_link];
if (invite_link_info == nullptr) {
invite_link_info = make_unique<InviteLinkInfo>();
}
invite_link_info->chat_id = chat_id;
invite_link_info->channel_id = channel_id;
@ -10059,22 +10059,28 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link,
}
case telegram_api::chatInvite::ID: {
auto chat_invite = move_tl_object_as<telegram_api::chatInvite>(chat_invite_ptr);
CHECK(invite_link_info != nullptr);
vector<UserId> participant_user_ids;
for (auto &user : chat_invite->participants_) {
auto user_id = get_user_id(user);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id;
continue;
}
on_get_user(std::move(user), "chatInvite");
participant_user_ids.push_back(user_id);
}
auto &invite_link_info = invite_link_infos_[invite_link];
if (invite_link_info == nullptr) {
invite_link_info = make_unique<InviteLinkInfo>();
}
invite_link_info->chat_id = ChatId();
invite_link_info->channel_id = ChannelId();
invite_link_info->title = chat_invite->title_;
invite_link_info->photo = get_photo(td_->file_manager_.get(), std::move(chat_invite->photo_), DialogId());
invite_link_info->participant_count = chat_invite->participants_count_;
invite_link_info->participant_user_ids.clear();
for (auto &user : chat_invite->participants_) {
auto user_id = get_user_id(user);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id;
} else {
on_get_user(std::move(user), "chatInvite");
}
invite_link_info->participant_user_ids.push_back(user_id);
}
invite_link_info->participant_user_ids = std::move(participant_user_ids);
invite_link_info->is_chat = (chat_invite->flags_ & CHAT_INVITE_FLAG_IS_CHANNEL) == 0;
invite_link_info->is_channel = (chat_invite->flags_ & CHAT_INVITE_FLAG_IS_CHANNEL) != 0;
@ -12718,8 +12724,8 @@ tl_object_ptr<td_api::user> ContactsManager::get_user_object(UserId user_id, con
u->is_received, std::move(type), u->language_code);
}
vector<int32> ContactsManager::get_user_ids_object(const vector<UserId> &user_ids) const {
return transform(user_ids, [this](UserId user_id) { return get_user_id_object(user_id, "get_user_ids_object"); });
vector<int32> ContactsManager::get_user_ids_object(const vector<UserId> &user_ids, const char *source) const {
return transform(user_ids, [this, source](UserId user_id) { return get_user_id_object(user_id, source); });
}
tl_object_ptr<td_api::users> ContactsManager::get_users_object(int32 total_count,
@ -12727,7 +12733,7 @@ tl_object_ptr<td_api::users> ContactsManager::get_users_object(int32 total_count
if (total_count == -1) {
total_count = narrow_cast<int32>(user_ids.size());
}
return td_api::make_object<td_api::users>(total_count, get_user_ids_object(user_ids));
return td_api::make_object<td_api::users>(total_count, get_user_ids_object(user_ids, "get_users_object"));
}
tl_object_ptr<td_api::userFullInfo> ContactsManager::get_user_full_info_object(UserId user_id) const {
@ -12958,7 +12964,7 @@ tl_object_ptr<td_api::chatInviteLinkInfo> ContactsManager::get_chat_invite_link_
invite_link_photo = as_dialog_photo(invite_link_info->photo);
photo = &invite_link_photo;
participant_count = invite_link_info->participant_count;
member_user_ids = get_user_ids_object(invite_link_info->participant_user_ids);
member_user_ids = get_user_ids_object(invite_link_info->participant_user_ids, "get_chat_invite_link_info_object");
is_public = invite_link_info->is_public;
if (invite_link_info->is_chat) {

View File

@ -485,7 +485,7 @@ class ContactsManager : public Actor {
tl_object_ptr<td_api::user> get_user_object(UserId user_id) const;
vector<int32> get_user_ids_object(const vector<UserId> &user_ids) const;
vector<int32> get_user_ids_object(const vector<UserId> &user_ids, const char *source) const;
tl_object_ptr<td_api::users> get_users_object(int32 total_count, const vector<UserId> &user_ids) const;
@ -551,7 +551,7 @@ class ContactsManager : public Actor {
static constexpr uint32 CACHE_VERSION = 1;
uint32 cache_version = 0;
bool is_min_access_hash = false;
bool is_min_access_hash = true;
bool is_received = false;
bool is_verified = false;
bool is_support = false;

View File

@ -97,6 +97,10 @@ tl_object_ptr<td_api::game> Game::get_game_object(Td *td) const {
td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object"));
}
bool Game::has_input_media() const {
return bot_user_id_.is_valid();
}
tl_object_ptr<telegram_api::inputMediaGame> Game::get_input_media_game(const Td *td) const {
auto input_user = td->contacts_manager_->get_input_user(bot_user_id_);
CHECK(input_user != nullptr);

View File

@ -65,6 +65,8 @@ class Game {
tl_object_ptr<td_api::game> get_game_object(Td *td) const;
bool has_input_media() const;
tl_object_ptr<telegram_api::inputMediaGame> get_input_media_game(const Td *td) const;
template <class StorerT>

View File

@ -12,6 +12,7 @@
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/net/TempAuthKeyWatchdog.h"
#include "td/telegram/StateManager.h"
#include "td/telegram/Td.h"
#include "td/telegram/TdDb.h"
#include "td/actor/PromiseFuture.h"
@ -138,6 +139,9 @@ void Global::update_server_time_difference(double diff) {
server_time_difference_ = diff;
server_time_difference_was_updated_ = true;
do_save_server_time_difference();
CHECK(Scheduler::instance());
send_closure(td(), &Td::on_update_server_time_difference);
}
}

View File

@ -123,6 +123,9 @@ class Global : public ActorContext {
return *shared_config_;
}
bool is_server_time_reliable() const {
return server_time_difference_was_updated_;
}
double to_server_time(double now) const {
return now + get_server_time_difference();
}

View File

@ -330,7 +330,7 @@ void InlineQueriesManager::answer_inline_query(int64 inline_query_id, bool is_pe
if (switch_pm_parameter.size() > 64) {
return promise.set_error(Status::Error(400, "Too long switch_pm_parameter specified"));
}
if (!is_base64url(switch_pm_parameter)) {
if (!is_base64url_characters(switch_pm_parameter)) {
return promise.set_error(Status::Error(400, "Unallowed characters in switch_pm_parameter are used"));
}
}

View File

@ -429,7 +429,7 @@ class MessageChatSetTtl : public MessageContent {
class MessageUnsupported : public MessageContent {
public:
static constexpr int32 CURRENT_VERSION = 4;
static constexpr int32 CURRENT_VERSION = 5;
int32 version = CURRENT_VERSION;
MessageUnsupported() = default;
@ -1806,8 +1806,35 @@ static Result<InputMessageContent> create_input_message_content(
}
}
content = make_unique<MessagePoll>(
td->poll_manager_->create_poll(std::move(input_poll->question_), std::move(input_poll->options_)));
bool allow_multiple_answers = false;
bool is_quiz = false;
int32 correct_option_id = -1;
if (input_poll->type_ == nullptr) {
return Status::Error(400, "Poll type must not be empty");
}
switch (input_poll->type_->get_id()) {
case td_api::pollTypeRegular::ID: {
auto type = td_api::move_object_as<td_api::pollTypeRegular>(input_poll->type_);
allow_multiple_answers = type->allow_multiple_answers_;
break;
}
case td_api::pollTypeQuiz::ID: {
auto type = td_api::move_object_as<td_api::pollTypeQuiz>(input_poll->type_);
is_quiz = true;
correct_option_id = type->correct_option_id_;
if (correct_option_id < 0 || correct_option_id >= static_cast<int32>(input_poll->options_.size())) {
return Status::Error(400, "Wrong correct option ID specified");
}
break;
}
default:
UNREACHABLE();
}
bool is_closed = is_bot ? input_poll->is_closed_ : false;
content = make_unique<MessagePoll>(td->poll_manager_->create_poll(
std::move(input_poll->question_), std::move(input_poll->options_), input_poll->is_anonymous_,
allow_multiple_answers, is_quiz, correct_option_id, is_closed));
break;
}
default:
@ -1929,6 +1956,58 @@ Result<InputMessageContent> get_input_message_content(
std::move(thumbnail), std::move(sticker_file_ids));
}
bool can_have_input_media(const Td *td, const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Game:
return static_cast<const MessageGame *>(content)->game.has_input_media();
case MessageContentType::Poll:
return td->poll_manager_->has_input_media(static_cast<const MessagePoll *>(content)->poll_id);
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
case MessageContentType::ChatChangeTitle:
case MessageContentType::ChatChangePhoto:
case MessageContentType::ChatDeletePhoto:
case MessageContentType::ChatDeleteHistory:
case MessageContentType::ChatAddUsers:
case MessageContentType::ChatJoinedByLink:
case MessageContentType::ChatDeleteUser:
case MessageContentType::ChatMigrateTo:
case MessageContentType::ChannelCreate:
case MessageContentType::ChannelMigrateFrom:
case MessageContentType::PinMessage:
case MessageContentType::GameScore:
case MessageContentType::ScreenshotTaken:
case MessageContentType::ChatSetTtl:
case MessageContentType::Call:
case MessageContentType::PaymentSuccessful:
case MessageContentType::ContactRegistered:
case MessageContentType::ExpiredPhoto:
case MessageContentType::ExpiredVideo:
case MessageContentType::CustomServiceAction:
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
return false;
case MessageContentType::Animation:
case MessageContentType::Audio:
case MessageContentType::Contact:
case MessageContentType::Document:
case MessageContentType::Invoice:
case MessageContentType::LiveLocation:
case MessageContentType::Location:
case MessageContentType::Photo:
case MessageContentType::Sticker:
case MessageContentType::Text:
case MessageContentType::Venue:
case MessageContentType::Video:
case MessageContentType::VideoNote:
case MessageContentType::VoiceNote:
return true;
default:
UNREACHABLE();
}
}
SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer) {
@ -1989,9 +2068,11 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
auto m = static_cast<const MessageVoiceNote *>(content);
return td->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text);
}
case MessageContentType::LiveLocation:
case MessageContentType::Call:
case MessageContentType::Game:
case MessageContentType::Invoice:
case MessageContentType::LiveLocation:
case MessageContentType::Poll:
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
case MessageContentType::ChatChangeTitle:
@ -2016,7 +2097,6 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
case MessageContentType::WebsiteConnected:
case MessageContentType::PassportDataSent:
case MessageContentType::PassportDataReceived:
case MessageContentType::Poll:
break;
default:
UNREACHABLE();
@ -2105,6 +2185,9 @@ static tl_object_ptr<telegram_api::inputMediaInvoice> get_input_media_invoice(co
static tl_object_ptr<telegram_api::InputMedia> get_input_media_impl(
const MessageContent *content, Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
tl_object_ptr<telegram_api::InputFile> input_thumbnail, int32 ttl) {
if (!can_have_input_media(td, content)) {
return nullptr;
}
switch (content->get_type()) {
case MessageContentType::Animation: {
auto m = static_cast<const MessageAnimation *>(content);
@ -2124,9 +2207,6 @@ static tl_object_ptr<telegram_api::InputMedia> get_input_media_impl(
}
case MessageContentType::Game: {
auto m = static_cast<const MessageGame *>(content);
if (!m->game.get_bot_user_id().is_valid()) {
return nullptr;
}
return m->game.get_input_media_game(td);
}
case MessageContentType::Invoice: {
@ -2147,6 +2227,10 @@ static tl_object_ptr<telegram_api::InputMedia> get_input_media_impl(
auto m = static_cast<const MessagePhoto *>(content);
return photo_get_input_media(td->file_manager_.get(), m->photo, std::move(input_file), ttl);
}
case MessageContentType::Poll: {
auto m = static_cast<const MessagePoll *>(content);
return td->poll_manager_->get_input_media(m->poll_id);
}
case MessageContentType::Sticker: {
auto m = static_cast<const MessageSticker *>(content);
return td->stickers_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
@ -2167,10 +2251,6 @@ static tl_object_ptr<telegram_api::InputMedia> get_input_media_impl(
auto m = static_cast<const MessageVoiceNote *>(content);
return td->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file));
}
case MessageContentType::Poll: {
auto m = static_cast<const MessagePoll *>(content);
return td->poll_manager_->get_input_media(m->poll_id);
}
case MessageContentType::Text:
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
@ -2709,15 +2789,6 @@ int32 get_message_content_live_location_period(const MessageContent *content) {
}
}
UserId get_message_content_game_bot_user_id(const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Game:
return static_cast<const MessageGame *>(content)->game.get_bot_user_id();
default:
return UserId();
}
}
bool get_message_content_poll_is_closed(const Td *td, const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Poll:
@ -2727,6 +2798,15 @@ bool get_message_content_poll_is_closed(const Td *td, const MessageContent *cont
}
}
bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *content) {
switch (content->get_type()) {
case MessageContentType::Poll:
return td->poll_manager_->get_poll_is_anonymous(static_cast<const MessagePoll *>(content)->poll_id);
default:
return true;
}
}
WebPageId get_message_content_web_page_id(const MessageContent *content) {
if (content->get_type() == MessageContentType::Text) {
return static_cast<const MessageText *>(content)->web_page_id;
@ -2739,18 +2819,26 @@ void set_message_content_web_page_id(MessageContent *content, WebPageId web_page
static_cast<MessageText *>(content)->web_page_id = web_page_id;
}
void set_message_content_poll_answer(Td *td, MessageContent *content, FullMessageId full_message_id,
void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id,
vector<int32> &&option_ids, Promise<Unit> &&promise) {
CHECK(content->get_type() == MessageContentType::Poll);
td->poll_manager_->set_poll_answer(static_cast<MessagePoll *>(content)->poll_id, full_message_id,
td->poll_manager_->set_poll_answer(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
std::move(option_ids), std::move(promise));
}
void stop_message_content_poll(Td *td, MessageContent *content, FullMessageId full_message_id,
void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id,
int32 option_id, int32 offset, int32 limit,
Promise<std::pair<int32, vector<UserId>>> &&promise) {
CHECK(content->get_type() == MessageContentType::Poll);
td->poll_manager_->get_poll_voters(static_cast<const MessagePoll *>(content)->poll_id, full_message_id, option_id,
offset, limit, std::move(promise));
}
void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id,
unique_ptr<ReplyMarkup> &&reply_markup, Promise<Unit> &&promise) {
CHECK(content->get_type() == MessageContentType::Poll);
td->poll_manager_->stop_poll(static_cast<MessagePoll *>(content)->poll_id, full_message_id, std::move(reply_markup),
std::move(promise));
td->poll_manager_->stop_poll(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
std::move(reply_markup), std::move(promise));
}
static void merge_location_access_hash(const Location &first, const Location &second) {
@ -3988,8 +4076,12 @@ unique_ptr<MessageContent> get_message_content(Td *td, FormattedText message,
}
unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const MessageContent *content,
bool for_forward, bool remove_caption) {
MessageContentDupType type) {
CHECK(content != nullptr);
if (type != MessageContentDupType::Forward && type != MessageContentDupType::SendViaBot &&
!can_have_input_media(td, content)) {
return nullptr;
}
bool to_secret = dialog_id.get_type() == DialogType::SecretChat;
auto fix_file_id = [dialog_id, to_secret, file_manager = td->file_manager_.get()](FileId file_id) {
@ -4008,6 +4100,7 @@ unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const
if (to_secret) {
thumbnail_file_id = get_message_content_thumbnail_file_id(content, td);
}
auto remove_caption = type == MessageContentDupType::CopyWithoutCaption;
switch (content->get_type()) {
case MessageContentType::Animation: {
auto result = make_unique<MessageAnimation>(*static_cast<const MessageAnimation *>(content));
@ -4052,10 +4145,10 @@ unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const
case MessageContentType::Invoice:
return make_unique<MessageInvoice>(*static_cast<const MessageInvoice *>(content));
case MessageContentType::LiveLocation:
if (to_secret || for_forward) {
return make_unique<MessageLocation>(Location(static_cast<const MessageLiveLocation *>(content)->location));
} else {
if (!to_secret && (type == MessageContentDupType::Send || type == MessageContentDupType::SendViaBot)) {
return make_unique<MessageLiveLocation>(*static_cast<const MessageLiveLocation *>(content));
} else {
return make_unique<MessageLocation>(Location(static_cast<const MessageLiveLocation *>(content)->location));
}
case MessageContentType::Location:
return make_unique<MessageLocation>(*static_cast<const MessageLocation *>(content));
@ -4119,6 +4212,8 @@ unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const
}
return std::move(result);
}
case MessageContentType::Poll:
return make_unique<MessagePoll>(*static_cast<const MessagePoll *>(content));
case MessageContentType::Sticker: {
auto result = make_unique<MessageSticker>(*static_cast<const MessageSticker *>(content));
if (td->stickers_manager_->has_input_media(result->file_id, to_secret)) {
@ -4167,8 +4262,6 @@ unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const
CHECK(result->file_id.is_valid());
return std::move(result);
}
case MessageContentType::Poll:
return make_unique<MessagePoll>(*static_cast<const MessagePoll *>(content));
case MessageContentType::Unsupported:
case MessageContentType::ChatCreate:
case MessageContentType::ChatChangeTitle:
@ -4466,7 +4559,7 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
case MessageContentType::ChatCreate: {
const MessageChatCreate *m = static_cast<const MessageChatCreate *>(content);
return make_tl_object<td_api::messageBasicGroupChatCreate>(
m->title, td->contacts_manager_->get_user_ids_object(m->participant_user_ids));
m->title, td->contacts_manager_->get_user_ids_object(m->participant_user_ids, "MessageChatCreate"));
}
case MessageContentType::ChatChangeTitle: {
const MessageChatChangeTitle *m = static_cast<const MessageChatChangeTitle *>(content);
@ -4482,7 +4575,8 @@ tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageCo
return make_tl_object<td_api::messageUnsupported>();
case MessageContentType::ChatAddUsers: {
const MessageChatAddUsers *m = static_cast<const MessageChatAddUsers *>(content);
return make_tl_object<td_api::messageChatAddMembers>(td->contacts_manager_->get_user_ids_object(m->user_ids));
return make_tl_object<td_api::messageChatAddMembers>(
td->contacts_manager_->get_user_ids_object(m->user_ids, "MessageChatAddUsers"));
}
case MessageContentType::ChatJoinedByLink:
return make_tl_object<td_api::messageChatJoinByLink>();
@ -4848,6 +4942,7 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte
case MessageContentType::ChatSetTtl:
case MessageContentType::Call:
case MessageContentType::PaymentSuccessful:
case MessageContentType::ContactRegistered:
case MessageContentType::ExpiredPhoto:
case MessageContentType::ExpiredVideo:
case MessageContentType::CustomServiceAction:
@ -5043,6 +5138,7 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC
case MessageContentType::PassportDataReceived:
break;
case MessageContentType::Poll:
// no need to add poll dependencies, because they are forcely loaded with the poll
break;
default:
UNREACHABLE();

View File

@ -142,6 +142,8 @@ unique_ptr<MessageContent> create_chat_set_ttl_message_content(int32 ttl);
Result<InputMessageContent> get_input_message_content(
DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Td *td);
bool can_have_input_media(const Td *td, const MessageContent *content);
SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer);
@ -178,20 +180,24 @@ vector<UserId> get_message_content_added_user_ids(const MessageContent *content)
UserId get_message_content_deleted_user_id(const MessageContent *content);
UserId get_message_content_game_bot_user_id(const MessageContent *content);
int32 get_message_content_live_location_period(const MessageContent *content);
bool get_message_content_poll_is_closed(const Td *td, const MessageContent *content);
bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *content);
WebPageId get_message_content_web_page_id(const MessageContent *content);
void set_message_content_web_page_id(MessageContent *content, WebPageId web_page_id);
void set_message_content_poll_answer(Td *td, MessageContent *content, FullMessageId full_message_id,
void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id,
vector<int32> &&option_ids, Promise<Unit> &&promise);
void stop_message_content_poll(Td *td, MessageContent *content, FullMessageId full_message_id,
void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id,
int32 option_id, int32 offset, int32 limit,
Promise<std::pair<int32, vector<UserId>>> &&promise);
void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id,
unique_ptr<ReplyMarkup> &&reply_markup, Promise<Unit> &&promise);
void merge_message_contents(Td *td, const MessageContent *old_content, MessageContent *new_content,
@ -215,8 +221,10 @@ unique_ptr<MessageContent> get_message_content(Td *td, FormattedText message_tex
DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id,
int32 *ttl);
enum class MessageContentDupType : int32 { Send, SendViaBot, Forward, Copy, CopyWithoutCaption };
unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const MessageContent *content,
bool for_forward, bool remove_caption = false);
MessageContentDupType type);
unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<telegram_api::MessageAction> &&action,
DialogId owner_dialog_id, MessageId reply_to_message_id);

View File

@ -2595,7 +2595,7 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
TRY_RESULT(result, clean_input_string_with_entities(text, entities));
// now entities are still sorted by offset and length, but not type,
// because some characters could be deleted and some entities bacame to end together
// because some characters could be deleted and after that some entities begin to share a common end
size_t last_non_whitespace_pos;
int32 last_non_whitespace_utf16_offset;
@ -2685,7 +2685,7 @@ FormattedText get_message_text(const ContactsManager *contacts_manager, string m
auto debug_entities = entities;
auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, false);
if (status.is_error()) {
if (send_date == 0 || send_date > 1497000000) { // approximate fix date
if (send_date == 0 || send_date > 1579219200) { // approximate fix date
LOG(ERROR) << "Receive error " << status << " while parsing message text from " << source << " with content \""
<< debug_message_text << "\" -> \"" << message_text << "\" sent at " << send_date << " with entities "
<< format::as_array(debug_entities) << " -> " << format::as_array(entities);

View File

@ -6379,6 +6379,7 @@ void MessagesManager::on_message_edited(FullMessageId full_message_id) {
const Message *m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
if (td_->auth_manager_->is_bot()) {
d->last_edited_message_id = m->message_id;
send_update_message_edited(dialog_id, m);
}
update_used_hashtags(dialog_id, m);
@ -12048,6 +12049,8 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vector<tl_object_ptr<te
}
auto &list = get_dialog_list(folder_id);
bool need_recalc_unread_count =
list.last_server_dialog_date_ == MIN_DIALOG_DATE && (from_dialog_list || from_pinned_dialog_list);
if (from_dialog_list) {
if (dialogs.empty()) {
// if there are no more dialogs on the server
@ -12262,6 +12265,10 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vector<tl_object_ptr<te
}
}
promise.set_value(Unit());
if (need_recalc_unread_count) {
recalc_unread_count(folder_id);
}
}
void MessagesManager::dump_debug_message_op(const Dialog *d, int priority) {
@ -12318,6 +12325,7 @@ bool MessagesManager::can_unload_message(const Dialog *d, const Message *m) cons
// don't want to unload messages with pending web pages
// don't want to unload message with active reply markup
// don't want to unload pinned message
// don't want to unload last edited message, because server can send updateEditChannelMessage again
// can't unload from memory last dialog, last database messages, yet unsent messages, being edited media messages and active live locations
// can't unload messages in dialog with active suffix load query
FullMessageId full_message_id{d->dialog_id, m->message_id};
@ -12325,7 +12333,8 @@ bool MessagesManager::can_unload_message(const Dialog *d, const Message *m) cons
!m->message_id.is_yet_unsent() && active_live_location_full_message_ids_.count(full_message_id) == 0 &&
replied_by_yet_unsent_messages_.count(full_message_id) == 0 && m->edited_content == nullptr &&
waiting_for_web_page_messages_.count(full_message_id) == 0 && d->suffix_load_queries_.empty() &&
m->message_id != d->reply_markup_message_id && m->message_id != d->pinned_message_id;
m->message_id != d->reply_markup_message_id && m->message_id != d->pinned_message_id &&
m->message_id != d->last_edited_message_id;
}
void MessagesManager::unload_message(Dialog *d, MessageId message_id) {
@ -14044,6 +14053,9 @@ void MessagesManager::on_get_message_link_dialog(MessageLinkInfo &&info, Promise
force_create_dialog(dialog_id, "on_get_message_link_dialog");
} else {
dialog_id = resolve_dialog_username(info.username);
if (dialog_id.is_valid()) {
force_create_dialog(dialog_id, "on_get_message_link_dialog", true);
}
}
Dialog *d = get_dialog_force(dialog_id);
if (d == nullptr) {
@ -17484,8 +17496,10 @@ Result<int32> MessagesManager::get_message_schedule_date(
}
switch (scheduling_state->get_id()) {
case td_api::messageSchedulingStateSendWhenOnline::ID:
return static_cast<int32>(SCHEDULE_WHEN_ONLINE_DATE);
case td_api::messageSchedulingStateSendWhenOnline::ID: {
auto send_date = SCHEDULE_WHEN_ONLINE_DATE;
return send_date;
}
case td_api::messageSchedulingStateSendAtDate::ID: {
auto send_at_date = td_api::move_object_as<td_api::messageSchedulingStateSendAtDate>(scheduling_state);
auto send_date = send_at_date->send_date_;
@ -17506,7 +17520,7 @@ Result<int32> MessagesManager::get_message_schedule_date(
}
}
tl_object_ptr<td_api::MessageSendingState> MessagesManager::get_message_sending_state_object(const Message *m) {
tl_object_ptr<td_api::MessageSendingState> MessagesManager::get_message_sending_state_object(const Message *m) const {
CHECK(m != nullptr);
if (m->message_id.is_yet_unsent()) {
return td_api::make_object<td_api::messageSendingStatePending>();
@ -17649,6 +17663,7 @@ MessagesManager::Message *MessagesManager::get_message_to_send(
bool *need_update_dialog_pos, unique_ptr<MessageForwardInfo> forward_info, bool is_copy) {
CHECK(d != nullptr);
CHECK(!reply_to_message_id.is_scheduled());
CHECK(content != nullptr);
bool is_scheduled = options.schedule_date != 0;
DialogId dialog_id = d->dialog_id;
@ -17761,8 +17776,8 @@ Status MessagesManager::can_send_message(DialogId dialog_id) const {
return Status::OK();
}
Status MessagesManager::can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward,
bool is_via_bot) const {
Status MessagesManager::can_send_message_content(DialogId dialog_id, const MessageContent *content,
bool is_forward) const {
auto dialog_type = dialog_id.get_type();
int32 secret_chat_layer = std::numeric_limits<int32>::max();
if (dialog_type == DialogType::SecretChat) {
@ -17780,8 +17795,9 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
auto content_type = content->get_type();
switch (dialog_type) {
case DialogType::User:
if (content_type == MessageContentType::Poll && !is_forward) {
return Status::Error(400, "Polls can't be sent to private chats");
if (content_type == MessageContentType::Poll && !is_forward && !td_->auth_manager_->is_bot() &&
!td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
return Status::Error(400, "Polls can't be sent to the private chat");
}
break;
case DialogType::Chat:
@ -17832,31 +17848,13 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
}
break;
case MessageContentType::Game:
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
// ok
break;
case DialogType::Channel: {
auto channel_type = td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id());
if (channel_type == ChannelType::Broadcast) {
// return Status::Error(400, "Games can't be sent to channel chats");
}
break;
}
case DialogType::SecretChat:
return Status::Error(400, "Games can't be sent to secret chats");
case DialogType::None:
default:
UNREACHABLE();
if (is_broadcast_channel(dialog_id)) {
// return Status::Error(400, "Games can't be sent to channel chats");
}
if (!can_send_games) {
return Status::Error(400, "Not enough rights to send games to the chat");
}
if (!is_forward && !is_via_bot && !get_message_content_game_bot_user_id(content).is_valid()) {
return Status::Error(400, "Games can't be copied");
}
break;
case MessageContentType::Invoice:
if (!is_forward) {
@ -17893,6 +17891,9 @@ Status MessagesManager::can_send_message_content(DialogId dialog_id, const Messa
if (!can_send_polls) {
return Status::Error(400, "Not enough rights to send polls to the chat");
}
if (!get_message_content_poll_is_anonymous(td_, content) && is_broadcast_channel(dialog_id)) {
return Status::Error(400, "Non-anonymous polls can't be sent to channel chats");
}
break;
case MessageContentType::Sticker:
if (!can_send_stickers) {
@ -18241,9 +18242,10 @@ Result<MessageId> MessagesManager::send_message(DialogId dialog_id, MessageId re
// there must be no errors after get_message_to_send call
bool need_update_dialog_pos = false;
Message *m = get_message_to_send(d, get_reply_to_message_id(d, reply_to_message_id), send_message_options,
dup_message_content(td_, dialog_id, message_content.content.get(), false),
&need_update_dialog_pos, nullptr, message_content.via_bot_user_id.is_valid());
Message *m = get_message_to_send(
d, get_reply_to_message_id(d, reply_to_message_id), send_message_options,
dup_message_content(td_, dialog_id, message_content.content.get(), MessageContentDupType::Send),
&need_update_dialog_pos, nullptr, message_content.via_bot_user_id.is_valid());
m->reply_markup = std::move(message_reply_markup);
m->via_bot_user_id = message_content.via_bot_user_id;
m->disable_web_page_preview = message_content.disable_web_page_preview;
@ -18301,8 +18303,9 @@ Result<InputMessageContent> MessagesManager::process_input_message_content(
return Status::Error(400, "Can't copy message");
}
unique_ptr<MessageContent> content =
dup_message_content(td_, dialog_id, copied_message->content.get(), true, input_message->remove_caption_);
unique_ptr<MessageContent> content = dup_message_content(
td_, dialog_id, copied_message->content.get(),
input_message->remove_caption_ ? MessageContentDupType::CopyWithoutCaption : MessageContentDupType::Copy);
if (content == nullptr) {
return Status::Error(400, "Can't copy message content");
}
@ -18428,9 +18431,10 @@ Result<vector<MessageId>> MessagesManager::send_message_group(
vector<MessageId> result;
bool need_update_dialog_pos = false;
for (auto &message_content : message_contents) {
Message *m = get_message_to_send(d, reply_to_message_id, send_message_options,
dup_message_content(td_, dialog_id, message_content.first.get(), false),
&need_update_dialog_pos);
Message *m = get_message_to_send(
d, reply_to_message_id, send_message_options,
dup_message_content(td_, dialog_id, message_content.first.get(), MessageContentDupType::Send),
&need_update_dialog_pos);
result.push_back(m->message_id);
auto ttl = message_content.second;
if (ttl > 0) {
@ -18527,7 +18531,7 @@ void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vect
} else {
auto input_media = get_input_media(content, td_, m->ttl, false);
if (input_media == nullptr) {
if (content_type == MessageContentType::Game) {
if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll) {
return;
}
if (content_type == MessageContentType::Photo) {
@ -19170,12 +19174,13 @@ Result<MessageId> MessagesManager::send_inline_query_result_message(DialogId dia
}
TRY_STATUS(can_use_send_message_options(send_message_options, content->message_content, 0));
TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false, true));
TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false));
bool need_update_dialog_pos = false;
Message *m = get_message_to_send(d, get_reply_to_message_id(d, reply_to_message_id), send_message_options,
dup_message_content(td_, dialog_id, content->message_content.get(), false),
&need_update_dialog_pos, nullptr, true);
Message *m = get_message_to_send(
d, get_reply_to_message_id(d, reply_to_message_id), send_message_options,
dup_message_content(td_, dialog_id, content->message_content.get(), MessageContentDupType::SendViaBot),
&need_update_dialog_pos, nullptr, true);
m->hide_via_bot = hide_via_bot;
if (!hide_via_bot) {
m->via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id);
@ -19420,7 +19425,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo
return false;
}
bool MessagesManager::can_resend_message(const Message *m) {
bool MessagesManager::can_resend_message(const Message *m) const {
if (m->send_error_code != 429 && m->send_error_message != "Message is too old to be re-sent automatically" &&
m->send_error_message != "SCHEDULE_TOO_MUCH") {
return false;
@ -19435,11 +19440,12 @@ bool MessagesManager::can_resend_message(const Message *m) {
auto content_type = m->content->get_type();
if (m->via_bot_user_id.is_valid() || m->hide_via_bot) {
// via bot message
if (content_type == MessageContentType::Game &&
!get_message_content_game_bot_user_id(m->content.get()).is_valid()) {
// TODO implement resending via_bot messages other than games
if (!can_have_input_media(td_, m->content.get())) {
return false;
}
// resend via_bot message as an ordinary message if error code is 429
// TODO support other error codes
}
if (content_type == MessageContentType::ChatSetTtl || content_type == MessageContentType::ScreenshotTaken) {
@ -19739,7 +19745,8 @@ void MessagesManager::edit_message_media(FullMessageId full_message_id,
cancel_edit_message_media(dialog_id, m, "Cancelled by new editMessageMedia request");
m->edited_content = dup_message_content(td_, dialog_id, content.content.get(), false);
m->edited_content = dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send);
CHECK(m->edited_content != nullptr);
m->edited_reply_markup = r_new_reply_markup.move_as_ok();
m->edit_generation = ++current_message_edit_generation_;
m->edit_promise = std::move(promise);
@ -20658,8 +20665,9 @@ Result<vector<MessageId>> MessagesManager::forward_messages(DialogId to_dialog_i
}
bool need_copy = !message_id.is_server() || to_secret || send_copy;
unique_ptr<MessageContent> content =
dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), true, need_copy && remove_caption);
auto type = need_copy ? (remove_caption ? MessageContentDupType::CopyWithoutCaption : MessageContentDupType::Copy)
: MessageContentDupType::Forward;
unique_ptr<MessageContent> content = dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), type);
if (content == nullptr) {
LOG(INFO) << "Can't forward " << message_id;
continue;
@ -20677,17 +20685,13 @@ Result<vector<MessageId>> MessagesManager::forward_messages(DialogId to_dialog_i
continue;
}
auto content_type = content->get_type();
bool is_game = content_type == MessageContentType::Game;
if (need_copy) {
if (is_game && !get_message_content_game_bot_user_id(content.get()).is_valid()) {
LOG(INFO) << "Can't copy game from " << message_id;
continue;
}
copied_messages.push_back({std::move(content), forwarded_message->disable_web_page_preview, i});
continue;
}
auto content_type = content->get_type();
bool is_game = content_type == MessageContentType::Game;
unique_ptr<MessageForwardInfo> forward_info;
if (!is_game && content_type != MessageContentType::Audio) {
DialogId saved_from_dialog_id;
@ -20889,7 +20893,8 @@ Result<vector<MessageId>> MessagesManager::resend_messages(DialogId dialog_id, v
const Message *m = get_message(d, message_id);
CHECK(m != nullptr);
unique_ptr<MessageContent> content = dup_message_content(td_, dialog_id, m->content.get(), false);
unique_ptr<MessageContent> content =
dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send);
if (content == nullptr) {
LOG(INFO) << "Can't resend " << m->message_id;
continue;
@ -20901,13 +20906,6 @@ Result<vector<MessageId>> MessagesManager::resend_messages(DialogId dialog_id, v
continue;
}
if (content->get_type() == MessageContentType::Game &&
!get_message_content_game_bot_user_id(content.get()).is_valid()) {
// must not happen
LOG(ERROR) << "Can't resend game from " << m->message_id;
continue;
}
new_contents[i] = std::move(content);
if (m->media_album_id != 0) {
@ -24253,7 +24251,7 @@ void MessagesManager::send_dialog_action(DialogId dialog_id, const tl_object_ptr
return promise.set_value(Unit());
}
if (!td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_user_status_exact(user_id)) {
return promise.set_value(Unit());
// return promise.set_value(Unit());
}
}
@ -26299,8 +26297,8 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false,
source);
} else {
// if non-scheduled outgoing message has id one greater than last_read_inbox_message_id then definitely there are no
// unread incoming messages before it
// if non-scheduled outgoing message has id one greater than last_read_inbox_message_id,
// then definitely there are no unread incoming messages before it
if (message_id.is_server() && d->last_read_inbox_message_id.is_valid() &&
d->last_read_inbox_message_id.is_server() &&
message_id.get_server_message_id().get() == d->last_read_inbox_message_id.get_server_message_id().get() + 1) {
@ -26411,6 +26409,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
update_sent_message_contents(dialog_id, m);
update_used_hashtags(dialog_id, m);
update_top_dialogs(dialog_id, m);
cancel_user_dialog_action(dialog_id, m);
}
Message *result_message = treap_insert_message(&d->messages, std::move(message));
@ -26749,7 +26748,10 @@ void MessagesManager::delete_message_files(DialogId dialog_id, const Message *m)
}
bool MessagesManager::need_delete_file(FullMessageId full_message_id, FileId file_id) const {
auto full_message_ids = td_->file_reference_manager_->get_some_message_file_sources(file_id);
auto main_file_id = td_->file_manager_->get_file_view(file_id).file_id();
auto full_message_ids = td_->file_reference_manager_->get_some_message_file_sources(main_file_id);
LOG(INFO) << "Receive " << full_message_ids << " as sources for file " << main_file_id << "/" << file_id << " from "
<< full_message_id;
for (auto other_full_messsage_id : full_message_ids) {
if (other_full_messsage_id != full_message_id) {
return false;
@ -27237,6 +27239,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr
}
if (is_edited && !td_->auth_manager_->is_bot()) {
d->last_edited_message_id = message_id;
send_update_message_edited(dialog_id, old_message);
}
@ -28252,7 +28255,8 @@ void MessagesManager::update_last_dialog_date(FolderId folder_id) {
list.last_dialog_date_ = list.last_server_dialog_date_; // std::min
CHECK(old_last_dialog_date <= list.last_dialog_date_);
LOG(INFO) << "Update last dialog date from " << old_last_dialog_date << " to " << list.last_dialog_date_;
LOG(INFO) << "Update last dialog date in " << folder_id << " from " << old_last_dialog_date << " to "
<< list.last_dialog_date_;
LOG(INFO) << "Know about " << list.ordered_server_dialogs_.size() << " chats";
if (old_last_dialog_date != list.last_dialog_date_) {
@ -29184,6 +29188,9 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) {
MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog_id, unique_ptr<Message> &&m,
uint64 logevent_id) {
CHECK(logevent_id != 0);
CHECK(m != nullptr);
CHECK(m->content != nullptr);
Dialog *d = get_dialog_force(dialog_id);
if (d == nullptr) {
LOG(ERROR) << "Can't find " << dialog_id << " to resend a message";
@ -29266,7 +29273,7 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
add_message_dependencies(dependencies, dialog_id, m.get());
resolve_dependencies_force(dependencies);
m->content = dup_message_content(td_, dialog_id, m->content.get(), false);
m->content = dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send);
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
@ -29331,7 +29338,7 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
add_message_dependencies(dependencies, dialog_id, m.get());
resolve_dependencies_force(dependencies);
m->content = dup_message_content(td_, dialog_id, m->content.get(), false);
m->content = dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::SendViaBot);
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
@ -29420,7 +29427,8 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
set_message_id(m, get_next_yet_unsent_message_id(to_dialog));
m->date = now;
}
m->content = dup_message_content(td_, to_dialog_id, m->content.get(), true);
m->content = dup_message_content(td_, to_dialog_id, m->content.get(), MessageContentDupType::Forward);
CHECK(m->content != nullptr);
m->have_previous = true;
m->have_next = true;
@ -30124,6 +30132,28 @@ void MessagesManager::set_poll_answer(FullMessageId full_message_id, vector<int3
set_message_content_poll_answer(td_, m->content.get(), full_message_id, std::move(option_ids), std::move(promise));
}
void MessagesManager::get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
Promise<std::pair<int32, vector<UserId>>> &&promise) {
auto m = get_message_force(full_message_id, "get_poll_voters");
if (m == nullptr) {
return promise.set_error(Status::Error(5, "Message not found"));
}
if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
return promise.set_error(Status::Error(3, "Can't access the chat"));
}
if (m->content->get_type() != MessageContentType::Poll) {
return promise.set_error(Status::Error(5, "Message is not a poll"));
}
if (m->message_id.is_scheduled()) {
return promise.set_error(Status::Error(5, "Can't get poll results from scheduled messages"));
}
if (!m->message_id.is_server()) {
return promise.set_error(Status::Error(5, "Poll results can't be received"));
}
get_message_content_poll_voters(td_, m->content.get(), full_message_id, option_id, offset, limit, std::move(promise));
}
void MessagesManager::stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
auto m = get_message_force(full_message_id, "stop_poll");

View File

@ -803,6 +803,9 @@ class MessagesManager : public Actor {
void set_poll_answer(FullMessageId full_message_id, vector<int32> &&option_ids, Promise<Unit> &&promise);
void get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
Promise<std::pair<int32, vector<UserId>>> &&promise);
void stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise);
@ -1060,6 +1063,7 @@ class MessagesManager : public Actor {
int32 pending_last_message_date = 0;
MessageId pending_last_message_id;
MessageId max_notification_message_id;
MessageId last_edited_message_id;
uint32 scheduled_messages_sync_generation = 0;
MessageId max_added_message_id;
@ -1526,10 +1530,10 @@ class MessagesManager : public Actor {
Status can_send_message(DialogId dialog_id) const TD_WARN_UNUSED_RESULT;
Status can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward,
bool is_via_bot = false) const TD_WARN_UNUSED_RESULT;
Status can_send_message_content(DialogId dialog_id, const MessageContent *content,
bool is_forward) const TD_WARN_UNUSED_RESULT;
static bool can_resend_message(const Message *m);
bool can_resend_message(const Message *m) const;
bool can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, bool only_reply_markup = false) const;
@ -1928,7 +1932,7 @@ class MessagesManager : public Actor {
static Result<int32> get_message_schedule_date(td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state);
static tl_object_ptr<td_api::MessageSendingState> get_message_sending_state_object(const Message *m);
tl_object_ptr<td_api::MessageSendingState> get_message_sending_state_object(const Message *m) const;
static tl_object_ptr<td_api::MessageSchedulingState> get_message_scheduling_state_object(int32 send_date);

View File

@ -2833,6 +2833,11 @@ string NotificationManager::convert_loc_key(const string &loc_key) {
return "MESSAGE_POLL";
}
break;
case 'Q':
if (loc_key == "MESSAGE_QUIZ") {
return "MESSAGE_QUIZ";
}
break;
case 'R':
if (loc_key == "MESSAGE_ROUND") {
return "MESSAGE_VIDEO_NOTE";
@ -2864,6 +2869,9 @@ string NotificationManager::convert_loc_key(const string &loc_key) {
if (loc_key == "PINNED_AUDIO") {
return "PINNED_MESSAGE_VOICE_NOTE";
}
if (loc_key == "PINNED_QUIZ") {
return "PINNED_MESSAGE_QUIZ";
}
if (loc_key == "CHAT_RETURNED") {
return "MESSAGE_CHAT_ADD_MEMBERS_RETURNED";
}

View File

@ -262,7 +262,12 @@ class NotificationTypePushMessage : public NotificationType {
return td_api::make_object<td_api::pushMessageContentMediaAlbum>(to_integer<int32>(arg), true, false);
}
if (key == "MESSAGE_POLL") {
return td_api::make_object<td_api::pushMessageContentPoll>(arg, is_pinned);
return td_api::make_object<td_api::pushMessageContentPoll>(arg, true, is_pinned);
}
break;
case 'Q':
if (key == "MESSAGE_QUIZ") {
return td_api::make_object<td_api::pushMessageContentPoll>(arg, false, is_pinned);
}
break;
case 'S':

View File

@ -734,7 +734,7 @@ void PasswordManager::drop_cached_secret() {
LOG(INFO) << "Drop passport secret";
secret_ = optional<secure_storage::Secret>();
}
/*
void PasswordManager::get_ton_wallet_password_salt(Promise<td_api::object_ptr<td_api::tonWalletPasswordSalt>> promise) {
if (!ton_wallet_password_salt_.empty()) {
return promise.set_value(td_api::make_object<td_api::tonWalletPasswordSalt>(ton_wallet_password_salt_));
@ -768,7 +768,7 @@ void PasswordManager::on_get_ton_wallet_password_salt(
}
}
}
*/
void PasswordManager::timeout_expired() {
if (Time::now() >= secret_expire_date_) {
drop_cached_secret();

View File

@ -89,7 +89,7 @@ class PasswordManager : public NetQueryCallback {
static TempPasswordState get_temp_password_state_sync();
void get_ton_wallet_password_salt(Promise<td_api::object_ptr<td_api::tonWalletPasswordSalt>> promise);
// void get_ton_wallet_password_salt(Promise<td_api::object_ptr<td_api::tonWalletPasswordSalt>> promise);
private:
static constexpr size_t MIN_NEW_SALT_SIZE = 8;
@ -162,8 +162,8 @@ class PasswordManager : public NetQueryCallback {
int32 last_code_length_ = 0;
string ton_wallet_password_salt_;
vector<Promise<td_api::object_ptr<td_api::tonWalletPasswordSalt>>> get_ton_wallet_password_salt_queries_;
// string ton_wallet_password_salt_;
// vector<Promise<td_api::object_ptr<td_api::tonWalletPasswordSalt>>> get_ton_wallet_password_salt_queries_;
static Result<secure_storage::Secret> decrypt_secure_secret(
Slice password, tl_object_ptr<telegram_api::SecurePasswordKdfAlgo> algo_ptr, Slice secret, int64 secret_id);
@ -191,7 +191,7 @@ class PasswordManager : public NetQueryCallback {
Promise<TempPasswordState> promise);
void on_finish_create_temp_password(Result<TempPasswordState> result, bool dummy);
void on_get_ton_wallet_password_salt(Result<telegram_api::object_ptr<telegram_api::wallet_secretSalt>> result);
// void on_get_ton_wallet_password_salt(Result<telegram_api::object_ptr<telegram_api::wallet_secretSalt>> result);
void on_result(NetQueryPtr query) override;

View File

@ -486,7 +486,7 @@ class ClearSavedInfoQuery : public Td::ResultHandler {
promise_.set_error(std::move(status));
}
};
/*
class SendLiteRequestQuery : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::tonLiteServerResponse>> promise_;
@ -514,7 +514,7 @@ class SendLiteRequestQuery : public Td::ResultHandler {
promise_.set_error(std::move(status));
}
};
*/
bool operator==(const LabeledPricePart &lhs, const LabeledPricePart &rhs) {
return lhs.label == rhs.label && lhs.amount == rhs.amount;
}
@ -896,9 +896,9 @@ void delete_saved_order_info(Promise<Unit> &&promise) {
void delete_saved_credentials(Promise<Unit> &&promise) {
G()->td().get_actor_unsafe()->create_handler<ClearSavedInfoQuery>(std::move(promise))->send(true, false);
}
/*
void send_ton_lite_server_request(Slice request, Promise<td_api::object_ptr<td_api::tonLiteServerResponse>> &&promise) {
G()->td().get_actor_unsafe()->create_handler<SendLiteRequestQuery>(std::move(promise))->send(BufferSlice{request});
}
*/
} // namespace td

View File

@ -154,6 +154,6 @@ void delete_saved_order_info(Promise<Unit> &&promise);
void delete_saved_credentials(Promise<Unit> &&promise);
void send_ton_lite_server_request(Slice request, Promise<td_api::object_ptr<td_api::tonLiteServerResponse>> &&promise);
// void send_ton_lite_server_request(Slice request, Promise<td_api::object_ptr<td_api::tonLiteServerResponse>> &&promise);
} // namespace td

View File

@ -8,6 +8,7 @@
#include "td/telegram/AccessRights.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Dependencies.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/Global.h"
@ -21,6 +22,7 @@
#include "td/telegram/StateManager.h"
#include "td/telegram/Td.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/telegram_api.hpp"
#include "td/telegram/UpdatesManager.h"
#include "td/db/binlog/BinlogEvent.h"
@ -39,6 +41,7 @@
#include <algorithm>
#include <limits>
#include <unordered_map>
namespace td {
@ -57,8 +60,7 @@ class GetPollResultsQuery : public Td::ResultHandler {
auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
LOG(INFO) << "Can't reget poll, because have no read access to " << dialog_id_;
// do not signal error to PollManager
return;
return promise_.set_value(nullptr);
}
auto message_id = full_message_id.get_message_id().get_server_message_id().get();
@ -84,6 +86,54 @@ class GetPollResultsQuery : public Td::ResultHandler {
}
};
class GetPollVotersQuery : public Td::ResultHandler {
Promise<tl_object_ptr<telegram_api::messages_votesList>> promise_;
PollId poll_id_;
DialogId dialog_id_;
public:
explicit GetPollVotersQuery(Promise<tl_object_ptr<telegram_api::messages_votesList>> &&promise)
: promise_(std::move(promise)) {
}
void send(PollId poll_id, FullMessageId full_message_id, BufferSlice &&option, const string &offset, int32 limit) {
poll_id_ = poll_id;
dialog_id_ = full_message_id.get_dialog_id();
auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
LOG(INFO) << "Can't get poll, because have no read access to " << dialog_id_;
return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
CHECK(!option.empty());
int32 flags = telegram_api::messages_getPollVotes::OPTION_MASK;
if (!offset.empty()) {
flags |= telegram_api::messages_getPollVotes::OFFSET_MASK;
}
auto message_id = full_message_id.get_message_id().get_server_message_id().get();
send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getPollVotes(
flags, std::move(input_peer), message_id, std::move(option), offset, limit))));
}
void on_result(uint64 id, BufferSlice packet) override {
auto result_ptr = fetch_result<telegram_api::messages_getPollVotes>(packet);
if (result_ptr.is_error()) {
return on_error(id, result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
void on_error(uint64 id, Status status) override {
if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPollVotersQuery") &&
status.message() != "MESSAGE_ID_INVALID") {
LOG(ERROR) << "Receive " << status << ", while trying to get voters of " << poll_id_;
}
promise_.set_error(std::move(status));
}
};
class SetPollAnswerActor : public NetActorOnce {
Promise<tl_object_ptr<telegram_api::Updates>> promise_;
DialogId dialog_id_;
@ -151,7 +201,8 @@ class StopPollActor : public NetActorOnce {
auto message_id = full_message_id.get_message_id().get_server_message_id().get();
auto poll = telegram_api::make_object<telegram_api::poll>();
poll->flags_ |= telegram_api::poll::CLOSED_MASK;
auto input_media = telegram_api::make_object<telegram_api::inputMediaPoll>(std::move(poll));
auto input_media =
telegram_api::make_object<telegram_api::inputMediaPoll>(0, std::move(poll), vector<BufferSlice>());
auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_editMessage(
flags, false /*ignored*/, std::move(input_peer), message_id, string(), std::move(input_media),
std::move(input_reply_markup), vector<tl_object_ptr<telegram_api::MessageEntity>>(), 0)));
@ -287,6 +338,9 @@ void PollManager::on_load_poll_from_database(PollId poll_id, string value) {
if (status.is_error()) {
LOG(FATAL) << status << ": " << format::as_hex_dump<4>(Slice(value));
}
for (auto &user_id : result->recent_voter_user_ids) {
td_->contacts_manager_->have_user_force(user_id);
}
polls_[poll_id] = std::move(result);
}
}
@ -473,15 +527,26 @@ td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id, co
poll_options[i]->vote_percentage_ = vote_percentage[i];
}
}
return td_api::make_object<td_api::poll>(poll_id.get(), poll->question, std::move(poll_options), total_voter_count,
poll->is_closed);
td_api::object_ptr<td_api::PollType> poll_type;
if (poll->is_quiz) {
auto correct_option_id = is_local_poll_id(poll_id) ? -1 : poll->correct_option_id;
poll_type = td_api::make_object<td_api::pollTypeQuiz>(correct_option_id);
} else {
poll_type = td_api::make_object<td_api::pollTypeRegular>(poll->allow_multiple_answers);
}
return td_api::make_object<td_api::poll>(
poll_id.get(), poll->question, std::move(poll_options), total_voter_count,
td_->contacts_manager_->get_user_ids_object(poll->recent_voter_user_ids, "get_poll_object"), poll->is_anonymous,
std::move(poll_type), poll->is_closed);
}
telegram_api::object_ptr<telegram_api::pollAnswer> PollManager::get_input_poll_option(const PollOption &poll_option) {
return telegram_api::make_object<telegram_api::pollAnswer>(poll_option.text, BufferSlice(poll_option.data));
}
PollId PollManager::create_poll(string &&question, vector<string> &&options) {
PollId PollManager::create_poll(string &&question, vector<string> &&options, bool is_anonymous,
bool allow_multiple_answers, bool is_quiz, int32 correct_option_id, bool is_closed) {
auto poll = make_unique<Poll>();
poll->question = std::move(question);
int pos = '0';
@ -491,6 +556,11 @@ PollId PollManager::create_poll(string &&question, vector<string> &&options) {
option.data = string(1, narrow_cast<char>(pos++));
poll->options.push_back(std::move(option));
}
poll->is_anonymous = is_anonymous;
poll->allow_multiple_answers = allow_multiple_answers;
poll->is_quiz = is_quiz;
poll->correct_option_id = correct_option_id;
poll->is_closed = is_closed;
PollId poll_id(--current_local_poll_id_);
CHECK(is_local_poll_id(poll_id));
@ -536,6 +606,12 @@ bool PollManager::get_poll_is_closed(PollId poll_id) const {
return poll->is_closed;
}
bool PollManager::get_poll_is_anonymous(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
return poll->is_anonymous;
}
string PollManager::get_poll_search_text(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
@ -550,11 +626,11 @@ string PollManager::get_poll_search_text(PollId poll_id) const {
void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
Promise<Unit> &&promise) {
if (option_ids.size() > 1) {
return promise.set_error(Status::Error(400, "Can't choose more than 1 option"));
}
std::sort(option_ids.begin(), option_ids.end());
option_ids.erase(std::unique(option_ids.begin(), option_ids.end()), option_ids.end());
if (is_local_poll_id(poll_id)) {
return promise.set_error(Status::Error(5, "Poll can't be answered"));
return promise.set_error(Status::Error(400, "Poll can't be answered"));
}
auto poll = get_poll(poll_id);
@ -562,13 +638,33 @@ void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id,
if (poll->is_closed) {
return promise.set_error(Status::Error(400, "Can't answer closed poll"));
}
if (!poll->allow_multiple_answers && option_ids.size() > 1) {
return promise.set_error(Status::Error(400, "Can't choose more than 1 option in the poll"));
}
if (poll->is_quiz && option_ids.empty()) {
return promise.set_error(Status::Error(400, "Can't retract vote in a quiz"));
}
std::unordered_map<size_t, int> affected_option_ids;
vector<string> options;
for (auto &option_id : option_ids) {
auto index = static_cast<size_t>(option_id);
if (index >= poll->options.size()) {
return promise.set_error(Status::Error(400, "Invalid option id specified"));
return promise.set_error(Status::Error(400, "Invalid option ID specified"));
}
options.push_back(poll->options[index].data);
affected_option_ids[index]++;
}
for (size_t option_index = 0; option_index < poll->options.size(); option_index++) {
if (poll->options[option_index].is_chosen) {
affected_option_ids[option_index]++;
}
}
for (auto it : affected_option_ids) {
if (it.second == 1) {
invalidate_poll_option_voters(poll, poll_id, it.first);
}
}
do_set_poll_answer(poll_id, full_message_id, std::move(options), 0, std::move(promise));
@ -701,6 +797,184 @@ void PollManager::on_set_poll_answer(PollId poll_id, uint64 generation,
}
}
void PollManager::invalidate_poll_voters(const Poll *poll, PollId poll_id) {
if (poll->is_anonymous) {
return;
}
auto it = poll_voters_.find(poll_id);
if (it == poll_voters_.end()) {
return;
}
for (auto &voters : it->second) {
voters.was_invalidated = true;
}
}
void PollManager::invalidate_poll_option_voters(const Poll *poll, PollId poll_id, size_t option_index) {
if (poll->is_anonymous) {
return;
}
auto it = poll_voters_.find(poll_id);
if (it == poll_voters_.end()) {
return;
}
auto &poll_voters = it->second;
CHECK(poll_voters.size() == poll->options.size());
CHECK(option_index < poll_voters.size());
poll_voters[option_index].was_invalidated = true;
}
PollManager::PollOptionVoters &PollManager::get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id) {
auto &poll_voters = poll_voters_[poll_id];
if (poll_voters.empty()) {
poll_voters.resize(poll->options.size());
}
auto index = narrow_cast<size_t>(option_id);
CHECK(index < poll_voters.size());
return poll_voters[index];
}
void PollManager::get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset,
int32 limit, Promise<std::pair<int32, vector<UserId>>> &&promise) {
if (is_local_poll_id(poll_id)) {
return promise.set_error(Status::Error(400, "Poll results can't be received"));
}
if (offset < 0) {
return promise.set_error(Status::Error(400, "Invalid offset specified"));
}
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
if (limit > MAX_GET_POLL_VOTERS) {
limit = MAX_GET_POLL_VOTERS;
}
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
if (option_id < 0 || static_cast<size_t>(option_id) >= poll->options.size()) {
return promise.set_error(Status::Error(400, "Invalid option ID specified"));
}
if (poll->is_anonymous) {
return promise.set_error(Status::Error(400, "Poll is anonymous"));
}
auto &voters = get_poll_option_voters(poll, poll_id, option_id);
if (voters.pending_queries.empty() && voters.was_invalidated && offset == 0) {
voters.voter_user_ids.clear();
voters.next_offset.clear();
voters.was_invalidated = false;
}
auto cur_offset = narrow_cast<int32>(voters.voter_user_ids.size());
if (offset > cur_offset) {
return promise.set_error(Status::Error(400, "Too big offset specified, voters can be received only consequently"));
}
if (offset < cur_offset) {
vector<UserId> result;
for (int32 i = offset; i != cur_offset && i - offset < limit; i++) {
result.push_back(voters.voter_user_ids[i]);
}
return promise.set_value({poll->options[option_id].voter_count, std::move(result)});
}
if (poll->options[option_id].voter_count == 0 || (voters.next_offset.empty() && cur_offset > 0)) {
return promise.set_value({0, vector<UserId>()});
}
voters.pending_queries.push_back(std::move(promise));
if (voters.pending_queries.size() > 1) {
return;
}
auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), poll_id, option_id, limit](
Result<tl_object_ptr<telegram_api::messages_votesList>> &&result) {
send_closure(actor_id, &PollManager::on_get_poll_voters, poll_id, option_id, limit, std::move(result));
});
td_->create_handler<GetPollVotersQuery>(std::move(query_promise))
->send(poll_id, full_message_id, BufferSlice(poll->options[option_id].data), voters.next_offset, max(limit, 15));
}
void PollManager::on_get_poll_voters(PollId poll_id, int32 option_id, int32 limit,
Result<tl_object_ptr<telegram_api::messages_votesList>> &&result) {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
if (option_id < 0 || static_cast<size_t>(option_id) >= poll->options.size()) {
LOG(ERROR) << "Can't process voters for option " << option_id << " in " << poll_id << ", because it has only "
<< poll->options.size() << " options";
return;
}
if (poll->is_anonymous) {
// just in case
result = Status::Error(400, "Poll is anonymous");
}
auto &voters = get_poll_option_voters(poll, poll_id, option_id);
auto promises = std::move(voters.pending_queries);
CHECK(!promises.empty());
if (result.is_error()) {
for (auto &promise : promises) {
promise.set_error(result.error().clone());
}
return;
}
auto vote_list = result.move_as_ok();
td_->contacts_manager_->on_get_users(std::move(vote_list->users_), "on_get_poll_voters");
voters.next_offset = std::move(vote_list->next_offset_);
if (poll->options[option_id].voter_count != vote_list->count_) {
++current_generation_;
update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
}
vector<UserId> user_ids;
for (auto &user_vote : vote_list->votes_) {
UserId user_id;
downcast_call(*user_vote, [&user_id](auto &voter) { user_id = UserId(voter.user_id_); });
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive " << user_id << " as voter in " << poll_id;
continue;
}
switch (user_vote->get_id()) {
case telegram_api::messageUserVote::ID: {
auto voter = telegram_api::move_object_as<telegram_api::messageUserVote>(user_vote);
if (voter->option_ != poll->options[option_id].data) {
continue;
}
user_ids.push_back(user_id);
break;
}
case telegram_api::messageUserVoteInputOption::ID:
user_ids.push_back(user_id);
break;
case telegram_api::messageUserVoteMultiple::ID: {
auto voter = telegram_api::move_object_as<telegram_api::messageUserVoteMultiple>(user_vote);
if (!td::contains(voter->options_, poll->options[option_id].data)) {
continue;
}
user_ids.push_back(user_id);
break;
}
}
}
voters.voter_user_ids.insert(voters.voter_user_ids.end(), user_ids.begin(), user_ids.end());
if (static_cast<int32>(user_ids.size()) > limit) {
user_ids.resize(limit);
}
for (auto &promise : promises) {
promise.set_value({vote_list->count_, vector<UserId>(user_ids)});
}
}
void PollManager::stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
if (is_local_poll_id(poll_id)) {
@ -809,16 +1083,19 @@ void PollManager::on_update_poll_timeout(PollId poll_id) {
void PollManager::on_get_poll_results(PollId poll_id, uint64 generation,
Result<tl_object_ptr<telegram_api::Updates>> result) {
if (result.is_error()) {
if (!get_poll_is_closed(poll_id) && !td_->auth_manager_->is_bot()) {
if (!get_poll_is_closed(poll_id) && !G()->close_flag() && !td_->auth_manager_->is_bot()) {
auto timeout = get_polling_timeout();
LOG(INFO) << "Schedule updating of " << poll_id << " in " << timeout;
update_poll_timeout_.add_timeout_in(poll_id.get(), timeout);
}
return;
}
if (result.ok() == nullptr) {
return;
}
if (generation != current_generation_) {
LOG(INFO) << "Receive possibly outdated result of " << poll_id << ", reget it";
if (!get_poll_is_closed(poll_id) && !td_->auth_manager_->is_bot()) {
if (!get_poll_is_closed(poll_id) && !G()->close_flag() && !td_->auth_manager_->is_bot()) {
update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
}
return;
@ -842,11 +1119,44 @@ void PollManager::on_online() {
}
}
bool PollManager::has_input_media(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
return !poll->is_quiz || poll->correct_option_id >= 0;
}
tl_object_ptr<telegram_api::InputMedia> PollManager::get_input_media(PollId poll_id) const {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
return telegram_api::make_object<telegram_api::inputMediaPoll>(telegram_api::make_object<telegram_api::poll>(
0, 0, false /* ignored */, poll->question, transform(poll->options, get_input_poll_option)));
int32 poll_flags = 0;
if (!poll->is_anonymous) {
poll_flags |= telegram_api::poll::PUBLIC_VOTERS_MASK;
}
if (poll->allow_multiple_answers) {
poll_flags |= telegram_api::poll::MULTIPLE_CHOICE_MASK;
}
if (poll->is_quiz) {
poll_flags |= telegram_api::poll::QUIZ_MASK;
}
if (poll->is_closed) {
poll_flags |= telegram_api::poll::CLOSED_MASK;
}
int32 flags = 0;
vector<BufferSlice> correct_answers;
if (poll->is_quiz) {
flags |= telegram_api::inputMediaPoll::CORRECT_ANSWERS_MASK;
CHECK(poll->correct_option_id >= 0);
CHECK(static_cast<size_t>(poll->correct_option_id) < poll->options.size());
correct_answers.push_back(BufferSlice(poll->options[poll->correct_option_id].data));
}
return telegram_api::make_object<telegram_api::inputMediaPoll>(
flags,
telegram_api::make_object<telegram_api::poll>(0, poll_flags, false /*ignored*/, false /*ignored*/,
false /*ignored*/, false /*ignored*/, poll->question,
transform(poll->options, get_input_poll_option)),
std::move(correct_answers));
}
vector<PollManager::PollOption> PollManager::get_poll_options(
@ -917,6 +1227,25 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
poll->is_closed = is_closed;
is_changed = true;
}
bool is_anonymous = (poll_server->flags_ & telegram_api::poll::PUBLIC_VOTERS_MASK) == 0;
if (is_anonymous != poll->is_anonymous) {
poll->is_anonymous = is_anonymous;
is_changed = true;
}
bool allow_multiple_answers = (poll_server->flags_ & telegram_api::poll::MULTIPLE_CHOICE_MASK) != 0;
bool is_quiz = (poll_server->flags_ & telegram_api::poll::QUIZ_MASK) != 0;
if (is_quiz && allow_multiple_answers) {
LOG(ERROR) << "Receive quiz " << poll_id << " allowing multiple answers";
allow_multiple_answers = false;
}
if (allow_multiple_answers != poll->allow_multiple_answers) {
poll->allow_multiple_answers = allow_multiple_answers;
is_changed = true;
}
if (is_quiz != poll->is_quiz) {
poll->is_quiz = is_quiz;
is_changed = true;
}
}
CHECK(poll_results != nullptr);
@ -930,9 +1259,12 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
}
is_changed = true;
}
for (auto &poll_result : poll_results->results_) {
int32 correct_option_id = -1;
for (size_t i = 0; i < poll_results->results_.size(); i++) {
auto &poll_result = poll_results->results_[i];
Slice data = poll_result->option_.as_slice();
for (auto &option : poll->options) {
for (size_t option_index = 0; option_index < poll->options.size(); option_index++) {
auto &option = poll->options[option_index];
if (option.data != data) {
continue;
}
@ -942,27 +1274,38 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
option.is_chosen = is_chosen;
is_changed = true;
}
bool is_correct = (poll_result->flags_ & telegram_api::pollAnswerVoters::CORRECT_MASK) != 0;
if (is_correct) {
if (correct_option_id != -1) {
LOG(ERROR) << "Receive more than 1 correct answers " << correct_option_id << " and " << i;
}
correct_option_id = static_cast<int32>(i);
}
} else {
correct_option_id = poll->correct_option_id;
}
if (poll_result->voters_ < 0) {
LOG(ERROR) << "Receive " << poll_result->voters_ << " voters for an option in " << poll_id;
poll_result->voters_ = 0;
}
if (option.is_chosen && poll_result->voters_ == 0) {
LOG(ERROR) << "Receive 0 voters for the chosen option in " << poll_id;
poll_result->voters_ = 1;
}
if (poll_result->voters_ > poll->total_voter_count) {
LOG(ERROR) << "Have only " << poll->total_voter_count << " poll voters, but there are " << poll_result->voters_
<< " voters for an option in " << poll_id;
poll->total_voter_count = poll_result->voters_;
}
auto max_voter_count = std::numeric_limits<int32>::max() / narrow_cast<int32>(poll->options.size()) - 2;
if (poll_result->voters_ > max_voter_count) {
LOG(ERROR) << "Have too much " << poll_result->voters_ << " poll voters for an option in " << poll_id;
poll_result->voters_ = max_voter_count;
}
if (poll_result->voters_ != option.voter_count) {
invalidate_poll_option_voters(poll, poll_id, option_index);
option.voter_count = poll_result->voters_;
if (option.voter_count < 0) {
LOG(ERROR) << "Receive " << option.voter_count << " voters for an option in " << poll_id;
option.voter_count = 0;
}
if (option.is_chosen && option.voter_count == 0) {
LOG(ERROR) << "Receive 0 voters for the chosen option in " << poll_id;
option.voter_count = 1;
}
if (option.voter_count > poll->total_voter_count) {
LOG(ERROR) << "Have only " << poll->total_voter_count << " poll voters, but there are " << option.voter_count
<< " voters for an option in " << poll_id;
poll->total_voter_count = option.voter_count;
}
auto max_voter_count = std::numeric_limits<int32>::max() / narrow_cast<int32>(poll->options.size()) - 2;
if (option.voter_count > max_voter_count) {
LOG(ERROR) << "Have too much " << option.voter_count << " poll voters for an option in " << poll_id;
option.voter_count = max_voter_count;
}
is_changed = true;
}
}
@ -978,8 +1321,36 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
poll->total_voter_count = max_total_voter_count;
}
}
if (poll->is_quiz) {
if (poll->correct_option_id != correct_option_id) {
poll->correct_option_id = correct_option_id;
is_changed = true;
}
} else if (correct_option_id != -1) {
LOG(ERROR) << "Receive correct option " << correct_option_id << " in non-quiz " << poll_id;
}
vector<UserId> recent_voter_user_ids;
if (!td_->auth_manager_->is_bot()) {
for (auto &user_id_int : poll_results->recent_voters_) {
UserId user_id(user_id_int);
if (user_id.is_valid()) {
recent_voter_user_ids.push_back(user_id);
} else {
LOG(ERROR) << "Receive " << user_id << " as recent voter in " << poll_id;
}
}
}
if (poll->is_anonymous && !recent_voter_user_ids.empty()) {
LOG(ERROR) << "Receive anonymous " << poll_id << " with recent voters " << recent_voter_user_ids;
recent_voter_user_ids.clear();
}
if (recent_voter_user_ids != poll->recent_voter_user_ids) {
poll->recent_voter_user_ids = std::move(recent_voter_user_ids);
invalidate_poll_voters(poll, poll_id);
is_changed = true;
}
if (!td_->auth_manager_->is_bot() && !poll->is_closed) {
if (!is_bot && !poll->is_closed) {
auto timeout = get_polling_timeout();
LOG(INFO) << "Schedule updating of " << poll_id << " in " << timeout;
update_poll_timeout_.set_timeout_in(poll_id.get(), timeout);
@ -994,6 +1365,35 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
return poll_id;
}
void PollManager::on_get_poll_vote(PollId poll_id, UserId user_id, vector<BufferSlice> &&options) {
if (!poll_id.is_valid()) {
LOG(ERROR) << "Receive updateMessagePollVote about invalid " << poll_id;
return;
}
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive updateMessagePollVote from invalid " << user_id;
return;
}
if (!td_->auth_manager_->is_bot()) {
return;
}
vector<int32> option_ids;
for (auto &option : options) {
auto slice = option.as_slice();
if (slice.size() != 1 || slice[0] < '0' || slice[0] > '9') {
LOG(ERROR) << "Receive updateMessagePollVote with unexpected option \"" << format::escaped(slice) << '"';
return;
}
option_ids.push_back(static_cast<int32>(slice[0] - '0'));
}
send_closure(G()->td(), &Td::send_update,
td_api::make_object<td_api::updatePollAnswer>(
poll_id.get(), td_->contacts_manager_->get_user_id_object(user_id, "on_get_poll_vote"),
std::move(option_ids)));
}
void PollManager::on_binlog_events(vector<BinlogEvent> &&events) {
for (auto &event : events) {
switch (event.type_) {

View File

@ -12,6 +12,7 @@
#include "td/telegram/ReplyMarkup.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"
@ -41,7 +42,8 @@ class PollManager : public Actor {
static bool is_local_poll_id(PollId poll_id);
PollId create_poll(string &&question, vector<string> &&options);
PollId create_poll(string &&question, vector<string> &&options, bool is_anonymous, bool allow_multiple_answers,
bool is_quiz, int32 correct_option_id, bool is_closed);
void register_poll(PollId poll_id, FullMessageId full_message_id);
@ -49,21 +51,30 @@ class PollManager : public Actor {
bool get_poll_is_closed(PollId poll_id) const;
bool get_poll_is_anonymous(PollId poll_id) const;
string get_poll_search_text(PollId poll_id) const;
void set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
Promise<Unit> &&promise);
void get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
Promise<std::pair<int32, vector<UserId>>> &&promise);
void stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise);
void stop_local_poll(PollId poll_id);
bool has_input_media(PollId poll_id) const;
tl_object_ptr<telegram_api::InputMedia> get_input_media(PollId poll_id) const;
PollId on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll> &&poll_server,
tl_object_ptr<telegram_api::pollResults> &&poll_results);
void on_get_poll_vote(PollId poll_id, UserId user_id, vector<BufferSlice> &&options);
td_api::object_ptr<td_api::poll> get_poll_object(PollId poll_id) const;
void on_binlog_events(vector<BinlogEvent> &&events);
@ -92,7 +103,12 @@ class PollManager : public Actor {
struct Poll {
string question;
vector<PollOption> options;
vector<UserId> recent_voter_user_ids;
int32 total_voter_count = 0;
int32 correct_option_id = -1;
bool is_anonymous = true;
bool allow_multiple_answers = false;
bool is_quiz = false;
bool is_closed = false;
template <class StorerT>
@ -101,6 +117,15 @@ class PollManager : public Actor {
void parse(ParserT &parser);
};
struct PollOptionVoters {
vector<UserId> voter_user_ids;
string next_offset;
vector<Promise<std::pair<int32, vector<UserId>>>> pending_queries;
bool was_invalidated = false; // the list needs to be invalidated when voters are changed
};
static constexpr int32 MAX_GET_POLL_VOTERS = 50; // server side limit
class SetPollAnswerLogEvent;
class StopPollLogEvent;
@ -148,6 +173,15 @@ class PollManager : public Actor {
void on_set_poll_answer(PollId poll_id, uint64 generation, Result<tl_object_ptr<telegram_api::Updates>> &&result);
void invalidate_poll_voters(const Poll *poll, PollId poll_id);
void invalidate_poll_option_voters(const Poll *poll, PollId poll_id, size_t option_index);
PollOptionVoters &get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id);
void on_get_poll_voters(PollId poll_id, int32 option_id, int32 limit,
Result<tl_object_ptr<telegram_api::messages_votesList>> &&result);
void do_stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
uint64 logevent_id, Promise<Unit> &&promise);
@ -168,6 +202,8 @@ class PollManager : public Actor {
};
std::unordered_map<PollId, PendingPollAnswer, PollIdHash> pending_answers_;
std::unordered_map<PollId, vector<PollOptionVoters>, PollIdHash> poll_voters_;
int64 current_local_poll_id_ = 0;
uint64 current_generation_ = 0;

View File

@ -7,6 +7,7 @@
#pragma once
#include "td/telegram/PollManager.h"
#include "td/telegram/Version.h"
#include "td/utils/common.h"
#include "td/utils/misc.h"
@ -41,25 +42,53 @@ void PollManager::PollOption::parse(ParserT &parser) {
template <class StorerT>
void PollManager::Poll::store(StorerT &storer) const {
using ::td::store;
bool is_public = !is_anonymous;
bool has_recent_voters = !recent_voter_user_ids.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(is_closed);
STORE_FLAG(is_public);
STORE_FLAG(allow_multiple_answers);
STORE_FLAG(is_quiz);
STORE_FLAG(has_recent_voters);
END_STORE_FLAGS();
store(question, storer);
store(options, storer);
store(total_voter_count, storer);
if (is_quiz) {
store(correct_option_id, storer);
}
if (has_recent_voters) {
store(recent_voter_user_ids, storer);
}
}
template <class ParserT>
void PollManager::Poll::parse(ParserT &parser) {
using ::td::parse;
bool is_public;
bool has_recent_voters;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_closed);
PARSE_FLAG(is_public);
PARSE_FLAG(allow_multiple_answers);
PARSE_FLAG(is_quiz);
PARSE_FLAG(has_recent_voters);
END_PARSE_FLAGS();
is_anonymous = !is_public;
parse(question, parser);
parse(options, parser);
parse(total_voter_count, parser);
if (is_quiz) {
parse(correct_option_id, parser);
if (correct_option_id < -1 || correct_option_id >= static_cast<int32>(options.size())) {
parser.set_error("Wrong correct_option_id");
}
}
if (has_recent_voters) {
parse(recent_voter_user_ids, parser);
}
}
template <class StorerT>
@ -68,9 +97,18 @@ void PollManager::store_poll(PollId poll_id, StorerT &storer) const {
if (is_local_poll_id(poll_id)) {
auto poll = get_poll(poll_id);
CHECK(poll != nullptr);
BEGIN_STORE_FLAGS();
STORE_FLAG(poll->is_closed);
STORE_FLAG(poll->is_anonymous);
STORE_FLAG(poll->allow_multiple_answers);
STORE_FLAG(poll->is_quiz);
END_STORE_FLAGS();
store(poll->question, storer);
vector<string> options = transform(poll->options, [](const PollOption &option) { return option.text; });
store(options, storer);
if (poll->is_quiz) {
store(poll->correct_option_id, storer);
}
}
}
@ -82,12 +120,33 @@ PollId PollManager::parse_poll(ParserT &parser) {
if (is_local_poll_id(poll_id)) {
string question;
vector<string> options;
bool is_closed = false;
bool is_anonymous = true;
bool allow_multiple_answers = false;
bool is_quiz = false;
int32 correct_option_id = -1;
if (parser.version() >= static_cast<int32>(Version::SupportPolls2_0)) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_closed);
PARSE_FLAG(is_anonymous);
PARSE_FLAG(allow_multiple_answers);
PARSE_FLAG(is_quiz);
END_PARSE_FLAGS();
}
parse(question, parser);
parse(options, parser);
if (is_quiz) {
parse(correct_option_id, parser);
if (correct_option_id < -1 || correct_option_id >= static_cast<int32>(options.size())) {
parser.set_error("Wrong correct_option_id");
}
}
if (parser.get_error() != nullptr) {
return PollId();
}
return create_poll(std::move(question), std::move(options));
return create_poll(std::move(question), std::move(options), is_anonymous, allow_multiple_answers, is_quiz,
correct_option_id, is_closed);
}
auto poll = get_poll_force(poll_id);

View File

@ -41,6 +41,15 @@ static StringBuilder &operator<<(StringBuilder &string_builder, const KeyboardBu
case KeyboardButton::Type::RequestLocation:
string_builder << "RequestLocation";
break;
case KeyboardButton::Type::RequestPoll:
string_builder << "RequestPoll";
break;
case KeyboardButton::Type::RequestPollQuiz:
string_builder << "RequestPollQuiz";
break;
case KeyboardButton::Type::RequestPollRegular:
string_builder << "RequestPollRegular";
break;
default:
UNREACHABLE();
}
@ -175,6 +184,20 @@ static KeyboardButton get_keyboard_button(tl_object_ptr<telegram_api::KeyboardBu
button.text = std::move(keyboard_button->text_);
break;
}
case telegram_api::keyboardButtonRequestPoll::ID: {
auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonRequestPoll>(keyboard_button_ptr);
if (keyboard_button->flags_ & telegram_api::keyboardButtonRequestPoll::QUIZ_MASK) {
if (keyboard_button->quiz_) {
button.type = KeyboardButton::Type::RequestPollQuiz;
} else {
button.type = KeyboardButton::Type::RequestPollRegular;
}
} else {
button.type = KeyboardButton::Type::RequestPoll;
}
button.text = std::move(keyboard_button->text_);
break;
}
default:
LOG(ERROR) << "Unsupported keyboard button: " << to_string(keyboard_button_ptr);
}
@ -346,16 +369,33 @@ static Result<KeyboardButton> get_keyboard_button(tl_object_ptr<td_api::keyboard
break;
case td_api::keyboardButtonTypeRequestPhoneNumber::ID:
if (!request_buttons_allowed) {
return Status::Error(400, "Phone number can be requested in a private chats only");
return Status::Error(400, "Phone number can be requested in private chats only");
}
current_button.type = KeyboardButton::Type::RequestPhoneNumber;
break;
case td_api::keyboardButtonTypeRequestLocation::ID:
if (!request_buttons_allowed) {
return Status::Error(400, "Location can be requested in a private chats only");
return Status::Error(400, "Location can be requested in private chats only");
}
current_button.type = KeyboardButton::Type::RequestLocation;
break;
case td_api::keyboardButtonTypeRequestPoll::ID: {
if (!request_buttons_allowed) {
return Status::Error(400, "Poll can be requested in private chats only");
}
auto *request_poll = static_cast<const td_api::keyboardButtonTypeRequestPoll *>(button->type_.get());
if (request_poll->force_quiz_ && request_poll->force_regular_) {
return Status::Error(400, "Can't force quiz mode and regular poll simultaneously");
}
if (request_poll->force_quiz_) {
current_button.type = KeyboardButton::Type::RequestPollQuiz;
} else if (request_poll->force_regular_) {
current_button.type = KeyboardButton::Type::RequestPollRegular;
} else {
current_button.type = KeyboardButton::Type::RequestPoll;
}
break;
}
default:
UNREACHABLE();
}
@ -560,6 +600,12 @@ static tl_object_ptr<telegram_api::KeyboardButton> get_keyboard_button(const Key
return make_tl_object<telegram_api::keyboardButtonRequestPhone>(keyboard_button.text);
case KeyboardButton::Type::RequestLocation:
return make_tl_object<telegram_api::keyboardButtonRequestGeoLocation>(keyboard_button.text);
case KeyboardButton::Type::RequestPoll:
return make_tl_object<telegram_api::keyboardButtonRequestPoll>(0, false, keyboard_button.text);
case KeyboardButton::Type::RequestPollQuiz:
return make_tl_object<telegram_api::keyboardButtonRequestPoll>(1, true, keyboard_button.text);
case KeyboardButton::Type::RequestPollRegular:
return make_tl_object<telegram_api::keyboardButtonRequestPoll>(1, false, keyboard_button.text);
default:
UNREACHABLE();
return nullptr;
@ -674,6 +720,15 @@ static tl_object_ptr<td_api::keyboardButton> get_keyboard_button_object(const Ke
case KeyboardButton::Type::RequestLocation:
type = make_tl_object<td_api::keyboardButtonTypeRequestLocation>();
break;
case KeyboardButton::Type::RequestPoll:
type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(false, false);
break;
case KeyboardButton::Type::RequestPollQuiz:
type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(false, true);
break;
case KeyboardButton::Type::RequestPollRegular:
type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(true, false);
break;
default:
UNREACHABLE();
return nullptr;

View File

@ -17,7 +17,14 @@ namespace td {
struct KeyboardButton {
// append only
enum class Type : int32 { Text, RequestPhoneNumber, RequestLocation };
enum class Type : int32 {
Text,
RequestPhoneNumber,
RequestLocation,
RequestPoll,
RequestPollQuiz,
RequestPollRegular
};
Type type;
string text;
};

View File

@ -1360,26 +1360,22 @@ EncryptedSecureValue encrypt_secure_value(FileManager *file_manager, const secur
return res;
}
static auto as_jsonable(const SecureDataCredentials &credentials) {
static auto as_jsonable_data(const SecureDataCredentials &credentials) {
return json_object([&credentials](auto &o) {
o("data_hash", base64_encode(credentials.hash));
o("secret", base64_encode(credentials.secret));
});
}
static auto as_jsonable(const SecureFileCredentials &credentials) {
static auto as_jsonable_file(const SecureFileCredentials &credentials) {
return json_object([&credentials](auto &o) {
o("file_hash", base64_encode(credentials.hash));
o("secret", base64_encode(credentials.secret));
});
}
static auto as_jsonable(const vector<SecureFileCredentials> &files) {
return json_array([&files](auto &arr) {
for (auto &file : files) {
arr(as_jsonable(file));
}
});
static auto as_jsonable_files(const vector<SecureFileCredentials> &files) {
return json_array(files, as_jsonable_file);
}
static Slice secure_value_type_as_slice(SecureValueType type) {
@ -1428,22 +1424,22 @@ static auto credentials_as_jsonable(const std::vector<SecureValueCredentials> &c
o(secure_value_type_as_slice(cred.type), json_object([&cred](auto &o) {
if (cred.data) {
o("data", as_jsonable(cred.data.value()));
o("data", as_jsonable_data(cred.data.value()));
}
if (!cred.files.empty()) {
o("files", as_jsonable(cred.files));
o("files", as_jsonable_files(cred.files));
}
if (cred.front_side) {
o("front_side", as_jsonable(cred.front_side.value()));
o("front_side", as_jsonable_file(cred.front_side.value()));
}
if (cred.reverse_side) {
o("reverse_side", as_jsonable(cred.reverse_side.value()));
o("reverse_side", as_jsonable_file(cred.reverse_side.value()));
}
if (cred.selfie) {
o("selfie", as_jsonable(cred.selfie.value()));
o("selfie", as_jsonable_file(cred.selfie.value()));
}
if (!cred.translations.empty()) {
o("translation", as_jsonable(cred.translations));
o("translation", as_jsonable_files(cred.translations));
}
}));
}

View File

@ -4948,8 +4948,8 @@ void StickersManager::on_get_language_codes(const string &key, Result<vector<str
}
auto language_codes = result.move_as_ok();
LOG(INFO) << "Receive language codes " << language_codes << " for emojis search";
td::remove_if(language_codes, [](const auto &language_code) {
LOG(INFO) << "Receive language codes " << language_codes << " for emojis search with key " << key;
td::remove_if(language_codes, [](const string &language_code) {
if (language_code.empty() || language_code.find('$') != string::npos) {
LOG(ERROR) << "Receive language_code \"" << language_code << '"';
return true;
@ -4963,22 +4963,28 @@ void StickersManager::on_get_language_codes(const string &key, Result<vector<str
std::sort(language_codes.begin(), language_codes.end());
language_codes.erase(std::unique(language_codes.begin(), language_codes.end()), language_codes.end());
G()->td_db()->get_sqlite_pmc()->set(key, implode(language_codes, '$'), Auto());
auto it = emoji_language_codes_.find(key);
CHECK(it != emoji_language_codes_.end());
it->second = std::move(language_codes);
if (it->second != language_codes) {
LOG(INFO) << "Update emoji language codes for " << key << " to " << language_codes;
G()->td_db()->get_sqlite_pmc()->set(key, implode(language_codes, '$'), Auto());
it->second = std::move(language_codes);
}
for (auto &promise : promises) {
promise.set_value(Unit());
}
}
vector<string> StickersManager::get_emoji_language_codes(Promise<Unit> &promise) {
vector<string> StickersManager::get_emoji_language_codes(const string &input_language_code, Promise<Unit> &promise) {
vector<string> language_codes = td_->language_pack_manager_->get_actor_unsafe()->get_used_language_codes();
auto system_language_code = G()->mtproto_header().get_system_language_code();
if (!system_language_code.empty() && system_language_code.find('$') == string::npos) {
language_codes.push_back(system_language_code);
}
if (!input_language_code.empty() && input_language_code.find('$') == string::npos) {
language_codes.push_back(input_language_code);
}
if (language_codes.empty()) {
LOG(ERROR) << "List of language codes is empty";
@ -4987,6 +4993,7 @@ vector<string> StickersManager::get_emoji_language_codes(Promise<Unit> &promise)
std::sort(language_codes.begin(), language_codes.end());
language_codes.erase(std::unique(language_codes.begin(), language_codes.end()), language_codes.end());
LOG(DEBUG) << "Have language codes " << language_codes;
auto key = get_emoji_language_codes_database_key(language_codes);
auto it = emoji_language_codes_.find(key);
if (it == emoji_language_codes_.end()) {
@ -5003,6 +5010,9 @@ vector<string> StickersManager::get_emoji_language_codes(Promise<Unit> &promise)
load_emoji_keywords_difference(language_code);
}
}
if (reloaded_emoji_keywords_.insert(key).second) {
load_language_codes(std::move(language_codes), std::move(key), Auto());
}
}
return it->second;
}
@ -5196,14 +5206,14 @@ void StickersManager::on_get_emoji_keywords_difference(
emoji_language_code_last_difference_times_[language_code] = static_cast<int32>(Time::now_cached());
}
vector<string> StickersManager::search_emojis(const string &text, bool exact_match, bool force,
Promise<Unit> &&promise) {
vector<string> StickersManager::search_emojis(const string &text, bool exact_match, const string &input_language_code,
bool force, Promise<Unit> &&promise) {
if (text.empty() || !G()->parameters().use_file_db /* have SQLite PMC */) {
promise.set_value(Unit());
return {};
}
auto language_codes = get_emoji_language_codes(promise);
auto language_codes = get_emoji_language_codes(input_language_code, promise);
if (language_codes.empty()) {
// promise was consumed
return {};

View File

@ -205,7 +205,8 @@ class StickersManager : public Actor {
vector<string> get_sticker_emojis(const tl_object_ptr<td_api::InputFile> &input_file, Promise<Unit> &&promise);
vector<string> search_emojis(const string &text, bool exact_match, bool force, Promise<Unit> &&promise);
vector<string> search_emojis(const string &text, bool exact_match, const string &input_language_code, bool force,
Promise<Unit> &&promise);
int64 get_emoji_suggestions_url(const string &language_code, Promise<Unit> &&promise);
@ -504,7 +505,7 @@ class StickersManager : public Actor {
double get_emoji_language_code_last_difference_time(const string &language_code);
vector<string> get_emoji_language_codes(Promise<Unit> &promise);
vector<string> get_emoji_language_codes(const string &input_language_code, Promise<Unit> &promise);
void load_language_codes(vector<string> language_codes, string key, Promise<Unit> &&promise);
@ -612,6 +613,7 @@ class StickersManager : public Actor {
std::unordered_map<string, vector<string>> emoji_language_codes_;
std::unordered_map<string, int32> emoji_language_code_versions_;
std::unordered_map<string, double> emoji_language_code_last_difference_times_;
std::unordered_set<string> reloaded_emoji_keywords_;
std::unordered_map<string, vector<Promise<Unit>>> load_emoji_keywords_queries_;
std::unordered_map<string, vector<Promise<Unit>>> load_language_codes_queries_;
std::unordered_map<int64, string> emoji_suggestions_urls_;

View File

@ -121,6 +121,7 @@
#include "td/utils/tl_parsers.h"
#include "td/utils/utf8.h"
#include <cmath>
#include <limits>
#include <tuple>
#include <type_traits>
@ -2778,11 +2779,13 @@ class GetStickerEmojisRequest : public RequestActor<> {
class SearchEmojisRequest : public RequestActor<> {
string text_;
bool exact_match_;
string input_language_code_;
vector<string> emojis_;
void do_run(Promise<Unit> &&promise) override {
emojis_ = td->stickers_manager_->search_emojis(text_, exact_match_, get_tries() < 2, std::move(promise));
emojis_ = td->stickers_manager_->search_emojis(text_, exact_match_, input_language_code_, get_tries() < 2,
std::move(promise));
}
void do_send_result() override {
@ -2790,8 +2793,12 @@ class SearchEmojisRequest : public RequestActor<> {
}
public:
SearchEmojisRequest(ActorShared<Td> td, uint64 request_id, string &&text, bool exact_match)
: RequestActor(std::move(td), request_id), text_(std::move(text)), exact_match_(exact_match) {
SearchEmojisRequest(ActorShared<Td> td, uint64 request_id, string &&text, bool exact_match,
string &&input_language_code)
: RequestActor(std::move(td), request_id)
, text_(std::move(text))
, exact_match_(exact_match)
, input_language_code_(std::move(input_language_code)) {
set_tries(3);
}
};
@ -3303,6 +3310,17 @@ void Td::on_alarm_timeout_callback(void *td_ptr, int64 alarm_id) {
send_closure_later(td_id, &Td::on_alarm_timeout, alarm_id);
}
void Td::on_update_server_time_difference() {
auto diff = G()->get_server_time_difference();
if (std::abs(diff - last_sent_server_time_difference_) < 0.5) {
return;
}
last_sent_server_time_difference_ = diff;
send_update(td_api::make_object<td_api::updateOption>(
"unix_time", td_api::make_object<td_api::optionValueInteger>(G()->unix_time())));
}
void Td::on_alarm_timeout(int64 alarm_id) {
if (alarm_id == ONLINE_ALARM_ID) {
on_online_updated(false, true);
@ -3497,7 +3515,7 @@ bool Td::is_preauthentication_request(int32 id) {
case td_api::setCustomLanguagePackString::ID:
case td_api::deleteLanguagePack::ID:
case td_api::processPushNotification::ID:
case td_api::sendTonLiteServerRequest::ID:
// case td_api::sendTonLiteServerRequest::ID:
case td_api::getOption::ID:
case td_api::setOption::ID:
case td_api::getStorageStatistics::ID:
@ -3661,8 +3679,9 @@ td_api::object_ptr<td_api::Object> Td::static_request(td_api::object_ptr<td_api:
return td_api::make_object<td_api::error>(400, "Request is empty");
}
bool need_logging = [&] {
switch (function->get_id()) {
auto function_id = function->get_id();
bool need_logging = [function_id] {
switch (function_id) {
case td_api::parseTextEntities::ID:
case td_api::getFileMimeType::ID:
case td_api::getFileExtension::ID:
@ -3682,6 +3701,7 @@ td_api::object_ptr<td_api::Object> Td::static_request(td_api::object_ptr<td_api:
td_api::object_ptr<td_api::Object> response;
downcast_call(*function, [&response](auto &request) { response = Td::do_static_request(request); });
LOG_CHECK(response != nullptr) << function_id;
if (need_logging) {
VLOG(td_requests) << "Sending result for static request: " << to_string(response);
@ -4298,7 +4318,11 @@ Status Td::init(DbKey key) {
LOG(INFO) << "Successfully inited database in " << tag("database_directory", parameters_.database_directory)
<< " and " << tag("files_directory", parameters_.files_directory);
VLOG(td_init) << "Successfully inited database";
G()->init(parameters_, actor_id(this), r_td_db.move_as_ok()).ensure();
last_sent_server_time_difference_ = G()->get_server_time_difference();
send_update(td_api::make_object<td_api::updateOption>(
"unix_time", td_api::make_object<td_api::optionValueInteger>(G()->unix_time())));
init_options_and_network();
@ -4670,6 +4694,7 @@ void Td::send_update(tl_object_ptr<td_api::Update> &&object) {
case td_api::updateTrendingStickerSets::ID:
VLOG(td_requests) << "Sending update: updateTrendingStickerSets { ... }";
break;
case td_api::updateOption::ID / 2:
case td_api::updateChatReadInbox::ID / 2:
case td_api::updateUnreadMessageCount::ID / 2:
case td_api::updateUnreadChatCount::ID / 2:
@ -6726,7 +6751,9 @@ void Td::on_request(uint64 id, td_api::getStickerEmojis &request) {
void Td::on_request(uint64 id, td_api::searchEmojis &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.text_);
CREATE_REQUEST(SearchEmojisRequest, std::move(request.text_), request.exact_match_);
CLEAN_INPUT_STRING(request.input_language_code_);
CREATE_REQUEST(SearchEmojisRequest, std::move(request.text_), request.exact_match_,
std::move(request.input_language_code_));
}
void Td::on_request(uint64 id, td_api::getEmojiSuggestionsUrl &request) {
@ -7226,6 +7253,21 @@ void Td::on_request(uint64 id, td_api::setPollAnswer &request) {
std::move(request.option_ids_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getPollVoters &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda(
[promise = std::move(promise), td = this](Result<std::pair<int32, vector<UserId>>> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
promise.set_value(td->contacts_manager_->get_users_object(result.ok().first, result.ok().second));
}
});
messages_manager_->get_poll_voters({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.option_id_,
request.offset_, request.limit_, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::stopPoll &request) {
CREATE_OK_REQUEST_PROMISE();
messages_manager_->stop_poll({DialogId(request.chat_id_), MessageId(request.message_id_)},
@ -7338,17 +7380,17 @@ void Td::on_request(uint64 id, const td_api::deleteSavedCredentials &request) {
delete_saved_credentials(std::move(promise));
}
void Td::on_request(uint64 id, const td_api::sendTonLiteServerRequest &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
send_ton_lite_server_request(request.request_, std::move(promise));
}
// void Td::on_request(uint64 id, const td_api::sendTonLiteServerRequest &request) {
// CHECK_IS_USER();
// CREATE_REQUEST_PROMISE();
// send_ton_lite_server_request(request.request_, std::move(promise));
// }
void Td::on_request(uint64 id, const td_api::getTonWalletPasswordSalt &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::get_ton_wallet_password_salt, std::move(promise));
}
// void Td::on_request(uint64 id, const td_api::getTonWalletPasswordSalt &request) {
// CHECK_IS_USER();
// CREATE_REQUEST_PROMISE();
// send_closure(password_manager_, &PasswordManager::get_ton_wallet_password_salt, std::move(promise));
// }
void Td::on_request(uint64 id, td_api::getPassportElement &request) {
CHECK_IS_USER();

View File

@ -106,7 +106,9 @@ class Td final : public NetQueryCallback {
void schedule_get_terms_of_service(int32 expires_in);
void on_result(NetQueryPtr query) override;
void on_connection_state_changed(StateManager::State new_state);
void on_update_server_time_difference();
void on_authorization_lost();
void on_online_updated(bool force, bool send_update);
@ -186,6 +188,7 @@ class Td final : public NetQueryCallback {
ResultHandler(const ResultHandler &) = delete;
ResultHandler &operator=(const ResultHandler &) = delete;
virtual ~ResultHandler() = default;
virtual void on_result(NetQueryPtr query);
virtual void on_result(uint64 id, BufferSlice packet) {
UNREACHABLE();
@ -197,7 +200,7 @@ class Td final : public NetQueryCallback {
friend class Td;
protected:
void send_query(NetQueryPtr);
void send_query(NetQueryPtr query);
Td *td = nullptr;
@ -224,12 +227,14 @@ class Td final : public NetQueryCallback {
static td_api::object_ptr<td_api::Object> static_request(td_api::object_ptr<td_api::Function> function);
private:
static constexpr const char *TDLIB_VERSION = "1.5.4";
static constexpr const char *TDLIB_VERSION = "1.5.5";
static constexpr int64 ONLINE_ALARM_ID = 0;
static constexpr int64 PING_SERVER_ALARM_ID = -1;
static constexpr int32 PING_SERVER_TIMEOUT = 300;
static constexpr int64 TERMS_OF_SERVICE_ALARM_ID = -2;
void on_connection_state_changed(StateManager::State new_state);
void send_result(uint64 id, tl_object_ptr<td_api::Object> object);
void send_error(uint64 id, Status error);
void send_error_impl(uint64 id, tl_object_ptr<td_api::error> error);
@ -277,6 +282,8 @@ class Td final : public NetQueryCallback {
TermsOfService pending_terms_of_service_;
double last_sent_server_time_difference_ = 1e100;
struct DownloadInfo {
int32 offset = -1;
int32 limit = -1;
@ -900,6 +907,8 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::setPollAnswer &request);
void on_request(uint64 id, td_api::getPollVoters &request);
void on_request(uint64 id, td_api::stopPoll &request);
void on_request(uint64 id, const td_api::getLoginUrlInfo &request);
@ -932,9 +941,9 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::deleteSavedCredentials &request);
void on_request(uint64 id, const td_api::sendTonLiteServerRequest &request);
// void on_request(uint64 id, const td_api::sendTonLiteServerRequest &request);
void on_request(uint64 id, const td_api::getTonWalletPasswordSalt &request);
// void on_request(uint64 id, const td_api::getTonWalletPasswordSalt &request);
void on_request(uint64 id, td_api::getPassportElement &request);

View File

@ -454,6 +454,19 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
}
}
/*
// the users are always min, so no need to check
if (media_id == telegram_api::messageMediaPoll::ID) {
auto message_media_poll = static_cast<const telegram_api::messageMediaPoll *>(message->media_.get());
for (auto recent_voter_user_id : message_media_poll->results_->recent_voters_) {
UserId user_id(recent_voter_user_id);
if (!is_acceptable_user(user_id)) {
return false;
}
}
}
*/
/*
// the channel is always min, so no need to check
if (media_id == telegram_api::messageMediaWebPage::ID) {
auto message_media_web_page = static_cast<const telegram_api::messageMediaWebPage *>(message->media_.get());
if (message_media_web_page->webpage_->get_id() == telegram_api::webPage::ID) {
@ -1967,6 +1980,10 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePoll> up
td_->poll_manager_->on_get_poll(PollId(update->poll_id_), std::move(update->poll_), std::move(update->results_));
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePollVote> update, bool /*force_apply*/) {
td_->poll_manager_->on_get_poll_vote(PollId(update->poll_id_), UserId(update->user_id_), std::move(update->options_));
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewScheduledMessage> update, bool /*force_apply*/) {
td_->messages_manager_->on_get_message(std::move(update->message_), true, false, true, true, true,
"updateNewScheduledMessage");

View File

@ -281,6 +281,7 @@ class UpdatesManager : public Actor {
void on_update(tl_object_ptr<telegram_api::updateGeoLiveViewed> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateMessagePollVote> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateNewScheduledMessage> update, bool /*force_apply*/);
void on_update(tl_object_ptr<telegram_api::updateDeleteScheduledMessages> update, bool /*force_apply*/);

View File

@ -8,7 +8,7 @@
namespace td {
constexpr int32 MTPROTO_LAYER = 107;
constexpr int32 MTPROTO_LAYER = 109;
enum class Version : int32 {
Initial,
@ -35,6 +35,7 @@ enum class Version : int32 {
AddVideoCallsSupport,
AddPhotoSizeSource,
AddFolders,
SupportPolls2_0,
Next
};

View File

@ -260,11 +260,8 @@ tl_object_ptr<telegram_api::InputMedia> VideosManager::get_input_media(
if (!video->file_name.empty()) {
attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(video->file_name));
}
int32 flags = 0;
int32 flags = telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
vector<tl_object_ptr<telegram_api::InputDocument>> added_stickers;
if (ttl != 0 || !td_->auth_manager_->is_bot()) {
flags |= telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
}
if (video->has_stickers) {
flags |= telegram_api::inputMediaUploadedDocument::STICKERS_MASK;
added_stickers = td_->file_manager_->get_input_documents(video->sticker_file_ids);

View File

@ -478,8 +478,10 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
page->document = std::move(parsed_document);
}
}
if (web_page->flags_ & WEBPAGE_FLAG_HAS_DOCUMENTS) {
for (auto &document : web_page->documents_) {
for (auto &attribute : web_page->attributes_) {
CHECK(attribute != nullptr);
page->documents.clear();
for (auto &document : attribute->documents_) {
int32 document_id = document->get_id();
if (document_id == telegram_api::document::ID) {
auto parsed_document = td_->documents_manager_->on_get_document(
@ -489,6 +491,7 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
}
}
}
// TODO attribute->settings_
}
if (web_page->flags_ & WEBPAGE_FLAG_HAS_INSTANT_VIEW) {
on_get_web_page_instant_view(page.get(), std::move(web_page->cached_page_), web_page->hash_, owner_dialog_id);

View File

@ -1627,10 +1627,10 @@ class CliClient final : public Actor {
send_request(td_api::make_object<td_api::deleteSavedOrderInfo>());
} else if (op == "dsc") {
send_request(td_api::make_object<td_api::deleteSavedCredentials>());
} else if (op == "stlsr") {
send_request(td_api::make_object<td_api::sendTonLiteServerRequest>());
} else if (op == "gtwps") {
send_request(td_api::make_object<td_api::getTonWalletPasswordSalt>());
// } else if (op == "stlsr") {
// send_request(td_api::make_object<td_api::sendTonLiteServerRequest>());
// } else if (op == "gtwps") {
// send_request(td_api::make_object<td_api::getTonWalletPasswordSalt>());
} else if (op == "gpr") {
send_request(td_api::make_object<td_api::getUserPrivacySettingRules>(get_user_privacy_setting(args)));
} else if (op == "spr") {
@ -2365,9 +2365,11 @@ class CliClient final : public Actor {
} else if (op == "gse") {
send_request(td_api::make_object<td_api::getStickerEmojis>(as_input_file_id(args)));
} else if (op == "se") {
send_request(td_api::make_object<td_api::searchEmojis>(args, false));
send_request(td_api::make_object<td_api::searchEmojis>(args, false, ""));
} else if (op == "see") {
send_request(td_api::make_object<td_api::searchEmojis>(args, true));
send_request(td_api::make_object<td_api::searchEmojis>(args, true, ""));
} else if (op == "seru") {
send_request(td_api::make_object<td_api::searchEmojis>(args, false, "ru_RU"));
} else if (op == "gesu") {
send_request(td_api::make_object<td_api::getEmojiSuggestionsUrl>(args));
} else {
@ -3192,14 +3194,21 @@ class CliClient final : public Actor {
send_message(chat_id, td_api::make_object<td_api::inputMessageLocation>(as_location(latitude, longitude),
to_integer<int32>(period)));
} else if (op == "spoll") {
} else if (op == "spoll" || op == "spollm" || op == "spollp" || op == "squiz") {
string chat_id;
string question;
std::tie(chat_id, args) = split(args);
std::tie(question, args) = split(args);
auto options = full_split(args);
send_message(chat_id, td_api::make_object<td_api::inputMessagePoll>(question, std::move(options)));
td_api::object_ptr<td_api::PollType> poll_type;
if (op == "squiz") {
poll_type = td_api::make_object<td_api::pollTypeQuiz>(narrow_cast<int32>(options.size() - 1));
} else {
poll_type = td_api::make_object<td_api::pollTypeRegular>(op == "spollm");
}
send_message(chat_id, td_api::make_object<td_api::inputMessagePoll>(question, std::move(options), op != "spollp",
std::move(poll_type), false));
} else if (op == "sp" || op == "spcaption" || op == "spttl") {
string chat_id;
string photo_path;
@ -3480,6 +3489,20 @@ class CliClient final : public Actor {
std::tie(message_id, option_ids) = split(args);
send_request(td_api::make_object<td_api::setPollAnswer>(as_chat_id(chat_id), as_message_id(message_id),
to_integers<int32>(option_ids)));
} else if (op == "gpollv") {
string chat_id;
string message_id;
string option_id;
string offset;
string limit;
std::tie(chat_id, args) = split(args);
std::tie(message_id, args) = split(args);
std::tie(option_id, args) = split(args);
std::tie(offset, limit) = split(args);
send_request(td_api::make_object<td_api::getPollVoters>(as_chat_id(chat_id), as_message_id(message_id),
to_integer<int32>(option_id), to_integer<int32>(offset),
to_integer<int32>(limit)));
} else if (op == "stoppoll") {
string chat_id;
string message_id;

View File

@ -220,7 +220,7 @@ Result<bool> FileDownloader::should_restart_part(Part part, NetQueryPtr &net_que
return false;
}
Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32 part_count) {
Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32 part_count, int64 streaming_offset) {
if (encryption_key_.is_secret()) {
part.size = (part.size + 15) & ~15; // fix for last part
}
@ -240,7 +240,9 @@ Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32
int32 flags = 0;
#if !TD_EMSCRIPTEN
// CDN is supported, unless we use domains instead of IPs from a browser
flags |= telegram_api::upload_getFile::CDN_SUPPORTED_MASK;
if (streaming_offset == 0) {
flags |= telegram_api::upload_getFile::CDN_SUPPORTED_MASK;
}
#endif
DcId dc_id = remote_.is_web() ? G()->get_webfile_dc_id() : remote_.get_dc_id();
net_query = G()->net_query_creator().create(

View File

@ -85,7 +85,8 @@ class FileDownloader : public FileLoader {
Status on_ok(int64 size) override TD_WARN_UNUSED_RESULT;
void on_error(Status status) override;
Result<bool> should_restart_part(Part part, NetQueryPtr &net_query) override TD_WARN_UNUSED_RESULT;
Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count) override TD_WARN_UNUSED_RESULT;
Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count,
int64 streaming_offset) override TD_WARN_UNUSED_RESULT;
Result<size_t> process_part(Part part, NetQueryPtr net_query) override TD_WARN_UNUSED_RESULT;
void on_progress(Progress progress) override;
FileLoader::Callback *get_callback() override;

View File

@ -194,7 +194,7 @@ Status FileLoader::do_loop() {
VLOG(files) << "Start part " << tag("id", part.id) << tag("size", part.size);
resource_state_.start_use(static_cast<int64>(part.size));
TRY_RESULT(query_flag, start_part(part, parts_manager_.get_part_count()));
TRY_RESULT(query_flag, start_part(part, parts_manager_.get_part_count(), parts_manager_.get_streaming_offset()));
NetQueryPtr query;
bool is_blocking;
std::tie(query, is_blocking) = std::move(query_flag);

View File

@ -69,7 +69,8 @@ class FileLoader : public FileLoaderActor {
virtual Status before_start_parts() {
return Status::OK();
}
virtual Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int part_count) TD_WARN_UNUSED_RESULT = 0;
virtual Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int part_count,
int64 streaming_offset) TD_WARN_UNUSED_RESULT = 0;
virtual void after_start_parts() {
}
virtual Result<size_t> process_part(Part part, NetQueryPtr net_query) TD_WARN_UNUSED_RESULT = 0;

View File

@ -331,10 +331,6 @@ class FullRemoteFileLocation {
}
}
void clear_file_reference() {
file_reference_.clear();
}
bool delete_file_reference(Slice bad_file_reference) {
if (file_reference_ != FileReferenceView::invalid_file_reference() && file_reference_ == bad_file_reference) {
file_reference_ = FileReferenceView::invalid_file_reference().str();

View File

@ -151,7 +151,9 @@ void FullRemoteFileLocation::parse(ParserT &parser) {
if (has_file_reference) {
parse(file_reference_, parser);
// file_reference_.clear();
if (file_reference_ == FileReferenceView::invalid_file_reference()) {
file_reference_.clear();
}
}
if (is_web) {
variant_ = WebRemoteFileLocation();

View File

@ -2726,14 +2726,10 @@ string FileManager::get_persistent_id(const FullGenerateFileLocation &location)
}
string FileManager::get_persistent_id(const FullRemoteFileLocation &location) {
auto location_copy = location;
location_copy.clear_file_reference();
auto binary = serialize(location_copy);
auto binary = serialize(location);
binary = zero_encode(binary);
binary.push_back(static_cast<char>(narrow_cast<uint8>(Version::AddFolders) - 1));
// TODO return back correct version
// binary.push_back(static_cast<char>(narrow_cast<uint8>(Version::Next) - 1));
binary.push_back(static_cast<char>(narrow_cast<uint8>(Version::Next) - 1));
binary.push_back(PERSISTENT_ID_VERSION);
return base64url_encode(binary);
}

View File

@ -247,7 +247,7 @@ void FileUploader::after_start_parts() {
try_release_fd();
}
Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 part_count) {
Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 part_count, int64 streaming_offset) {
auto padded_size = part.size;
if (encryption_key_.is_secret()) {
padded_size = (padded_size + 15) & ~15;

View File

@ -63,7 +63,8 @@ class FileUploader : public FileLoader {
void on_error(Status status) override;
Status before_start_parts() override;
void after_start_parts() override;
Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count) override TD_WARN_UNUSED_RESULT;
Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count,
int64 streaming_offset) override TD_WARN_UNUSED_RESULT;
Result<size_t> process_part(Part part, NetQueryPtr net_query) override TD_WARN_UNUSED_RESULT;
void on_progress(Progress progress) override;
FileLoader::Callback *get_callback() override;

View File

@ -281,19 +281,16 @@ string get_emoji_fingerprint(uint64 num) {
Result<string> check_url(Slice url) {
bool is_tg = false;
bool is_ton = false;
if (begins_with(url, "tg://")) {
url.remove_prefix(5);
is_tg = true;
} else if (begins_with(url, "tg:")) {
if (begins_with(url, "tg:")) {
url.remove_prefix(3);
is_tg = true;
} else if (begins_with(url, "ton://")) {
url.remove_prefix(6);
is_ton = true;
} else if (begins_with(url, "ton:")) {
url.remove_prefix(4);
is_ton = true;
}
if ((is_tg || is_ton) && begins_with(url, "//")) {
url.remove_prefix(2);
}
TRY_RESULT(http_url, parse_url(url));
if (is_tg || is_ton) {
if (begins_with(url, "http://") || http_url.protocol_ == HttpUrl::Protocol::HTTPS || !http_url.userinfo_.empty() ||

View File

@ -106,7 +106,8 @@ class AuthDataSharedImpl : public AuthDataShared {
}
void log_auth_key(const mtproto::AuthKey &auth_key) {
LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id()) << tag("state", get_auth_key_state(auth_key));
LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id()) << tag("state", get_auth_key_state(auth_key))
<< tag("created_at", auth_key.created_at());
}
};

View File

@ -724,10 +724,12 @@ ActorOwn<> ConnectionCreator::prepare_connection(SocketFd socket_fd, const Proxy
class Callback : public TransparentProxy::Callback {
public:
explicit Callback(Promise<ConnectionData> promise,
unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback, bool use_connection_token)
unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback, bool use_connection_token,
bool was_connected)
: promise_(std::move(promise))
, stats_callback_(std::move(stats_callback))
, use_connection_token_(use_connection_token) {
, use_connection_token_(use_connection_token)
, was_connected_(was_connected) {
}
void set_result(Result<SocketFd> result) override {
if (result.is_error()) {
@ -756,13 +758,14 @@ ActorOwn<> ConnectionCreator::prepare_connection(SocketFd socket_fd, const Proxy
private:
Promise<ConnectionData> promise_;
StateManager::ConnectionToken connection_token_;
bool was_connected_{false};
unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback_;
bool use_connection_token_;
bool was_connected_{false};
};
LOG(INFO) << "Start " << (proxy.use_socks5_proxy() ? "Socks5" : (proxy.use_http_tcp_proxy() ? "HTTP" : "TLS"))
<< ": " << debug_str;
auto callback = make_unique<Callback>(std::move(promise), std::move(stats_callback), use_connection_token);
auto callback = make_unique<Callback>(std::move(promise), std::move(stats_callback), use_connection_token,
!proxy.use_socks5_proxy());
if (proxy.use_socks5_proxy()) {
return ActorOwn<>(create_actor<Socks5>(PSLICE() << actor_name_prefix << "Socks5", std::move(socket_fd),
mtproto_ip, proxy.user().str(), proxy.password().str(),

View File

@ -157,6 +157,10 @@ void DcAuthManager::dc_loop(DcInfo &dc) {
if (dc.auth_key_state == AuthKeyState::OK) {
return;
}
if (dc.state == DcInfo::State::Ok) {
LOG(WARNING) << "Lost key in " << dc.dc_id << ", restart dc_loop";
dc.state = DcInfo::State::Waiting;
}
CHECK(dc.shared_auth_data);
switch (dc.state) {
case DcInfo::State::Waiting: {

View File

@ -129,6 +129,7 @@ Session::Session(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared>
shared_auth_data_ = std::move(shared_auth_data);
auth_data_.set_use_pfs(use_pfs);
auth_data_.set_main_auth_key(shared_auth_data_->get_auth_key());
// auth_data_.break_main_auth_key();
auth_data_.set_server_time_difference(shared_auth_data_->get_server_time_difference());
auth_data_.set_future_salts(shared_auth_data_->get_future_salts(), Time::now());
if (use_pfs && !tmp_auth_key.empty()) {
@ -140,6 +141,7 @@ Session::Session(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared>
Random::secure_bytes(reinterpret_cast<uint8 *>(&session_id), sizeof(session_id));
} while (session_id == 0);
auth_data_.set_session_id(session_id);
use_pfs_ = use_pfs;
LOG(WARNING) << "Generate new session_id " << session_id << " for " << (use_pfs ? "temp " : "")
<< (is_cdn ? "CDN " : "") << "auth key " << auth_data_.get_auth_key().id() << " for "
<< (is_main_ ? "main " : "") << "DC" << dc_id;
@ -238,41 +240,98 @@ void Session::send(NetQueryPtr &&query) {
loop();
}
void Session::on_result(NetQueryPtr query) {
CHECK(UniqueId::extract_type(query->id()) == UniqueId::BindKey);
if (last_bind_id_ != query->id()) {
query->clear();
return;
}
void Session::on_bind_result(NetQueryPtr query) {
LOG(INFO) << "Receive answer to BindKey: " << query;
being_binded_tmp_auth_key_id_ = 0;
last_bind_query_id_ = 0;
LOG(INFO) << "ANSWER TO BindKey" << query;
Status status;
tmp_auth_key_id_ = 0;
last_bind_id_ = 0;
if (query->is_error()) {
status = std::move(query->error());
if (status.code() == 400 && status.message() == "ENCRYPTED_MESSAGE_INVALID") {
bool has_immunity =
!G()->is_server_time_reliable() || G()->server_time() - auth_data_.get_main_auth_key().created_at() < 60;
if (!use_pfs_) {
if (has_immunity) {
LOG(WARNING) << "Do not drop main key, because it was created too recently";
} else {
LOG(WARNING) << "Drop main key because check with temporary key failed";
auth_data_.drop_main_auth_key();
on_auth_key_updated();
}
} else {
if (has_immunity) {
LOG(WARNING) << "Do not validate main key, because it was created too recently";
} else {
need_check_main_key_ = true;
auth_data_.set_use_pfs(false);
LOG(WARNING) << "Got ENCRYPTED_MESSAGE_INVALID error, validate main key";
}
}
}
} else {
auto r_flag = fetch_result<telegram_api::auth_bindTempAuthKey>(query->ok());
if (r_flag.is_error()) {
status = r_flag.move_as_error();
} else {
auto flag = r_flag.move_as_ok();
if (!flag) {
status = Status::Error("Returned false");
}
} else if (!r_flag.ok()) {
status = Status::Error("Returned false");
}
}
if (status.is_ok()) {
LOG(INFO) << "BOUND!" << tag("tmp_id", auth_data_.get_tmp_auth_key().id());
LOG(INFO) << "Bound temp auth key " << auth_data_.get_tmp_auth_key().id();
auth_data_.on_bind();
on_tmp_auth_key_updated();
} else if (status.error().message() == "DispatchTtlError") {
LOG(INFO) << "Resend bind auth key " << auth_data_.get_tmp_auth_key().id() << " request after DispatchTtlError";
} else {
LOG(ERROR) << "BindKey failed: " << status;
connection_close(&main_connection_);
connection_close(&long_poll_connection_);
}
query->clear();
yield();
}
void Session::on_check_key_result(NetQueryPtr query) {
LOG(INFO) << "Receive answer to GetNearestDc: " << query;
being_checked_main_auth_key_id_ = 0;
last_check_query_id_ = 0;
Status status;
if (query->is_error()) {
status = std::move(query->error());
} else {
auto r_flag = fetch_result<telegram_api::help_getNearestDc>(query->ok());
if (r_flag.is_error()) {
status = r_flag.move_as_error();
}
}
if (status.is_ok() || status.error().code() != -404) {
LOG(INFO) << "Check main key ok";
need_check_main_key_ = false;
auth_data_.set_use_pfs(true);
} else {
LOG(ERROR) << "Check main key failed: " << status;
connection_close(&main_connection_);
connection_close(&long_poll_connection_);
}
query->clear();
yield();
}
void Session::on_result(NetQueryPtr query) {
CHECK(UniqueId::extract_type(query->id()) == UniqueId::BindKey);
if (last_bind_query_id_ == query->id()) {
return on_bind_result(std::move(query));
}
if (last_check_query_id_ == query->id()) {
return on_check_key_result(std::move(query));
}
query->clear();
}
void Session::return_query(NetQueryPtr &&query) {
last_activity_timestamp_ = Time::now();
@ -422,17 +481,28 @@ void Session::on_closed(Status status) {
on_tmp_auth_key_updated();
yield();
} else if (is_cdn_) {
LOG(WARNING) << "Invalidate cdn tmp_key";
LOG(WARNING) << "Invalidate CDN tmp_key";
auth_data_.drop_main_auth_key();
on_auth_key_updated();
on_session_failed(std::move(status));
} else if (need_destroy_) {
auth_data_.drop_main_auth_key();
on_auth_key_updated();
} else {
// log out if has error and or 1 minute is passed from start, or 1 minute has passed since auth_key creation
if (!use_pfs_) {
LOG(WARNING) << "Use PFS to check main key";
auth_data_.set_use_pfs(true);
} else if (need_check_main_key_) {
LOG(WARNING) << "Invalidate main key";
auth_data_.drop_main_auth_key();
on_auth_key_updated();
}
yield();
}
}
// resend all queries without ack.
// resend all queries without ack
for (auto it = sent_queries_.begin(); it != sent_queries_.end();) {
if (!it->second.ack && it->second.connection_id == current_info_->connection_id) {
// container vector leak otherwise
@ -688,7 +758,7 @@ void Session::on_message_result_error(uint64 id, int error_code, BufferSlice mes
return;
}
LOG(DEBUG) << "Session::on_error " << tag("id", id) << tag("error_code", error_code)
LOG(DEBUG) << "Session::on_message_result_error " << tag("id", id) << tag("error_code", error_code)
<< tag("msg", message.as_slice());
auto it = sent_queries_.find(id);
if (it == sent_queries_.end()) {
@ -1019,22 +1089,48 @@ void Session::connection_close(ConnectionInfo *info) {
info->connection->force_close(static_cast<mtproto::SessionConnection::Callback *>(this));
CHECK(info->state == ConnectionInfo::State::Empty);
}
bool Session::need_send_check_main_key() const {
return need_check_main_key_ && auth_data_.get_main_auth_key().id() != being_checked_main_auth_key_id_;
}
bool Session::connection_send_check_main_key(ConnectionInfo *info) {
if (!need_check_main_key_) {
return false;
}
uint64 key_id = auth_data_.get_main_auth_key().id();
if (key_id == being_checked_main_auth_key_id_) {
return false;
}
CHECK(info->state != ConnectionInfo::State::Empty);
LOG(INFO) << "Check main key";
being_checked_main_auth_key_id_ = key_id;
last_check_query_id_ = UniqueId::next(UniqueId::BindKey);
NetQueryPtr query =
G()->net_query_creator().create(last_check_query_id_, create_storer(telegram_api::help_getNearestDc()));
query->dispatch_ttl = 0;
query->set_callback(actor_shared(this));
connection_send_query(info, std::move(query));
return true;
}
bool Session::need_send_bind_key() const {
return auth_data_.use_pfs() && !auth_data_.get_bind_flag() && auth_data_.get_tmp_auth_key().id() != tmp_auth_key_id_;
return auth_data_.use_pfs() && !auth_data_.get_bind_flag() &&
auth_data_.get_tmp_auth_key().id() != being_binded_tmp_auth_key_id_;
}
bool Session::need_send_query() const {
return !close_flag_ && (!auth_data_.use_pfs() || auth_data_.get_bind_flag()) && !pending_queries_.empty() &&
!can_destroy_auth_key();
return !close_flag_ && !need_check_main_key_ && (!auth_data_.use_pfs() || auth_data_.get_bind_flag()) &&
!pending_queries_.empty() && !can_destroy_auth_key();
}
bool Session::connection_send_bind_key(ConnectionInfo *info) {
CHECK(info->state != ConnectionInfo::State::Empty);
uint64 key_id = auth_data_.get_tmp_auth_key().id();
if (key_id == tmp_auth_key_id_) {
if (key_id == being_binded_tmp_auth_key_id_) {
return false;
}
tmp_auth_key_id_ = key_id;
last_bind_id_ = UniqueId::next(UniqueId::BindKey);
being_binded_tmp_auth_key_id_ = key_id;
last_bind_query_id_ = UniqueId::next(UniqueId::BindKey);
int64 perm_auth_key_id = auth_data_.get_main_auth_key().id();
int64 nonce = Random::secure_int64();
@ -1045,7 +1141,7 @@ bool Session::connection_send_bind_key(ConnectionInfo *info) {
LOG(INFO) << "Bind key: " << tag("tmp", key_id) << tag("perm", static_cast<uint64>(perm_auth_key_id));
NetQueryPtr query = G()->net_query_creator().create(
last_bind_id_,
last_bind_query_id_,
create_storer(telegram_api::auth_bindTempAuthKey(perm_auth_key_id, nonce, expires_at, std::move(encrypted))));
query->dispatch_ttl = 0;
query->set_callback(actor_shared(this));
@ -1070,13 +1166,13 @@ void Session::on_handshake_ready(Result<unique_ptr<mtproto::AuthKeyHandshake>> r
info.handshake_ = std::move(handshake);
} else {
if (is_main) {
auth_data_.set_main_auth_key(std::move(handshake->auth_key));
auth_data_.set_main_auth_key(handshake->release_auth_key());
on_auth_key_updated();
} else {
auth_data_.set_tmp_auth_key(handshake->release_auth_key());
if (is_main_) {
registered_temp_auth_key_ = TempAuthKeyWatchdog::register_auth_key_id(handshake->auth_key.id());
registered_temp_auth_key_ = TempAuthKeyWatchdog::register_auth_key_id(auth_data_.get_tmp_auth_key().id());
}
auth_data_.set_tmp_auth_key(std::move(handshake->auth_key));
on_tmp_auth_key_updated();
}
LOG(WARNING) << "Update auth key in session_id " << auth_data_.get_session_id() << " to "
@ -1086,10 +1182,10 @@ void Session::on_handshake_ready(Result<unique_ptr<mtproto::AuthKeyHandshake>> r
// Salt of temporary key is different salt. Do not rewrite it
if (auth_data_.use_pfs() ^ is_main) {
auth_data_.set_server_salt(handshake->server_salt, Time::now_cached());
auth_data_.set_server_salt(handshake->get_server_salt(), Time::now_cached());
on_server_salt_updated();
}
if (auth_data_.update_server_time_difference(handshake->server_time_diff)) {
if (auth_data_.update_server_time_difference(handshake->get_server_time_diff())) {
on_server_time_difference_updated();
}
LOG(INFO) << "Got " << (is_main ? "main" : "tmp") << " auth key";
@ -1215,6 +1311,10 @@ void Session::loop() {
connection_send_bind_key(&main_connection_);
need_flush = true;
}
if (need_send_check_main_key()) {
connection_send_check_main_key(&main_connection_);
need_flush = true;
}
}
if (need_flush) {
connection_flush(&main_connection_);

View File

@ -112,8 +112,10 @@ class Session final
uint32 network_generation_ = 0;
bool online_flag_ = false;
bool connection_online_flag_ = false;
uint64 tmp_auth_key_id_ = 0;
uint64 last_bind_id_ = 0;
uint64 being_binded_tmp_auth_key_id_ = 0;
uint64 being_checked_main_auth_key_id_ = 0;
uint64 last_bind_query_id_ = 0;
uint64 last_check_query_id_ = 0;
double last_activity_timestamp_ = 0;
size_t dropped_size_ = 0;
@ -148,6 +150,8 @@ class Session final
std::shared_ptr<Callback> callback_;
mtproto::AuthData auth_data_;
bool use_pfs_{false};
bool need_check_main_key_{false};
TempAuthKeyWatchdog::RegisteredAuthKey registered_temp_auth_key_;
std::shared_ptr<AuthDataShared> shared_auth_data_;
bool close_flag_ = false;
@ -229,9 +233,14 @@ class Session final
bool need_send_query() const;
bool can_destroy_auth_key() const;
bool connection_send_bind_key(ConnectionInfo *info);
bool need_send_check_main_key() const;
bool connection_send_check_main_key(ConnectionInfo *info);
void on_result(NetQueryPtr query) override;
void on_bind_result(NetQueryPtr query);
void on_check_key_result(NetQueryPtr query);
void start_up() override;
void loop() override;
void hangup() override;

View File

@ -259,12 +259,12 @@ class CancellablePromise : public PromiseT {
template <class ValueT, class FunctionOkT, class FunctionFailT>
class LambdaPromise : public PromiseInterface<ValueT> {
enum OnFail { None, Ok, Fail };
enum class OnFail { None, Ok, Fail };
public:
void set_value(ValueT &&value) override {
ok_(std::move(value));
on_fail_ = None;
on_fail_ = OnFail::None;
}
void set_error(Status &&error) override {
do_error(std::move(error));
@ -279,13 +279,15 @@ class LambdaPromise : public PromiseInterface<ValueT> {
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 ? Ok : 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_ = None;
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) {
@ -299,16 +301,16 @@ class LambdaPromise : public PromiseInterface<ValueT> {
void do_error(Status &&error) {
switch (on_fail_) {
case None:
case OnFail::None:
break;
case Ok:
case OnFail::Ok:
do_error_impl(ok_, std::move(error));
break;
case Fail:
case OnFail::Fail:
fail_(std::move(error));
break;
}
on_fail_ = None;
on_fail_ = OnFail::None;
}
};

View File

@ -12,42 +12,49 @@
namespace td {
class DbKey {
public:
enum Type { Empty, RawKey, Password };
enum class Type { Empty, RawKey, Password };
Type type() const {
return type_;
}
public:
bool is_empty() const {
return type_ == Empty;
return type_ == Type::Empty;
}
bool is_raw_key() const {
return type_ == RawKey;
return type_ == Type::RawKey;
}
bool is_password() const {
return type_ == Password;
return type_ == Type::Password;
}
CSlice data() const {
return data_;
}
static DbKey raw_key(string raw_key) {
DbKey res;
res.type_ = RawKey;
res.type_ = Type::RawKey;
res.data_ = std::move(raw_key);
return res;
}
static DbKey password(string password) {
DbKey res;
res.type_ = Password;
res.type_ = Type::Password;
res.data_ = std::move(password);
return res;
}
static DbKey empty() {
return DbKey();
}
private:
Type type_{Empty};
Type type_{Type::Empty};
string data_;
};

View File

@ -169,11 +169,11 @@ SqliteStatement::Datatype SqliteStatement::view_datatype(int id) {
void SqliteStatement::reset() {
sqlite3_reset(stmt_.get());
state_ = Start;
state_ = State::Start;
}
Status SqliteStatement::step() {
if (state_ == Finish) {
if (state_ == State::Finish) {
return Status::Error("One has to reset statement");
}
VLOG(sqlite) << "Start step " << tag("query", sqlite3_sql(stmt_.get())) << tag("statement", stmt_.get())
@ -182,14 +182,14 @@ Status SqliteStatement::step() {
VLOG(sqlite) << "Finish step " << tag("query", sqlite3_sql(stmt_.get())) << tag("statement", stmt_.get())
<< tag("database", db_.get());
if (rc == SQLITE_ROW) {
state_ = GotRow;
state_ = State::GotRow;
return Status::OK();
}
state_ = State::Finish;
if (rc == SQLITE_DONE) {
state_ = Finish;
return Status::OK();
}
state_ = Finish;
return last_error();
}

View File

@ -44,10 +44,10 @@ class SqliteStatement {
Result<string> explain();
bool can_step() const {
return state_ != Finish;
return state_ != State::Finish;
}
bool has_row() const {
return state_ == GotRow;
return state_ == State::GotRow;
}
bool empty() const {
return !stmt_;
@ -72,7 +72,8 @@ class SqliteStatement {
void operator()(sqlite3_stmt *stmt);
};
enum { Start, GotRow, Finish } state_ = Start;
enum class State { Start, GotRow, Finish };
State state_ = State::Start;
std::unique_ptr<sqlite3_stmt, StmtDeleter> stmt_;
std::shared_ptr<detail::RawSqliteDb> db_;

View File

@ -108,7 +108,7 @@ class BinlogReader {
return offset_;
}
Result<size_t> read_next(BinlogEvent *event) {
if (state_ == ReadLength) {
if (state_ == State::ReadLength) {
if (input_->size() < 4) {
return 4;
}
@ -129,7 +129,7 @@ class BinlogReader {
<< expected_size_ << ' ' << tag("is_encrypted", is_encrypted_)
<< format::as_hex_dump<4>(Slice(input_->prepare_read().truncate(28))));
}
state_ = ReadEvent;
state_ = State::ReadEvent;
}
if (input_->size() < size_) {
@ -140,13 +140,14 @@ class BinlogReader {
TRY_STATUS(event->init(input_->cut_head(size_).move_as_buffer_slice()));
offset_ += size_;
event->offset_ = offset_;
state_ = ReadLength;
state_ = State::ReadLength;
return 0;
}
private:
ChainBufferReader *input_;
enum { ReadLength, ReadEvent } state_ = ReadLength;
enum class State { ReadLength, ReadEvent };
State state_ = State::ReadLength;
size_t size_{0};
int64 offset_{0};
int64 expected_size_{0};
@ -162,8 +163,6 @@ static int64 file_size(CSlice path) {
}
} // namespace detail
bool Binlog::IGNORE_ERASE_HACK = false;
Binlog::Binlog() = default;
Binlog::~Binlog() {
@ -521,17 +520,12 @@ Status Binlog::load_binlog(const Callback &callback, const Callback &debug_callb
auto need_size = r_need_size.move_as_ok();
// LOG(ERROR) << "Need size = " << need_size;
if (need_size == 0) {
if (IGNORE_ERASE_HACK && event.type_ == BinlogEvent::ServiceTypes::Empty &&
(event.flags_ & BinlogEvent::Flags::Rewrite) != 0) {
// skip erase
} else {
if (debug_callback) {
debug_callback(event);
}
do_add_event(std::move(event));
if (info_.wrong_password) {
return Status::OK();
}
if (debug_callback) {
debug_callback(event);
}
do_add_event(std::move(event));
if (info_.wrong_password) {
return Status::OK();
}
} else {
TRY_STATUS(fd_.flush_read(max(need_size, static_cast<size_t>(4096))));
@ -560,7 +554,7 @@ Status Binlog::load_binlog(const Callback &callback, const Callback &debug_callb
fd_.truncate_to_current_position(offset).ensure();
db_key_used_ = false; // force reindex
}
LOG_CHECK(IGNORE_ERASE_HACK || fd_size_ == offset) << fd_size << " " << fd_size_ << " " << offset;
LOG_CHECK(fd_size_ == offset) << fd_size << " " << fd_size_ << " " << offset;
binlog_reader_ptr_ = nullptr;
state_ = State::Run;

View File

@ -41,7 +41,6 @@ class BinlogEventsBuffer;
class Binlog {
public:
enum Error : int { WrongPassword = -1 };
static bool IGNORE_ERASE_HACK;
Binlog();
Binlog(const Binlog &other) = delete;
Binlog &operator=(const Binlog &other) = delete;

View File

@ -18,7 +18,7 @@ void HttpChunkedByteFlow::loop() {
bool was_updated = false;
size_t need_size;
while (true) {
if (state_ == ReadChunkLength) {
if (state_ == State::ReadChunkLength) {
bool ok = find_boundary(input_->clone(), "\r\n", len_);
if (len_ > 10) {
return finish(Status::Error(PSLICE() << "Too long length in chunked "
@ -35,7 +35,7 @@ void HttpChunkedByteFlow::loop() {
return finish(Status::Error(PSLICE() << "Invalid chunk size " << tag("size", len_)));
}
save_len_ = len_;
state_ = ReadChunkContent;
state_ = State::ReadChunkContent;
}
auto size = input_->size();
@ -67,7 +67,7 @@ void HttpChunkedByteFlow::loop() {
if (save_len_ == 0) {
return finish(Status::OK());
}
state_ = ReadChunkLength;
state_ = State::ReadChunkLength;
len_ = 0;
}
}

View File

@ -18,7 +18,8 @@ class HttpChunkedByteFlow final : public ByteFlowBase {
static constexpr int MAX_CHUNK_SIZE = 15 << 20; // some reasonable limit
static constexpr int MAX_SIZE = 150 << 20; // some reasonable limit
static constexpr size_t MIN_UPDATE_SIZE = 1 << 14;
enum { ReadChunkLength, ReadChunkContent, OK } state_ = ReadChunkLength;
enum class State { ReadChunkLength, ReadChunkContent, OK };
State state_ = State::ReadChunkLength;
size_t len_ = 0;
size_t save_len_ = 0;
size_t total_size_ = 0;

View File

@ -23,9 +23,9 @@ class HttpQuery {
std::vector<BufferSlice> container_;
Type type_ = Type::EMPTY;
int32 code_ = 0;
MutableSlice url_path_;
std::vector<std::pair<MutableSlice, MutableSlice>> args_;
int code_ = 0;
MutableSlice reason_;
bool keep_alive_ = true;

View File

@ -50,7 +50,7 @@ static MutableSlice urldecode_inplace(MutableSlice str, bool decode_plus_sign_as
void HttpReader::init(ChainBufferReader *input, size_t max_post_size, size_t max_files) {
input_ = input;
state_ = ReadHeaders;
state_ = State::ReadHeaders;
headers_read_length_ = 0;
content_length_ = 0;
query_ = nullptr;
@ -67,7 +67,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
}
size_t need_size = input_->size() + 1;
while (true) {
if (state_ != ReadHeaders) {
if (state_ != State::ReadHeaders) {
flow_source_.wakeup();
if (flow_sink_.is_ready() && flow_sink_.status().is_error()) {
if (!temp_file_.empty()) {
@ -81,7 +81,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
}
}
switch (state_) {
case ReadHeaders: {
case State::ReadHeaders: {
auto result = split_header();
if (result.is_error() || result.ok() != 0) {
return result;
@ -107,7 +107,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
if (content_encoding_.empty()) {
} else if (content_encoding_ == "gzip" || content_encoding_ == "deflate") {
gzip_flow_ = GzipByteFlow(Gzip::Decode);
gzip_flow_ = GzipByteFlow(Gzip::Mode::Decode);
gzip_flow_.set_max_output_size(MAX_FILE_SIZE);
*source >> gzip_flow_;
source = &gzip_flow_;
@ -125,7 +125,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
}
if (content_type_lowercased_.find("multipart/form-data") != string::npos) {
state_ = ReadMultipartFormData;
state_ = State::ReadMultipartFormData;
const char *p = std::strstr(content_type_lowercased_.c_str(), "boundary");
if (p == nullptr) {
@ -154,21 +154,21 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
}
boundary_ = "\r\n--" + boundary.str();
form_data_parse_state_ = SkipPrologue;
form_data_parse_state_ = FormDataParseState::SkipPrologue;
form_data_read_length_ = 0;
form_data_skipped_length_ = 0;
} else if (content_type_lowercased_.find("application/x-www-form-urlencoded") != string::npos ||
content_type_lowercased_.find("application/json") != string::npos) {
state_ = ReadArgs;
state_ = State::ReadArgs;
} else {
form_data_skipped_length_ = 0;
state_ = ReadContent;
state_ = State::ReadContent;
}
continue;
}
case ReadContent: {
case State::ReadContent: {
if (content_->size() > max_post_size_) {
state_ = ReadContentToFile;
state_ = State::ReadContentToFile;
continue;
}
if (flow_sink_.is_ready()) {
@ -180,7 +180,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
return need_size;
}
case ReadContentToFile: {
case State::ReadContentToFile: {
// save content to a file
if (temp_file_.empty()) {
auto file = open_temp_file("file");
@ -201,7 +201,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
return need_size;
}
case ReadArgs: {
case State::ReadArgs: {
auto size = content_->size();
if (size > MAX_TOTAL_PARAMETERS_LENGTH - total_parameters_length_) {
return Status::Error(413, "Request Entity Too Large: too much parameters");
@ -227,7 +227,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
return need_size;
}
case ReadMultipartFormData: {
case State::ReadMultipartFormData: {
TRY_RESULT(result, parse_multipart_form_data());
if (result) {
break;
@ -249,16 +249,16 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
// returns false if need more data
Result<bool> HttpReader::parse_multipart_form_data() {
while (true) {
LOG(DEBUG) << "Parsing multipart form data in state " << form_data_parse_state_;
LOG(DEBUG) << "Parsing multipart form data in state " << static_cast<int32>(form_data_parse_state_);
switch (form_data_parse_state_) {
case SkipPrologue:
case FormDataParseState::SkipPrologue:
if (find_boundary(content_->clone(), {boundary_.c_str() + 2, boundary_.size() - 2}, form_data_read_length_)) {
size_t to_skip = form_data_read_length_ + (boundary_.size() - 2);
content_->advance(to_skip);
form_data_skipped_length_ += to_skip;
form_data_read_length_ = 0;
form_data_parse_state_ = ReadPartHeaders;
form_data_parse_state_ = FormDataParseState::ReadPartHeaders;
continue;
}
@ -266,7 +266,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
form_data_skipped_length_ += form_data_read_length_;
form_data_read_length_ = 0;
return false;
case ReadPartHeaders:
case FormDataParseState::ReadPartHeaders:
if (find_boundary(content_->clone(), "\r\n\r\n", form_data_read_length_)) {
total_headers_length_ += form_data_read_length_;
if (total_headers_length_ > MAX_TOTAL_HEADERS_LENGTH) {
@ -382,11 +382,11 @@ Result<bool> HttpReader::parse_multipart_form_data() {
// don't need to save headers for files
file_field_name_ = field_name_.str();
form_data_parse_state_ = ReadFile;
form_data_parse_state_ = FormDataParseState::ReadFile;
} else {
// save headers for query parameters. They contain header names
query_->container_.push_back(std::move(headers));
form_data_parse_state_ = ReadPartValue;
form_data_parse_state_ = FormDataParseState::ReadPartValue;
}
continue;
@ -396,7 +396,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
return Status::Error(431, "Request Header Fields Too Large: total headers size exceeded");
}
return false;
case ReadPartValue:
case FormDataParseState::ReadPartValue:
if (find_boundary(content_->clone(), boundary_, form_data_read_length_)) {
if (total_parameters_length_ + form_data_read_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
return Status::Error(413, "Request Entity Too Large: too much parameters in form data");
@ -421,7 +421,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
query_->args_.emplace_back(field_name_, value);
}
form_data_parse_state_ = CheckForLastBoundary;
form_data_parse_state_ = FormDataParseState::CheckForLastBoundary;
continue;
}
CHECK(content_->size() < form_data_read_length_ + boundary_.size());
@ -430,7 +430,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
return Status::Error(413, "Request Entity Too Large: too much parameters in form data");
}
return false;
case ReadFile: {
case FormDataParseState::ReadFile: {
if (find_boundary(content_->clone(), boundary_, form_data_read_length_)) {
auto file_part = content_->cut_head(form_data_read_length_).move_as_buffer_slice();
content_->advance(boundary_.size());
@ -442,7 +442,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
query_->files_.emplace_back(file_field_name_, file_name_, field_content_type_, file_size_, temp_file_name_);
close_temp_file();
form_data_parse_state_ = CheckForLastBoundary;
form_data_parse_state_ = FormDataParseState::CheckForLastBoundary;
continue;
}
@ -455,7 +455,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
TRY_STATUS(save_file_part(std::move(file_part)));
return false;
}
case CheckForLastBoundary: {
case FormDataParseState::CheckForLastBoundary: {
if (content_->size() < 2) {
// need more data
return false;
@ -467,13 +467,13 @@ Result<bool> HttpReader::parse_multipart_form_data() {
if (x[0] == '-' && x[1] == '-') {
content_->advance(2);
form_data_skipped_length_ += 2;
form_data_parse_state_ = SkipEpilogue;
form_data_parse_state_ = FormDataParseState::SkipEpilogue;
} else {
form_data_parse_state_ = ReadPartHeaders;
form_data_parse_state_ = FormDataParseState::ReadPartHeaders;
}
continue;
}
case SkipEpilogue: {
case FormDataParseState::SkipEpilogue: {
size_t size = content_->size();
LOG(DEBUG) << "Skipping epilogue. Have " << size << " bytes";
content_->advance(size);
@ -739,12 +739,12 @@ Status HttpReader::open_temp_file(CSlice desired_file_name) {
TRY_RESULT(dir, realpath(tmp_dir, true));
CHECK(!dir.empty());
/*
auto first_try = try_open_temp_file(dir, desired_file_name);
if (first_try.is_ok()) {
return Status::OK();
}
*/
// Creation of new file with desired name has failed. Trying to create unique directory for it
TRY_RESULT(directory, mkdtemp(dir, TEMP_DIRECTORY_PREFIX));
auto second_try = try_open_temp_file(directory, desired_file_name);

View File

@ -46,7 +46,8 @@ class HttpReader {
size_t max_post_size_ = 0;
size_t max_files_ = 0;
enum { ReadHeaders, ReadContent, ReadContentToFile, ReadArgs, ReadMultipartFormData } state_;
enum class State { ReadHeaders, ReadContent, ReadContentToFile, ReadArgs, ReadMultipartFormData };
State state_ = State::ReadHeaders;
size_t headers_read_length_ = 0;
size_t content_length_ = 0;
ChainBufferReader *input_ = nullptr;
@ -68,14 +69,15 @@ class HttpReader {
string boundary_;
size_t form_data_read_length_ = 0;
size_t form_data_skipped_length_ = 0;
enum {
enum class FormDataParseState : int32 {
SkipPrologue,
ReadPartHeaders,
ReadPartValue,
ReadFile,
CheckForLastBoundary,
SkipEpilogue
} form_data_parse_state_;
};
FormDataParseState form_data_parse_state_ = FormDataParseState::SkipPrologue;
MutableSlice field_name_;
string file_field_name_;
string field_content_type_;

View File

@ -33,9 +33,9 @@ class Gzip::Impl {
};
Status Gzip::init_encode() {
CHECK(mode_ == Empty);
CHECK(mode_ == Mode::Empty);
init_common();
mode_ = Encode;
mode_ = Mode::Encode;
int ret = deflateInit2(&impl_->stream_, 6, Z_DEFLATED, 15, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) {
return Status::Error(PSLICE() << "zlib deflate init failed: " << ret);
@ -44,9 +44,9 @@ Status Gzip::init_encode() {
}
Status Gzip::init_decode() {
CHECK(mode_ == Empty);
CHECK(mode_ == Mode::Empty);
init_common();
mode_ = Decode;
mode_ = Mode::Decode;
int ret = inflateInit2(&impl_->stream_, MAX_WBITS + 32);
if (ret != Z_OK) {
return Status::Error(PSLICE() << "zlib inflate init failed: " << ret);
@ -76,19 +76,19 @@ void Gzip::set_output(MutableSlice output) {
Result<Gzip::State> Gzip::run() {
while (true) {
int ret;
if (mode_ == Decode) {
if (mode_ == Mode::Decode) {
ret = inflate(&impl_->stream_, Z_NO_FLUSH);
} else {
ret = deflate(&impl_->stream_, close_input_flag_ ? Z_FINISH : Z_NO_FLUSH);
}
if (ret == Z_OK) {
return Running;
return State::Running;
}
if (ret == Z_STREAM_END) {
// TODO(now): fail if input is not empty;
clear();
return Done;
return State::Done;
}
clear();
return Status::Error(PSLICE() << "zlib error " << ret);
@ -119,12 +119,12 @@ void Gzip::init_common() {
}
void Gzip::clear() {
if (mode_ == Decode) {
if (mode_ == Mode::Decode) {
inflateEnd(&impl_->stream_);
} else if (mode_ == Encode) {
} else if (mode_ == Mode::Encode) {
deflateEnd(&impl_->stream_);
}
mode_ = Empty;
mode_ = Mode::Empty;
}
Gzip::Gzip() : impl_(make_unique<Impl>()) {
@ -168,7 +168,7 @@ BufferSlice gzdecode(Slice s) {
return BufferSlice();
}
auto state = r_state.ok();
if (state == Gzip::Done) {
if (state == Gzip::State::Done) {
message.confirm_append(gzip.flush_output());
break;
}
@ -197,7 +197,7 @@ BufferSlice gzencode(Slice s, double k) {
return BufferSlice();
}
auto state = r_state.ok();
if (state != Gzip::Done) {
if (state != Gzip::State::Done) {
return BufferSlice();
}
message.confirm_append(gzip.flush_output());

View File

@ -24,11 +24,11 @@ class Gzip {
Gzip &operator=(Gzip &&other);
~Gzip();
enum Mode { Empty, Encode, Decode };
enum class Mode { Empty, Encode, Decode };
Status init(Mode mode) TD_WARN_UNUSED_RESULT {
if (mode == Encode) {
if (mode == Mode::Encode) {
return init_encode();
} else if (mode == Decode) {
} else if (mode == Mode::Decode) {
return init_decode();
}
clear();
@ -79,7 +79,7 @@ class Gzip {
return res;
}
enum State { Running, Done };
enum class State { Running, Done };
Result<State> run() TD_WARN_UNUSED_RESULT;
private:
@ -89,7 +89,7 @@ class Gzip {
size_t input_size_ = 0;
size_t output_size_ = 0;
bool close_input_flag_ = false;
Mode mode_ = Empty;
Mode mode_ = Mode::Empty;
void init_common();
void clear();

View File

@ -52,7 +52,7 @@ void GzipByteFlow::loop() {
return finish(r_state.move_as_error());
}
auto state = r_state.ok();
if (state == Gzip::Done) {
if (state == Gzip::State::Done) {
on_output_updated();
return consume_input();
}

View File

@ -801,6 +801,7 @@ class JsonObjectImpl : Jsonable {
private:
F f_;
};
template <class F>
auto json_object(F &&f) {
return JsonObjectImpl<F>(std::forward<F>(f));

View File

@ -44,9 +44,9 @@ Result<int> OptionsParser::run(int argc, char *argv[]) {
char buff[1024];
StringBuilder sb(MutableSlice{buff, sizeof(buff)});
for (auto &opt : options_) {
CHECK(opt.type != Option::OptionalArg);
CHECK(opt.type != Option::Type::OptionalArg);
sb << opt.short_key;
if (opt.type == Option::Arg) {
if (opt.type == Option::Type::Arg) {
sb << ":";
}
}
@ -63,7 +63,7 @@ Result<int> OptionsParser::run(int argc, char *argv[]) {
option o;
o.flag = nullptr;
o.val = opt.short_key;
o.has_arg = opt.type == Option::Arg ? required_argument : no_argument;
o.has_arg = opt.type == Option::Type::Arg ? required_argument : no_argument;
o.name = opt.long_key.c_str();
long_options.push_back(o);
}
@ -84,7 +84,7 @@ Result<int> OptionsParser::run(int argc, char *argv[]) {
for (auto &opt : options_) {
if (opt.short_key == opt_i) {
Slice arg;
if (opt.type == Option::Arg) {
if (opt.type == Option::Type::Arg) {
arg = Slice(optarg);
}
auto status = opt.arg_callback(arg);
@ -112,13 +112,13 @@ StringBuilder &operator<<(StringBuilder &sb, const OptionsParser &o) {
if (!opt.long_key.empty()) {
sb << "|--" << opt.long_key;
}
if (opt.type == OptionsParser::Option::OptionalArg) {
if (opt.type == OptionsParser::Option::Type::OptionalArg) {
sb << "[";
}
if (opt.type != OptionsParser::Option::NoArg) {
if (opt.type != OptionsParser::Option::Type::NoArg) {
sb << "<arg>";
}
if (opt.type == OptionsParser::Option::OptionalArg) {
if (opt.type == OptionsParser::Option::Type::OptionalArg) {
sb << "]";
}
sb << "\t" << opt.description;

View File

@ -18,7 +18,7 @@ namespace td {
class OptionsParser {
class Option {
public:
enum Type { NoArg, Arg, OptionalArg };
enum class Type { NoArg, Arg, OptionalArg };
Type type;
char short_key;
std::string long_key;

View File

@ -14,55 +14,68 @@
#include <iterator>
namespace td {
//TODO: fix copypaste
static const char *const symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
template <bool is_url>
static const char *get_characters() {
return is_url ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
}
string base64_encode(Slice input) {
template <bool is_url>
static const unsigned char *get_character_table() {
static unsigned char char_to_value[256];
static bool is_inited = [] {
auto characters = get_characters<is_url>();
std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast<unsigned char>(64));
for (unsigned char i = 0; i < 64; i++) {
char_to_value[static_cast<size_t>(characters[i])] = i;
}
return true;
}();
CHECK(is_inited);
return char_to_value;
}
template <bool is_url>
string base64_encode_impl(Slice input) {
auto characters = get_characters<is_url>();
string base64;
base64.reserve((input.size() + 2) / 3 * 4);
for (size_t i = 0; i < input.size();) {
size_t left = min(input.size() - i, static_cast<size_t>(3));
int c = input.ubegin()[i++] << 16;
base64 += symbols64[c >> 18];
base64 += characters[c >> 18];
if (left != 1) {
c |= input.ubegin()[i++] << 8;
}
base64 += symbols64[(c >> 12) & 63];
base64 += characters[(c >> 12) & 63];
if (left == 3) {
c |= input.ubegin()[i++];
}
if (left != 1) {
base64 += symbols64[(c >> 6) & 63];
} else {
base64 += characters[(c >> 6) & 63];
} else if (!is_url) {
base64 += '=';
}
if (left == 3) {
base64 += symbols64[c & 63];
} else {
base64 += characters[c & 63];
} else if (!is_url) {
base64 += '=';
}
}
return base64;
}
static unsigned char char_to_value[256];
static void init_base64_table() {
static bool is_inited = [] {
std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast<unsigned char>(64));
for (unsigned char i = 0; i < 64; i++) {
char_to_value[static_cast<size_t>(symbols64[i])] = i;
}
return true;
}();
CHECK(is_inited);
string base64_encode(Slice input) {
return base64_encode_impl<false>(input);
}
Result<Slice> base64_drop_padding(Slice base64) {
if ((base64.size() & 3) != 0) {
return Status::Error("Wrong string length");
}
string base64url_encode(Slice input) {
return base64_encode_impl<true>(input);
}
template <bool is_url>
Result<Slice> base64_drop_padding(Slice base64) {
size_t padding_length = 0;
while (!base64.empty() && base64.back() == '=') {
base64.remove_suffix(1);
@ -71,149 +84,77 @@ Result<Slice> base64_drop_padding(Slice base64) {
if (padding_length >= 3) {
return Status::Error("Wrong string padding");
}
if ((!is_url || padding_length > 0) && ((base64.size() + padding_length) & 3) != 0) {
return Status::Error("Wrong padding length");
}
if (is_url && (base64.size() & 3) == 1) {
return Status::Error("Wrong string length");
}
return base64;
}
template <class F>
Status base64_do_decode(Slice base64, F &&append) {
static Status do_base64_decode_impl(Slice base64, const unsigned char *table, char *ptr) {
for (size_t i = 0; i < base64.size();) {
size_t left = min(base64.size() - i, static_cast<size_t>(4));
int c = 0;
for (size_t t = 0; t < left; t++) {
auto value = char_to_value[base64.ubegin()[i++]];
auto value = table[base64.ubegin()[i++]];
if (value == 64) {
return Status::Error("Wrong character in the string");
}
c |= value << ((3 - t) * 6);
}
append(static_cast<char>(static_cast<unsigned char>(c >> 16))); // implementation-defined
*ptr++ = static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined
if (left == 2) {
if ((c & ((1 << 16) - 1)) != 0) {
return Status::Error("Wrong padding in the string");
}
} else {
append(static_cast<char>(static_cast<unsigned char>(c >> 8))); // implementation-defined
*ptr++ = static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined
if (left == 3) {
if ((c & ((1 << 8) - 1)) != 0) {
return Status::Error("Wrong padding in the string");
}
} else {
append(static_cast<char>(static_cast<unsigned char>(c))); // implementation-defined
*ptr++ = static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined
}
}
}
return Status::OK();
}
template <class T>
static T create_empty(size_t size);
template <>
string create_empty<string>(size_t size) {
return string(size, '\0');
}
template <>
SecureString create_empty<SecureString>(size_t size) {
return SecureString{size};
}
template <bool is_url, class T>
static Result<T> base64_decode_impl(Slice base64) {
TRY_RESULT_ASSIGN(base64, base64_drop_padding<is_url>(base64));
T result = create_empty<T>(base64.size() / 4 * 3 + ((base64.size() & 3) + 1) / 2);
TRY_STATUS(do_base64_decode_impl(base64, get_character_table<is_url>(), as_mutable_slice(result).begin()));
return std::move(result);
}
Result<string> base64_decode(Slice base64) {
init_base64_table();
TRY_RESULT_ASSIGN(base64, base64_drop_padding(base64));
string output;
output.reserve(((base64.size() + 3) >> 2) * 3);
TRY_STATUS(base64_do_decode(base64, [&output](char c) { output += c; }));
return output;
return base64_decode_impl<false, string>(base64);
}
Result<SecureString> base64_decode_secure(Slice base64) {
init_base64_table();
TRY_RESULT_ASSIGN(base64, base64_drop_padding(base64));
SecureString output(((base64.size() + 3) >> 2) * 3);
char *ptr = output.as_mutable_slice().begin();
TRY_STATUS(base64_do_decode(base64, [&ptr](char c) { *ptr++ = c; }));
size_t size = ptr - output.as_mutable_slice().begin();
if (size == output.size()) {
return std::move(output);
}
return SecureString(output.as_slice().substr(0, size));
}
static const char *const url_symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
string base64url_encode(Slice input) {
string base64;
base64.reserve((input.size() + 2) / 3 * 4);
for (size_t i = 0; i < input.size();) {
size_t left = min(input.size() - i, static_cast<size_t>(3));
int c = input.ubegin()[i++] << 16;
base64 += url_symbols64[c >> 18];
if (left != 1) {
c |= input.ubegin()[i++] << 8;
}
base64 += url_symbols64[(c >> 12) & 63];
if (left == 3) {
c |= input.ubegin()[i++];
}
if (left != 1) {
base64 += url_symbols64[(c >> 6) & 63];
}
if (left == 3) {
base64 += url_symbols64[c & 63];
}
}
return base64;
}
static unsigned char url_char_to_value[256];
static void init_base64url_table() {
static bool is_inited = [] {
std::fill(std::begin(url_char_to_value), std::end(url_char_to_value), static_cast<unsigned char>(64));
for (unsigned char i = 0; i < 64; i++) {
url_char_to_value[static_cast<size_t>(url_symbols64[i])] = i;
}
return true;
}();
CHECK(is_inited);
return base64_decode_impl<false, SecureString>(base64);
}
Result<string> base64url_decode(Slice base64) {
init_base64url_table();
size_t padding_length = 0;
while (!base64.empty() && base64.back() == '=') {
base64.remove_suffix(1);
padding_length++;
}
if (padding_length >= 3 || (padding_length > 0 && ((base64.size() + padding_length) & 3) != 0)) {
return Status::Error("Wrong string padding");
}
if ((base64.size() & 3) == 1) {
return Status::Error("Wrong string length");
}
string output;
output.reserve(((base64.size() + 3) >> 2) * 3);
for (size_t i = 0; i < base64.size();) {
size_t left = min(base64.size() - i, static_cast<size_t>(4));
int c = 0;
for (size_t t = 0; t < left; t++) {
auto value = url_char_to_value[base64.ubegin()[i++]];
if (value == 64) {
return Status::Error("Wrong character in the string");
}
c |= value << ((3 - t) * 6);
}
output += static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined
if (left == 2) {
if ((c & ((1 << 16) - 1)) != 0) {
return Status::Error("Wrong padding in the string");
}
} else {
output += static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined
if (left == 3) {
if ((c & ((1 << 8) - 1)) != 0) {
return Status::Error("Wrong padding in the string");
}
} else {
output += static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined
}
}
}
return output;
return base64_decode_impl<true, string>(base64);
}
template <bool is_url>
@ -233,14 +174,7 @@ static bool is_base64_impl(Slice input) {
return false;
}
unsigned char *table;
if (is_url) {
init_base64url_table();
table = url_char_to_value;
} else {
init_base64_table();
table = char_to_value;
}
auto table = get_character_table<is_url>();
for (auto c : input) {
if (table[static_cast<unsigned char>(c)] == 64) {
return false;
@ -271,12 +205,31 @@ bool is_base64url(Slice input) {
return is_base64_impl<true>(input);
}
template <bool is_url>
static bool is_base64_characters_impl(Slice input) {
auto table = get_character_table<is_url>();
for (auto c : input) {
if (table[static_cast<unsigned char>(c)] == 64) {
return false;
}
}
return true;
}
bool is_base64_characters(Slice input) {
return is_base64_characters_impl<false>(input);
}
bool is_base64url_characters(Slice input) {
return is_base64_characters_impl<true>(input);
}
string base64_filter(Slice input) {
auto table = get_character_table<false>();
string res;
res.reserve(input.size());
init_base64_table();
for (auto c : input) {
if (char_to_value[static_cast<unsigned char>(c)] != 64 || c == '=') {
if (table[static_cast<unsigned char>(c)] != 64 || c == '=') {
res += c;
}
}

View File

@ -23,6 +23,9 @@ Result<string> base64url_decode(Slice base64);
bool is_base64(Slice input);
bool is_base64url(Slice input);
bool is_base64_characters(Slice input);
bool is_base64url_characters(Slice input);
string base64_filter(Slice input);
} // namespace td

View File

@ -32,6 +32,7 @@ template <>
BufferSlice create_empty<BufferSlice>(size_t size) {
return BufferSlice{size};
}
template <>
SecureString create_empty<SecureString>(size_t size) {
return SecureString{size};

View File

@ -38,7 +38,7 @@ class RegressionTesterImpl : public RegressionTester {
}
RegressionTesterImpl(string db_path, string db_cache_dir) : db_path_(db_path), db_cache_dir_(db_cache_dir) {
load_db(db_path);
load_db(db_path).ignore();
if (db_cache_dir_.empty()) {
db_cache_dir_ = PathView(db_path).without_extension().str() + ".cache/";
}

View File

@ -42,8 +42,8 @@ TEST(Gzip, flow) {
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
td::GzipByteFlow gzip_flow(td::Gzip::Encode);
gzip_flow = td::GzipByteFlow(td::Gzip::Encode);
td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode);
gzip_flow = td::GzipByteFlow(td::Gzip::Mode::Encode);
td::ByteFlowSink sink;
source >> gzip_flow >> sink;
@ -70,7 +70,7 @@ TEST(Gzip, flow_error) {
auto input_writer = td::ChainBufferWriter();
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
td::GzipByteFlow gzip_flow(td::Gzip::Decode);
td::GzipByteFlow gzip_flow(td::Gzip::Mode::Decode);
td::ByteFlowSink sink;
source >> gzip_flow >> sink;
@ -92,10 +92,10 @@ TEST(Gzip, encode_decode_flow) {
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
td::GzipByteFlow gzip_encode_flow(td::Gzip::Encode);
td::GzipByteFlow gzip_decode_flow(td::Gzip::Decode);
td::GzipByteFlow gzip_encode_flow2(td::Gzip::Encode);
td::GzipByteFlow gzip_decode_flow2(td::Gzip::Decode);
td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode);
td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode);
td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode);
td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode);
td::ByteFlowSink sink;
source >> gzip_encode_flow >> gzip_decode_flow >> gzip_encode_flow2 >> gzip_decode_flow2 >> sink;

View File

@ -163,7 +163,9 @@ TEST(Misc, base64) {
ASSERT_TRUE(is_base64("dGVzdB==") == false);
ASSERT_TRUE(is_base64("dGVzdA=") == false);
ASSERT_TRUE(is_base64("dGVzdA") == false);
ASSERT_TRUE(is_base64("dGVzd") == false);
ASSERT_TRUE(is_base64("dGVz") == true);
ASSERT_TRUE(is_base64("dGVz====") == false);
ASSERT_TRUE(is_base64("") == true);
ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true);
ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false);
@ -175,7 +177,9 @@ TEST(Misc, base64) {
ASSERT_TRUE(is_base64url("dGVzdB==") == false);
ASSERT_TRUE(is_base64url("dGVzdA=") == false);
ASSERT_TRUE(is_base64url("dGVzdA") == true);
ASSERT_TRUE(is_base64url("dGVzd") == false);
ASSERT_TRUE(is_base64url("dGVz") == true);
ASSERT_TRUE(is_base64url("dGVz====") == false);
ASSERT_TRUE(is_base64url("") == true);
ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true);
ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == false);
@ -183,6 +187,30 @@ TEST(Misc, base64) {
ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false);
ASSERT_TRUE(is_base64url("====") == false);
ASSERT_TRUE(is_base64_characters("dGVzdA==") == false);
ASSERT_TRUE(is_base64_characters("dGVzdB==") == false);
ASSERT_TRUE(is_base64_characters("dGVzdA=") == false);
ASSERT_TRUE(is_base64_characters("dGVzdA") == true);
ASSERT_TRUE(is_base64_characters("dGVz") == true);
ASSERT_TRUE(is_base64_characters("") == true);
ASSERT_TRUE(is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true);
ASSERT_TRUE(is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false);
ASSERT_TRUE(is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
ASSERT_TRUE(is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false);
ASSERT_TRUE(is_base64_characters("====") == false);
ASSERT_TRUE(is_base64url_characters("dGVzdA==") == false);
ASSERT_TRUE(is_base64url_characters("dGVzdB==") == false);
ASSERT_TRUE(is_base64url_characters("dGVzdA=") == false);
ASSERT_TRUE(is_base64url_characters("dGVzdA") == true);
ASSERT_TRUE(is_base64url_characters("dGVz") == true);
ASSERT_TRUE(is_base64url_characters("") == true);
ASSERT_TRUE(is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true);
ASSERT_TRUE(is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == false);
ASSERT_TRUE(is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
ASSERT_TRUE(is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false);
ASSERT_TRUE(is_base64url_characters("====") == false);
for (int l = 0; l < 300000; l += l / 20 + l / 1000 * 500 + 1) {
for (int t = 0; t < 10; t++) {
string s = rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l);
@ -195,6 +223,10 @@ TEST(Misc, base64) {
decoded = base64_decode(encoded);
ASSERT_TRUE(decoded.is_ok());
ASSERT_TRUE(decoded.ok() == s);
auto decoded_secure = base64_decode_secure(encoded);
ASSERT_TRUE(decoded_secure.is_ok());
ASSERT_TRUE(decoded_secure.ok().as_slice() == s);
}
}
@ -207,6 +239,9 @@ TEST(Misc, base64) {
ASSERT_TRUE(base64_encode(" /'.;.';≤.];,].',[.;/,.;/]/..;!@#!*(%?::;!%\";") ==
"ICAgICAgLycuOy4nO+KJpC5dOyxdLicsWy47LywuOy9dLy4uOyFAIyEqKCU/"
"Ojo7ISUiOw==");
ASSERT_TRUE(base64url_encode("ab><") == "YWI-PA");
ASSERT_TRUE(base64url_encode("ab><c") == "YWI-PGM");
ASSERT_TRUE(base64url_encode("ab><cd") == "YWI-PGNk");
}
template <class T>

View File

@ -356,11 +356,11 @@ TEST(Http, gzip_chunked_flow) {
auto str = rand_string('a', 'z', 1000000);
auto parts = rand_split(make_chunked(gzencode(str).as_slice().str()));
td::ChainBufferWriter input_writer;
ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
ByteFlowSource source(&input);
HttpChunkedByteFlow chunked_flow;
GzipByteFlow gzip_flow(Gzip::Decode);
GzipByteFlow gzip_flow(Gzip::Mode::Decode);
ByteFlowSink sink;
source >> chunked_flow >> gzip_flow >> sink;

View File

@ -564,9 +564,9 @@ class FastPingTestActor : public Actor {
unique_ptr<mtproto::AuthData> auth_data;
if (iteration_ % 2 == 0) {
auth_data = make_unique<mtproto::AuthData>();
auth_data->set_tmp_auth_key(handshake_->auth_key);
auth_data->set_server_time_difference(handshake_->server_time_diff);
auth_data->set_server_salt(handshake_->server_salt, Time::now());
auth_data->set_tmp_auth_key(handshake_->release_auth_key());
auth_data->set_server_time_difference(handshake_->get_server_time_diff());
auth_data->set_server_salt(handshake_->get_server_salt(), Time::now());
auth_data->set_future_salts({mtproto::ServerSalt{0u, 1e20, 1e30}}, Time::now());
auth_data->set_use_pfs(true);
uint64 session_id = 0;