Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2022-02-05 01:12:29 +01:00
commit 39856bc56f
87 changed files with 4835 additions and 1946 deletions

View File

@ -134,7 +134,7 @@ function(td_set_up_compiler)
endif()
if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
add_cxx_compiler_flag("-Wno-maybe-uninitialized") # too much false positives
add_cxx_compiler_flag("-Wno-maybe-uninitialized") # too many false positives
endif()
if (WIN32 AND GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0))
# warns about casts of function pointers returned by GetProcAddress

View File

@ -6,7 +6,7 @@ if (POLICY CMP0065)
cmake_policy(SET CMP0065 NEW)
endif()
project(TDLib VERSION 1.8.0 LANGUAGES CXX C)
project(TDLib VERSION 1.8.1 LANGUAGES CXX C)
if (NOT DEFINED CMAKE_MODULE_PATH)
set(CMAKE_MODULE_PATH "")
@ -360,6 +360,7 @@ set(TDLIB_SOURCE
td/telegram/MessageContentType.cpp
td/telegram/MessageEntity.cpp
td/telegram/MessageId.cpp
td/telegram/MessageReaction.cpp
td/telegram/MessageReplyInfo.cpp
td/telegram/MessagesDb.cpp
td/telegram/MessageSearchFilter.cpp
@ -413,6 +414,7 @@ set(TDLIB_SOURCE
td/telegram/SpecialStickerSetType.cpp
td/telegram/SponsoredMessageManager.cpp
td/telegram/StateManager.cpp
td/telegram/StickerFormat.cpp
td/telegram/StickersManager.cpp
td/telegram/StorageManager.cpp
td/telegram/MemoryManager.cpp
@ -565,6 +567,7 @@ set(TDLIB_SOURCE
td/telegram/MessageEntity.h
td/telegram/MessageId.h
td/telegram/MessageLinkInfo.h
td/telegram/MessageReaction.h
td/telegram/MessageReplyInfo.h
td/telegram/MessageThreadInfo.h
td/telegram/MessagesDb.h
@ -640,6 +643,7 @@ set(TDLIB_SOURCE
td/telegram/SpecialStickerSetType.h
td/telegram/SponsoredMessageManager.h
td/telegram/StateManager.h
td/telegram/StickerFormat.h
td/telegram/StickerSetId.h
td/telegram/StickersManager.h
td/telegram/StorageManager.h
@ -681,6 +685,7 @@ set(TDLIB_SOURCE
td/telegram/Game.hpp
td/telegram/InputMessageText.hpp
td/telegram/MessageEntity.hpp
td/telegram/MessageReaction.hpp
td/telegram/MessageReplyInfo.hpp
td/telegram/MinChannel.hpp
td/telegram/NotificationSettings.hpp

View File

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

View File

@ -156,7 +156,7 @@ td::Result<TlsInfo> test_tls(const td::string &url) {
size_t server_hello_length = 0;
size_t encrypted_application_data_length_sum = 0;
while (td::Time::now() < end_time) {
char buf[20000];
static char buf[20000];
TRY_RESULT(res, socket.read(td::MutableSlice{buf, sizeof(buf)}));
if (res > 0) {
auto read_length = [&]() -> size_t {

View File

@ -909,7 +909,7 @@ function onOptionsChanged() {
commands.push('git clone https://github.com/tdlight-team/tdlight.git');
commands.push('cd tdlight');
commands.push('git checkout v1.8.0');
// commands.push('git checkout v1.8.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.4 FATAL_ERROR)
project(TdExample VERSION 1.0 LANGUAGES CXX)
find_package(Td 1.8.0 REQUIRED)
find_package(Td 1.8.1 REQUIRED)
add_executable(tdjson_example tdjson_example.cpp)
target_link_libraries(tdjson_example PRIVATE Td::TdJson)

View File

@ -41,7 +41,7 @@ function prepare {
cd build-native
cmake "$td_root" -A Win32 -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=ON
cmake "$td_root" -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=ON
CheckLastExitCode
cmake --build . --target prepare_cross_compiling
CheckLastExitCode

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.8.0" Language="en-US" Publisher="Telegram LLC" />
<Identity Id="Telegram.Td.UWP" Version="1.8.1" 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

@ -6,7 +6,7 @@ You need a Unix shell with `sed`, `tar` and `wget` utilities to run the provided
## Building tdweb NPM package
* Install the 2.0.6 [emsdk](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html), which is known to work. Do not use the system-provided `emscripten` package, because it contains a too old emsdk version.
* Install the 3.1.1 [emsdk](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html), which is known to work. Do not use the system-provided `emscripten` package, because it contains a too old emsdk version.
* Install all `TDLib` build dependencies described in [Building](https://github.com/tdlib/td#building) and `sed`, `tar` and `wget` utilities.
* Run `source ./emsdk_env.sh` from `emsdk` directory to set up the correct build environment.
* On `macOS`, install the `coreutils` [Homebrew](https://brew.sh) package and replace `realpath` in scripts with `grealpath`:

View File

@ -1,6 +1,6 @@
{
"name": "tdweb",
"version": "1.6.6",
"version": "1.8.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "tdweb",
"version": "1.8.0",
"version": "1.8.1",
"description": "JavaScript interface for TDLib (Telegram library)",
"main": "dist/tdweb.js",
"repository": {

View File

@ -73,6 +73,7 @@ async function loadTdlibWasm(onFS, wasmUrl) {
let module = createTdwebModule({
onRuntimeInitialized: () => {
log.info('runtime intialized');
onFS(module.FS);
},
instantiateWasm: (imports, successCallback) => {
log.info('start instantiateWasm', td_wasm, imports);
@ -85,7 +86,6 @@ async function loadTdlibWasm(onFS, wasmUrl) {
},
ENVIROMENT: 'WORKER'
});
onFS(module.FS); // hack
log.info('Wait module');
module = await module;
log.info('Got module', module);
@ -103,6 +103,7 @@ async function loadTdlibAsmjs(onFS) {
let module = createTdwebModule({
onRuntimeInitialized: () => {
console.log('runtime intialized');
onFS(module.FS);
},
locateFile: name => {
if (name === fromFile) {
@ -112,7 +113,6 @@ async function loadTdlibAsmjs(onFS) {
},
ENVIROMENT: 'WORKER'
});
onFS(module.FS); // hack
log.info('Wait module');
module = await module;
log.info('Got module', module);
@ -614,7 +614,11 @@ class TdClient {
this.TdModule = await loadTdlib(mode, this.onFS, options.wasmUrl);
log.info('got TdModule');
this.td_functions = {
td_create: this.TdModule.cwrap('td_emscripten_create_client_id', 'number', []),
td_create: this.TdModule.cwrap(
'td_emscripten_create_client_id',
'number',
[]
),
td_send: this.TdModule.cwrap('td_emscripten_send', null, [
'number',
'string'

View File

@ -1,3 +1,3 @@
./src.ps1 | Select-String -NotMatch "CxCli.h" | Select-String -NotMatch "DotNet" | Select-String -NotMatch "/tl-parser/" | ForEach-Object {
./src.ps1 | Select-String -NotMatch "CxCli.h" | Select-String -CaseSensitive -NotMatch "DotNet" | Select-String -NotMatch "tl/tl_dotnet_object.h" | Select-String -NotMatch "/tl-parser/" | ForEach-Object {
clang-format -verbose -style=file -i $_
}

View File

@ -1,3 +1,3 @@
#!/bin/sh
cd $(dirname $0)
./src.sh | grep -v CxCli.h | grep -iv dotnet | grep -v /tl-parser/ | xargs -n 1 clang-format -verbose -style=file -i
./src.sh | grep -v CxCli.h | grep -v DotNet | grep -v tl/tl_dotnet_object.h | grep -v /tl-parser/ | xargs -n 1 clang-format -verbose -style=file -i

View File

@ -191,26 +191,29 @@ photoSize type:string photo:file width:int32 height:int32 progressive_sizes:vect
minithumbnail width:int32 height:int32 data:bytes = Minithumbnail;
//@class ThumbnailFormat @description Describes format of the thumbnail
//@class ThumbnailFormat @description Describes format of a thumbnail
//@description The thumbnail is in JPEG format
thumbnailFormatJpeg = ThumbnailFormat;
//@description The thumbnail is in PNG format. It will be used only for background patterns
thumbnailFormatPng = ThumbnailFormat;
//@description The thumbnail is in WEBP format. It will be used only for some stickers
thumbnailFormatWebp = ThumbnailFormat;
//@description The thumbnail is in static GIF format. It will be used only for some bot inline results
thumbnailFormatGif = ThumbnailFormat;
//@description The thumbnail is in TGS format. It will be used only for animated sticker sets
thumbnailFormatTgs = ThumbnailFormat;
//@description The thumbnail is in MPEG4 format. It will be used only for some animations and videos
thumbnailFormatMpeg4 = ThumbnailFormat;
//@description The thumbnail is in PNG format. It will be used only for background patterns
thumbnailFormatPng = ThumbnailFormat;
//@description The thumbnail is in TGS format. It will be used only for TGS sticker sets
thumbnailFormatTgs = ThumbnailFormat;
//@description The thumbnail is in WEBM format. It will be used only for WEBM sticker sets
thumbnailFormatWebm = ThumbnailFormat;
//@description The thumbnail is in WEBP format. It will be used only for some stickers
thumbnailFormatWebp = ThumbnailFormat;
//@description Represents a thumbnail @format Thumbnail format @width Thumbnail width @height Thumbnail height @file The thumbnail
thumbnail format:ThumbnailFormat width:int32 height:int32 file:file = Thumbnail;
@ -237,6 +240,21 @@ maskPointChin = MaskPoint;
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
//@class StickerType @description Describes type of a sticker
//@description The sticker is an image in WEBP format
stickerTypeStatic = StickerType;
//@description The sticker is an animation in TGS format
stickerTypeAnimated = StickerType;
//@description The sticker is a video in WEBM format
stickerTypeVideo = StickerType;
//@description The sticker is a mask in WEBP format to be placed on photos or videos @mask_position Position where the mask is placed; may be null
stickerTypeMask mask_position:maskPosition = StickerType;
//@description Represents a closed vector path. The path begins at the end point of the last command @commands List of vector path commands
closedVectorPath commands:vector<VectorPathCommand> = ClosedVectorPath;
@ -277,9 +295,9 @@ document file_name:string mime_type:string minithumbnail:minithumbnail thumbnail
photo has_stickers:Bool minithumbnail:minithumbnail sizes:vector<photoSize> = Photo;
//@description Describes a sticker @set_id The identifier of the sticker set to which the sticker belongs; 0 if none @width Sticker width; as defined by the sender @height Sticker height; as defined by the sender
//@emoji Emoji corresponding to the sticker @is_animated True, if the sticker is an animated sticker in TGS format @is_mask True, if the sticker is a mask @mask_position Position where the mask is placed; may be null
//@outline Sticker's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner @thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
sticker set_id:int64 width:int32 height:int32 emoji:string is_animated:Bool is_mask:Bool mask_position:maskPosition outline:vector<closedVectorPath> thumbnail:thumbnail sticker:file = Sticker;
//@emoji Emoji corresponding to the sticker @type Sticker type @outline Sticker's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
//@thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
sticker set_id:int64 width:int32 height:int32 emoji:string type:StickerType outline:vector<closedVectorPath> thumbnail:thumbnail sticker:file = Sticker;
//@description Describes a video file @duration Duration of the video, in seconds; as defined by the sender @width Video width; as defined by the sender @height Video height; as defined by the sender
//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender
@ -686,7 +704,7 @@ supergroup id:int53 username:string date:int32 status:ChatMemberStatus member_co
//@is_all_history_available True, if new chat members will have access to old messages. In public or discussion groups and both public and private channels, old messages are always available, so this option affects only private supergroups without a linked chat. The value of this field is only available for chat administrators
//@sticker_set_id Identifier of the supergroup sticker set; 0 if none
//@location Location to which the supergroup is connected; may be null
//@invite_link Primary invite link for this chat; may be null. For chat administrators with can_invite_users right only
//@invite_link Primary invite link for the chat; may be null. For chat administrators with can_invite_users right only
//@bot_commands List of commands of bots in the group
//@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none
//@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none
@ -768,11 +786,25 @@ messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announc
//@last_message_id Identifier of the last reply to the message
messageReplyInfo reply_count:int32 recent_replier_ids:vector<MessageSender> last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 last_message_id:int53 = MessageReplyInfo;
//@description Contains information about a reaction to a message
//@reaction Text representation of the reaction
//@total_count Number of times the reaction was added
//@is_chosen True, if the reaction is chosen by the current user
//@recent_sender_ids Identifiers of at most 3 recent message senders, added the reaction; available in private chats, basic groups and supergroups
messageReaction reaction:string total_count:int32 is_chosen:Bool recent_sender_ids:vector<MessageSender> = MessageReaction;
//@description Contains information about interactions with a message
//@view_count Number of times the message was viewed
//@forward_count Number of times the message was forwarded
//@reply_info Information about direct or indirect replies to the message; may be null. Currently, available only in channels with a discussion supergroup and discussion supergroups for messages, which are not replies itself
messageInteractionInfo view_count:int32 forward_count:int32 reply_info:messageReplyInfo = MessageInteractionInfo;
//@reactions The list of reactions added to the message
messageInteractionInfo view_count:int32 forward_count:int32 reply_info:messageReplyInfo reactions:vector<messageReaction> = MessageInteractionInfo;
//@description Contains information about an unread reaction to a message
//@reaction Text representation of the reaction
//@sender_id Identifier of the sender, added the reaction
//@is_big True, if the reaction was added with a big animation
unreadReaction reaction:string sender_id:MessageSender is_big:Bool = UnreadReaction;
//@class MessageSendingState @description Contains information about the sending state of the message
@ -800,10 +832,11 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool n
//@can_be_saved True, if content of the message can be saved locally or copied
//@can_be_deleted_only_for_self True, if the message can be deleted only for the current user while other users will continue to see it
//@can_be_deleted_for_all_users True, if the message can be deleted for all users
//@can_get_statistics True, if the message statistics are available
//@can_get_message_thread True, if the message thread info is available
//@can_get_added_reactions True, if the list of added reactions is available through getMessageAddedReactions
//@can_get_statistics True, if the message statistics are available through getMessageStatistics
//@can_get_message_thread True, if the message thread info is available through getMessageThread
//@can_get_viewers True, if chat members already viewed the message can be received through getMessageViewers
//@can_get_media_timestamp_links True, if media timestamp links can be generated for media timestamp entities in the message text, caption or web page description
//@can_get_media_timestamp_links True, if media timestamp links can be generated for media timestamp entities in the message text, caption or web page description through getMessageLink
//@has_timestamped_media True, if media timestamp entities refers to a media in this message as opposed to a media in the replied message
//@is_channel_post True, if the message is a channel post. All messages to channels are channel posts, all other messages are not channel posts
//@contains_unread_mention True, if the message contains an unread mention for the current user
@ -811,6 +844,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool n
//@edit_date Point in time (Unix timestamp) when the message was last edited
//@forward_info Information about the initial message sender; may be null
//@interaction_info Information about interactions with the message; may be null
//@unread_reactions Information about unread reactions added to the message
//@reply_in_chat_id If non-zero, the identifier of the chat to which the replied message belongs; Currently, only messages in the Replies chat can have different reply_in_chat_id and chat_id
//@reply_to_message_id If non-zero, the identifier of the message this message is replying to; can be the identifier of a deleted message
//@message_thread_id If non-zero, the identifier of the message thread the message belongs to; unique within the chat to which the message belongs
@ -822,7 +856,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool n
//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted
//@content Content of the message
//@reply_markup Reply markup for the message; may be null
message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_saved:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_viewers:Bool can_get_media_timestamp_links:Bool has_timestamped_media:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int53 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message;
message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_saved:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_added_reactions:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_viewers:Bool can_get_media_timestamp_links:Bool has_timestamped_media:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo unread_reactions:vector<unreadReaction> reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int53 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message;
//@description Contains a list of messages @total_count Approximate total count of messages found @messages List of messages; messages may be null
messages total_count:int32 messages:vector<message> = Messages;
@ -993,7 +1027,9 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa
//@last_read_inbox_message_id Identifier of the last read incoming message
//@last_read_outbox_message_id Identifier of the last read outgoing message
//@unread_mention_count Number of unread messages with a mention/reply in the chat
//@notification_settings Notification settings for this chat
//@unread_reaction_count Number of messages with unread reactions in the chat
//@notification_settings Notification settings for the chat
//@available_reactions List of reactions, available in the chat
//@message_ttl Current message Time To Live setting (self-destruct timer) for the chat; 0 if not defined. TTL is counted from the time message or its content is viewed in secret chats and from the send date in other chats
//@theme_name If non-empty, name of a theme, set for the chat
//@action_bar Information about actions which must be possible to do through the chat action bar; may be null
@ -1002,7 +1038,7 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa
//@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat
//@draft_message A draft of a message in the chat; may be null
//@client_data Application-specific data associated with the chat. (For example, the chat scroll position or local chat notification settings can be stored here.) Persistent if the message database is used
chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector<chatPosition> message_sender_id:MessageSender has_protected_content:Bool is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:chatNotificationSettings message_ttl:int32 theme_name:string action_bar:ChatActionBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat;
chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector<chatPosition> message_sender_id:MessageSender has_protected_content:Bool is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:vector<string> message_ttl:int32 theme_name:string action_bar:ChatActionBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat;
//@description Represents a list of chats @total_count Approximate total count of chats found @chat_ids List of chat identifiers
chats total_count:int32 chat_ids:vector<int53> = Chats;
@ -2095,6 +2131,9 @@ searchMessagesFilterMention = SearchMessagesFilter;
//@description Returns only messages with unread mentions of the current user, or messages that are replies to their messages. When using this filter the results can't be additionally filtered by a query, a message thread or by the sending user
searchMessagesFilterUnreadMention = SearchMessagesFilter;
//@description Returns only messages with unread reactions for the current user. When using this filter the results can't be additionally filtered by a query, a message thread or by the sending user
searchMessagesFilterUnreadReaction = SearchMessagesFilter;
//@description Returns only failed to send messages. This filter can be used only if the message database is used
searchMessagesFilterFailedToSend = SearchMessagesFilter;
@ -2178,20 +2217,20 @@ stickers stickers:vector<sticker> = Stickers;
emojis emojis:vector<string> = Emojis;
//@description Represents a sticker set
//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP or TGS format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed
//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed
//@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
//@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously
//@is_official True, if the sticker set is official @is_animated True, is the stickers in the set are animated @is_masks True, if the stickers in the set are masks @is_viewed True for already viewed trending sticker sets
//@is_official True, if the sticker set is official @sticker_type Type of the stickers in the set @is_viewed True for already viewed trending sticker sets
//@stickers List of stickers in this set @emojis A list of emoji corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object
stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool is_animated:Bool is_masks:Bool is_viewed:Bool stickers:vector<sticker> emojis:vector<emojis> = StickerSet;
stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool sticker_type:StickerType is_viewed:Bool stickers:vector<sticker> emojis:vector<emojis> = StickerSet;
//@description Represents short information about a sticker set
//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP or TGS format with width and height 100; may be null
//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null
//@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
//@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously
//@is_official True, if the sticker set is official @is_animated True, is the stickers in the set are animated @is_masks True, if the stickers in the set are masks @is_viewed True for already viewed trending sticker sets
//@is_official True, if the sticker set is official @sticker_type Type of the stickers in the set @is_viewed True for already viewed trending sticker sets
//@size Total number of stickers in the set @covers Up to the first 5 stickers from the set, depending on the context. If the application needs more stickers the full sticker set needs to be requested
stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool is_animated:Bool is_masks:Bool is_viewed:Bool size:int32 covers:vector<sticker> = StickerSetInfo;
stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool sticker_type:StickerType is_viewed:Bool size:int32 covers:vector<sticker> = StickerSetInfo;
//@description Represents a list of sticker sets @total_count Approximate total number of sticker sets found @sets List of sticker sets
stickerSets total_count:int32 sets:vector<stickerSetInfo> = StickerSets;
@ -2374,6 +2413,30 @@ call id:int32 user_id:int53 is_outgoing:Bool is_video:Bool state:CallState = Cal
phoneNumberAuthenticationSettings allow_flash_call:Bool allow_missed_call:Bool is_current_phone_number:Bool allow_sms_retriever_api:Bool authentication_tokens:vector<string> = PhoneNumberAuthenticationSettings;
//@description Represents a reaction applied to a message @reaction Text representation of the reaction @sender_id Identifier of the chat member, applied the reaction
addedReaction reaction:string sender_id:MessageSender = AddedReaction;
//@description Represents a list of reactions added to a message @total_count The total count of found reactions @reactions The list of added reactions @next_offset The offset for the next request. If empty, there are no more results
addedReactions total_count:int32 reactions:vector<addedReaction> next_offset:string = AddedReactions;
//@description Represents a list of available reactions @reactions List of reactions
availableReactions reactions:vector<string> = AvailableReactions;
//@description Contains stickers which must be used for reaction animation rendering
//@reaction Text representation of the reaction
//@title Reaction title
//@is_active True, if the reaction can be added to new messages and enabled in chats
//@static_icon Static icon for the reaction
//@appear_animation Appear animation for the reaction
//@select_animation Select animation for the reaction
//@activate_animation Activate animation for the reaction
//@effect_animation Effect animation for the reaction
//@around_animation Around animation for the reaction; may be null
//@center_animation Center animation for the reaction; may be null
reaction reaction:string title:string is_active:Bool static_icon:sticker appear_animation:sticker select_animation:sticker activate_animation:sticker effect_animation:sticker around_animation:sticker center_animation:sticker = Reaction;
//@description Represents a list of animations @animations List of animations
animations animations:vector<animation> = Animations;
@ -2451,8 +2514,8 @@ inputInlineQueryResultLocation id:string location:location live_period:int32 tit
//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessagePhoto, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultPhoto id:string title:string description:string thumbnail_url:string photo_url:string photo_width:int32 photo_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a link to a WEBP or TGS sticker @id Unique identifier of the query result @thumbnail_url URL of the sticker thumbnail, if it exists
//@sticker_url The URL of the WEBP or TGS sticker (sticker file size must not exceed 5MB) @sticker_width Width of the sticker @sticker_height Height of the sticker
//@description Represents a link to a WEBP, TGS, or WEBM sticker @id Unique identifier of the query result @thumbnail_url URL of the sticker thumbnail, if it exists
//@sticker_url The URL of the WEBP, TGS, or WEBM sticker (sticker file size must not exceed 5MB) @sticker_width Width of the sticker @sticker_height Height of the sticker
//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageSticker, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultSticker id:string thumbnail_url:string sticker_url:string sticker_width:int32 sticker_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
@ -2556,15 +2619,15 @@ chatEventMessageEdited old_message:message new_message:message = ChatEventAction
//@description A message was deleted @message Deleted message
chatEventMessageDeleted message:message = ChatEventAction;
//@description A poll in a message was stopped @message The message with the poll
chatEventPollStopped message:message = ChatEventAction;
//@description A message was pinned @message Pinned message
chatEventMessagePinned message:message = ChatEventAction;
//@description A message was unpinned @message Unpinned message
chatEventMessageUnpinned message:message = ChatEventAction;
//@description A poll in a message was stopped @message The message with the poll
chatEventPollStopped message:message = ChatEventAction;
//@description A new member joined the chat
chatEventMemberJoined = ChatEventAction;
@ -2574,60 +2637,63 @@ chatEventMemberJoinedByInviteLink invite_link:chatInviteLink = ChatEventAction;
//@description A new member was accepted to the chat by an administrator @approver_user_id User identifier of the chat administrator, approved user join request @invite_link Invite link used to join the chat; may be null
chatEventMemberJoinedByRequest approver_user_id:int53 invite_link:chatInviteLink = ChatEventAction;
//@description A member left the chat
chatEventMemberLeft = ChatEventAction;
//@description A new chat member was invited @user_id New member user identifier @status New member status
chatEventMemberInvited user_id:int53 status:ChatMemberStatus = ChatEventAction;
//@description A member left the chat
chatEventMemberLeft = ChatEventAction;
//@description A chat member has gained/lost administrator status, or the list of their administrator privileges has changed @user_id Affected chat member user identifier @old_status Previous status of the chat member @new_status New status of the chat member
chatEventMemberPromoted user_id:int53 old_status:ChatMemberStatus new_status:ChatMemberStatus = ChatEventAction;
//@description A chat member was restricted/unrestricted or banned/unbanned, or the list of their restrictions has changed @member_id Affected chat member identifier @old_status Previous status of the chat member @new_status New status of the chat member
chatEventMemberRestricted member_id:MessageSender old_status:ChatMemberStatus new_status:ChatMemberStatus = ChatEventAction;
//@description The chat title was changed @old_title Previous chat title @new_title New chat title
chatEventTitleChanged old_title:string new_title:string = ChatEventAction;
//@description The chat permissions was changed @old_permissions Previous chat permissions @new_permissions New chat permissions
chatEventPermissionsChanged old_permissions:chatPermissions new_permissions:chatPermissions = ChatEventAction;
//@description The chat available reactions were changed @old_available_reactions Previous chat available reactions @new_available_reactions New chat available reactions
chatEventAvailableReactionsChanged old_available_reactions:vector<string> new_available_reactions:vector<string> = ChatEventAction;
//@description The chat description was changed @old_description Previous chat description @new_description New chat description
chatEventDescriptionChanged old_description:string new_description:string = ChatEventAction;
//@description The chat username was changed @old_username Previous chat username @new_username New chat username
chatEventUsernameChanged old_username:string new_username:string = ChatEventAction;
//@description The chat photo was changed @old_photo Previous chat photo value; may be null @new_photo New chat photo value; may be null
chatEventPhotoChanged old_photo:chatPhoto new_photo:chatPhoto = ChatEventAction;
//@description The can_invite_users permission of a supergroup chat was toggled @can_invite_users New value of can_invite_users permission
chatEventInvitesToggled can_invite_users:Bool = ChatEventAction;
//@description The linked chat of a supergroup was changed @old_linked_chat_id Previous supergroup linked chat identifier @new_linked_chat_id New supergroup linked chat identifier
chatEventLinkedChatChanged old_linked_chat_id:int53 new_linked_chat_id:int53 = ChatEventAction;
//@description The slow_mode_delay setting of a supergroup was changed @old_slow_mode_delay Previous value of slow_mode_delay, in seconds @new_slow_mode_delay New value of slow_mode_delay, in seconds
chatEventSlowModeDelayChanged old_slow_mode_delay:int32 new_slow_mode_delay:int32 = ChatEventAction;
//@description The message TTL was changed @old_message_ttl Previous value of message_ttl @new_message_ttl New value of message_ttl
chatEventMessageTtlChanged old_message_ttl:int32 new_message_ttl:int32 = ChatEventAction;
//@description The sign_messages setting of a channel was toggled @sign_messages New value of sign_messages
chatEventSignMessagesToggled sign_messages:Bool = ChatEventAction;
//@description The has_protected_content setting of a channel was toggled @has_protected_content New value of has_protected_content
chatEventHasProtectedContentToggled has_protected_content:Bool = ChatEventAction;
//@description The supergroup sticker set was changed @old_sticker_set_id Previous identifier of the chat sticker set; 0 if none @new_sticker_set_id New identifier of the chat sticker set; 0 if none
chatEventStickerSetChanged old_sticker_set_id:int64 new_sticker_set_id:int64 = ChatEventAction;
//@description The supergroup location was changed @old_location Previous location; may be null @new_location New location; may be null
chatEventLocationChanged old_location:chatLocation new_location:chatLocation = ChatEventAction;
//@description The message TTL was changed @old_message_ttl Previous value of message_ttl @new_message_ttl New value of message_ttl
chatEventMessageTtlChanged old_message_ttl:int32 new_message_ttl:int32 = ChatEventAction;
//@description The chat permissions was changed @old_permissions Previous chat permissions @new_permissions New chat permissions
chatEventPermissionsChanged old_permissions:chatPermissions new_permissions:chatPermissions = ChatEventAction;
//@description The chat photo was changed @old_photo Previous chat photo value; may be null @new_photo New chat photo value; may be null
chatEventPhotoChanged old_photo:chatPhoto new_photo:chatPhoto = ChatEventAction;
//@description The slow_mode_delay setting of a supergroup was changed @old_slow_mode_delay Previous value of slow_mode_delay, in seconds @new_slow_mode_delay New value of slow_mode_delay, in seconds
chatEventSlowModeDelayChanged old_slow_mode_delay:int32 new_slow_mode_delay:int32 = ChatEventAction;
//@description The supergroup sticker set was changed @old_sticker_set_id Previous identifier of the chat sticker set; 0 if none @new_sticker_set_id New identifier of the chat sticker set; 0 if none
chatEventStickerSetChanged old_sticker_set_id:int64 new_sticker_set_id:int64 = ChatEventAction;
//@description The chat title was changed @old_title Previous chat title @new_title New chat title
chatEventTitleChanged old_title:string new_title:string = ChatEventAction;
//@description The chat username was changed @old_username Previous chat username @new_username New chat username
chatEventUsernameChanged old_username:string new_username:string = ChatEventAction;
//@description The has_protected_content setting of a channel was toggled @has_protected_content New value of has_protected_content
chatEventHasProtectedContentToggled has_protected_content:Bool = ChatEventAction;
//@description The can_invite_users permission of a supergroup chat was toggled @can_invite_users New value of can_invite_users permission
chatEventInvitesToggled can_invite_users:Bool = ChatEventAction;
//@description The is_all_history_available setting of a supergroup was toggled @is_all_history_available New value of is_all_history_available
chatEventIsAllHistoryAvailableToggled is_all_history_available:Bool = ChatEventAction;
//@description The sign_messages setting of a channel was toggled @sign_messages New value of sign_messages
chatEventSignMessagesToggled sign_messages:Bool = ChatEventAction;
//@description A chat invite link was edited @old_invite_link Previous information about the invite link @new_invite_link New information about the invite link
chatEventInviteLinkEdited old_invite_link:chatInviteLink new_invite_link:chatInviteLink = ChatEventAction;
@ -2643,15 +2709,15 @@ chatEventVideoChatCreated group_call_id:int32 = ChatEventAction;
//@description A video chat was ended @group_call_id Identifier of the video chat. The video chat can be received through the method getGroupCall
chatEventVideoChatEnded group_call_id:int32 = ChatEventAction;
//@description The mute_new_participants setting of a video chat was toggled @mute_new_participants New value of the mute_new_participants setting
chatEventVideoChatMuteNewParticipantsToggled mute_new_participants:Bool = ChatEventAction;
//@description A video chat participant was muted or unmuted @participant_id Identifier of the affected group call participant @is_muted New value of is_muted
chatEventVideoChatParticipantIsMutedToggled participant_id:MessageSender is_muted:Bool = ChatEventAction;
//@description A video chat participant volume level was changed @participant_id Identifier of the affected group call participant @volume_level New value of volume_level; 1-20000 in hundreds of percents
chatEventVideoChatParticipantVolumeLevelChanged participant_id:MessageSender volume_level:int32 = ChatEventAction;
//@description The mute_new_participants setting of a video chat was toggled @mute_new_participants New value of the mute_new_participants setting
chatEventVideoChatMuteNewParticipantsToggled mute_new_participants:Bool = ChatEventAction;
//@description Represents a chat event @id Chat event identifier @date Point in time (Unix timestamp) when the event happened @member_id Identifier of the user or chat who performed the action @action The action
chatEvent id:int64 date:int32 member_id:MessageSender action:ChatEventAction = ChatEvent;
@ -2849,7 +2915,7 @@ checkChatUsernameResultUsernameInvalid = CheckChatUsernameResult;
//@description The username is occupied
checkChatUsernameResultUsernameOccupied = CheckChatUsernameResult;
//@description The user has too much chats with username, one of them must be made private first
//@description The user has too many chats with username, one of them must be made private first
checkChatUsernameResultPublicChatsTooMuch = CheckChatUsernameResult;
//@description The user can't be a member of a public supergroup
@ -3268,7 +3334,7 @@ internalLinkTypeUnknownDeepLink link:string = InternalLinkType;
//@description The link is a link to an unsupported proxy. An alert can be shown to the user
internalLinkTypeUnsupportedProxy = InternalLinkType;
//@description The link is a link to a video chat. Call searchPublicChat with the given chat username, and then joinGoupCall with the given invite hash to process the link
//@description The link is a link to a video chat. Call searchPublicChat with the given chat username, and then joinGroupCall with the given invite hash to process the link
//@chat_username Username of the chat with the video chat @invite_hash If non-empty, invite hash to be used to join the video chat without being muted by administrators
//@is_live_stream True, if the video chat is expected to be a live stream in a channel or a broadcast group
internalLinkTypeVideoChat chat_username:string invite_hash:string is_live_stream:Bool = InternalLinkType;
@ -3546,18 +3612,11 @@ proxy id:int32 server:string port:int32 last_used_date:int32 is_enabled:Bool typ
proxies proxies:vector<proxy> = Proxies;
//@class InputSticker @description Describes a sticker that needs to be added to a sticker set
//@description A static sticker in PNG format, which will be converted to WEBP server-side
//@sticker PNG image with the sticker; must be up to 512 KB in size and fit in a 512x512 square
//@description A sticker to be added to a sticker set
//@sticker File with the sticker; must fit in a 512x512 square. For WEBP stickers and masks the file must be in PNG format, which will be converted to WEBP server-side. Otherwise, the file must be local or uploaded within a week. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements
//@emojis Emojis corresponding to the sticker
//@mask_position For masks, position where the mask is placed; pass null if unspecified
inputStickerStatic sticker:InputFile emojis:string mask_position:maskPosition = InputSticker;
//@description An animated sticker in TGS format
//@sticker File with the animated sticker. Only local or uploaded within a week files are supported. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements
//@emojis Emojis corresponding to the sticker
inputStickerAnimated sticker:InputFile emojis:string = InputSticker;
//@type Sticker type
inputSticker sticker:InputFile emojis:string type:StickerType = InputSticker;
//@description Represents a date range @start_date Point in time (Unix timestamp) at which the date range begins @end_date Point in time (Unix timestamp) at which the date range ends
@ -3723,6 +3782,9 @@ updateMessageContentOpened chat_id:int53 message_id:int53 = Update;
//@description A message with an unread mention was read @chat_id Chat identifier @message_id Message identifier @unread_mention_count The new number of unread mention messages left in the chat
updateMessageMentionRead chat_id:int53 message_id:int53 unread_mention_count:int32 = Update;
//@description The list of unread reactions added to a message was changed @chat_id Chat identifier @message_id Message identifier @unread_reactions The new list of unread reactions @unread_reaction_count The new number of messages with unread reactions left in the chat
updateMessageUnreadReactions chat_id:int53 message_id:int53 unread_reactions:vector<unreadReaction> unread_reaction_count:int32 = Update;
//@description A message with a live location was viewed. When the update is received, the application is supposed to update the live location
//@chat_id Identifier of the chat with the live location message @message_id Identifier of the message with live location
updateMessageLiveLocationViewed chat_id:int53 message_id:int53 = Update;
@ -3754,6 +3816,9 @@ updateChatReadOutbox chat_id:int53 last_read_outbox_message_id:int53 = Update;
//@description The chat action bar was changed @chat_id Chat identifier @action_bar The new value of the action bar; may be null
updateChatActionBar chat_id:int53 action_bar:ChatActionBar = Update;
//@description The chat available reactions were changed @chat_id Chat identifier @available_reactions The new list of reactions, available in the chat
updateChatAvailableReactions chat_id:int53 available_reactions:vector<string> = Update;
//@description A chat draft has changed. Be aware that the update may come in the currently opened chat but with old content of the draft. If the user has changed the content of the draft, this update mustn't be applied @chat_id Chat identifier @draft_message The new draft message; may be null @positions The new chat positions in the chat lists
updateChatDraftMessage chat_id:int53 draft_message:draftMessage positions:vector<chatPosition> = Update;
@ -3779,6 +3844,9 @@ updateChatTheme chat_id:int53 theme_name:string = Update;
//@description The chat unread_mention_count has changed @chat_id Chat identifier @unread_mention_count The number of unread mention messages left in the chat
updateChatUnreadMentionCount chat_id:int53 unread_mention_count:int32 = Update;
//@description The chat unread_reaction_count has changed @chat_id Chat identifier @unread_reaction_count The number of messages with unread reactions left in the chat
updateChatUnreadReactionCount chat_id:int53 unread_reaction_count:int32 = Update;
//@description A chat video chat state has changed @chat_id Chat identifier @video_chat New value of video_chat
updateChatVideoChat chat_id:int53 video_chat:videoChat = Update;
@ -3946,6 +4014,9 @@ updateTermsOfService terms_of_service_id:string terms_of_service:termsOfService
//@description The list of users nearby has changed. The update is guaranteed to be sent only 60 seconds after a successful searchChatsNearby request @users_nearby The new list of users nearby
updateUsersNearby users_nearby:vector<chatNearby> = Update;
//@description The list of supported reactions has changed @reactions The new list of supported reactions
updateReactions reactions:vector<reaction> = Update;
//@description The list of supported dice emojis has changed @emojis The new list of supported dice emojis
updateDiceEmojis emojis:vector<string> = Update;
@ -4326,7 +4397,7 @@ searchChatMessages chat_id:int53 query:string sender_id:MessageSender from_messa
//@offset_chat_id The chat identifier of the last found message, or 0 for the first request
//@offset_message_id The message identifier of the last found message, or 0 for the first request
//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter Additional filter for messages to search; pass null to search for all messages. Filters searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterFailedToSend and searchMessagesFilterPinned are unsupported in this function
//@filter Additional filter for messages to search; pass null to search for all messages. Filters searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterUnreadReaction, searchMessagesFilterFailedToSend, and searchMessagesFilterPinned are unsupported in this function
//@min_date If not 0, the minimum date of the messages to return
//@max_date If not 0, the maximum date of the messages to return
searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = Messages;
@ -4334,7 +4405,7 @@ searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:
//@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats
//@query Query to search for. If empty, searchChatMessages must be used instead
//@offset Offset of the first entry to return as received from the previous request; use empty string to get first chunk of results
//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
//@filter Additional filter for messages to search; pass null to search for all messages
searchSecretMessages chat_id:int53 query:string offset:string limit:int32 filter:SearchMessagesFilter = FoundMessages;
@ -4359,14 +4430,14 @@ getChatMessageByDate chat_id:int53 date:int32 = Message;
//@description Returns sparse positions of messages of the specified type in the chat to be used for shared media scroll implementation. Returns the results in reverse chronological order (i.e., in order of decreasing message_id).
//-Cannot be used in secret chats or with searchMessagesFilterFailedToSend filter without an enabled message database
//@chat_id Identifier of the chat in which to return information about message positions
//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterMention and searchMessagesFilterUnreadMention are unsupported in this function
//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterMention, searchMessagesFilterUnreadMention, and searchMessagesFilterUnreadReaction are unsupported in this function
//@from_message_id The message identifier from which to return information about message positions
//@limit The expected number of message positions to be returned; 50-2000. A smaller number of positions can be returned, if there are not enough appropriate messages
getChatSparseMessagePositions chat_id:int53 filter:SearchMessagesFilter from_message_id:int53 limit:int32 = MessagePositions;
//@description Returns information about the next messages of the specified type in the chat split by days. Returns the results in reverse chronological order. Can return partial result for the last returned day. Behavior of this method depends on the value of the option "utc_time_offset"
//@chat_id Identifier of the chat in which to return information about messages
//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterMention and searchMessagesFilterUnreadMention are unsupported in this function
//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterMention, searchMessagesFilterUnreadMention, and searchMessagesFilterUnreadReaction are unsupported in this function
//@from_message_id The message identifier from which to return information about messages; use 0 to get results from the last message
getChatMessageCalendar chat_id:int53 filter:SearchMessagesFilter from_message_id:int53 = MessageCalendar;
@ -4379,7 +4450,7 @@ getChatScheduledMessages chat_id:int53 = Messages;
//@description Returns forwarded copies of a channel message to different public channels. For optimal performance, the number of returned messages is chosen by TDLib
//@chat_id Chat identifier of the message
//@message_id Message identifier
//@offset Offset of the first entry to return as received from the previous request; use empty string to get first chunk of results
//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages;
@ -4412,6 +4483,13 @@ getMessageEmbeddingCode chat_id:int53 message_id:int53 for_album:Bool = Text;
getMessageLinkInfo url:string = MessageLinkInfo;
//@description Translates a text to the given language. Returns a 404 error if the translation can't be performed
//@text Text to translate
//@from_language_code A two-letter ISO 639-1 language code of the language from which the message is translated. If empty, the language will be detected automatically
//@to_language_code A two-letter ISO 639-1 language code of the language to which the message is translated
translateText text:string from_language_code:string to_language_code:string = Text;
//@description Returns list of message sender identifiers, which can be used to send messages in a chat @chat_id Chat identifier
getChatAvailableMessageSenders chat_id:int53 = MessageSenders;
@ -4561,6 +4639,27 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup =
editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok;
//@description Returns reactions, which can be added to a message. The list can change after updateReactions, updateChatAvailableReactions for the chat, or updateMessageInteractionInfo for the message
//@chat_id Identifier of the chat to which the message belongs
//@message_id Identifier of the message
getMessageAvailableReactions chat_id:int53 message_id:int53 = AvailableReactions;
//@description Changes chosen reaction for a message
//@chat_id Identifier of the chat to which the message belongs
//@message_id Identifier of the message
//@reaction Text representation of the new chosen reaction. Can be an empty string or the currently chosen reaction to remove the reaction
//@is_big True, if the reaction is added with a big animation
setMessageReaction chat_id:int53 message_id:int53 reaction:string is_big:Bool = Ok;
//@description Returns reactions added for a message, along with their sender
//@chat_id Identifier of the chat to which the message belongs
//@message_id Identifier of the message
//@reaction If non-empty, only added reactions with the specified text representation will be returned
//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
//@limit The maximum number of reactions to be returned; must be positive and can't be greater than 100
getMessageAddedReactions chat_id:int53 message_id:int53 reaction:string offset:string limit:int32 = AddedReactions;
//@description Returns all entities (mentions, hashtags, cashtags, bot commands, bank card numbers, URLs, and email addresses) contained in the text. Can be called synchronously @text The text in which to look for entites
getTextEntities text:string = TextEntities;
@ -4595,12 +4694,14 @@ getJsonString json_value:JsonValue = Text;
//@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
//@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 optimal performance, the number of returned users is chosen by TDLib
//@chat_id Identifier of the chat to which the poll belongs @message_id Identifier of the message containing the poll
//@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. For optimal performance, the number of returned users is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached
@ -4719,6 +4820,9 @@ getExternalLink link:string allow_write_access:Bool = HttpUrl;
//@description Marks all mentions in a chat as read @chat_id Chat identifier
readAllChatMentions chat_id:int53 = Ok;
//@description Marks all reactions in a chat as read @chat_id Chat identifier
readAllChatReactions chat_id:int53 = Ok;
//@description Returns an existing chat corresponding to a given user @user_id User identifier @force If true, the chat will be created without network request. In this case all information about the chat except its type, title and photo can be incorrect
createPrivateChat user_id:int53 force:Bool = Chat;
@ -4816,6 +4920,9 @@ toggleChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Ok;
//@description Changes the value of the default disable_notification parameter, used when a message is sent to a chat @chat_id Chat identifier @default_disable_notification New value of default_disable_notification
toggleChatDefaultDisableNotification chat_id:int53 default_disable_notification:Bool = Ok;
//@description Changes reactions, available in a chat. Available for basic groups, supergroups, and channels. Requires can_change_info administrator right @chat_id Identifier of the chat @available_reactions New list of reactions, available in the chat. All reactions must be active and order of the reactions must be the same as in updateReactions
setChatAvailableReactions chat_id:int53 available_reactions:vector<string> = Ok;
//@description Changes application-specific data associated with a chat @chat_id Chat identifier @client_data New value of client_data
setChatClientData chat_id:int53 client_data:string = Ok;
@ -4911,7 +5018,7 @@ setScopeNotificationSettings scope:NotificationSettingsScope notification_settin
resetAllNotificationSettings = Ok;
//@description Changes the pinned state of a chat. There can be up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") pinned non-secret chats and the same number of secret chats in the main/arhive chat list
//@description Changes the pinned state of a chat. There can be up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") pinned non-secret chats and the same number of secret chats in the main/archive chat list
//@chat_list Chat list in which to change the pinned state of the chat @chat_id Chat identifier @is_pinned True, if the chat is pinned
toggleChatIsPinned chat_list:ChatList chat_id:int53 is_pinned:Bool = Ok;
@ -5054,13 +5161,13 @@ joinChatByInviteLink invite_link:string = Chat;
//@limit The maximum number of requests to join the chat to return
getChatJoinRequests chat_id:int53 invite_link:string query:string offset_request:chatJoinRequest limit:int32 = ChatJoinRequests;
//@description Handles a pending join request in a chat @chat_id Chat identifier @user_id Identifier of the user that sent the request @approve True, if the request is approved. Otherwise the request is declived
//@description Handles a pending join request in a chat @chat_id Chat identifier @user_id Identifier of the user that sent the request @approve True, if the request is approved. Otherwise the request is declined
processChatJoinRequest chat_id:int53 user_id:int53 approve:Bool = Ok;
//@description Handles all pending join requests for a given link in a chat
//@chat_id Chat identifier
//@invite_link Invite link for which to process join requests. If empty, all join requests will be processed. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links
//@approve True, if the requests are approved. Otherwise the requests are declived
//@approve True, if the requests are approved. Otherwise the requests are declined
processChatJoinRequests chat_id:int53 invite_link:string approve:Bool = Ok;
@ -5382,18 +5489,18 @@ checkChangePhoneNumberCode code:string = Ok;
//@description Sets the list of commands supported by the bot for the given user scope and language; for bots only
//@scope The scope to which the commands are relevant; pass null to change commands in the default bot command scope
//@language_code A two-letter ISO 639-1 country code. If empty, the commands will be applied to all users from the given scope, for which language there are no dedicated commands
//@language_code A two-letter ISO 639-1 language code. If empty, the commands will be applied to all users from the given scope, for which language there are no dedicated commands
//@commands List of the bot's commands
setCommands scope:BotCommandScope language_code:string commands:vector<botCommand> = Ok;
//@description Deletes commands supported by the bot for the given user scope and language; for bots only
//@scope The scope to which the commands are relevant; pass null to delete commands in the default bot command scope
//@language_code A two-letter ISO 639-1 country code or an empty string
//@language_code A two-letter ISO 639-1 language code or an empty string
deleteCommands scope:BotCommandScope language_code:string = Ok;
//@description Returns the list of commands supported by the bot for the given user scope and language; for bots only
//@scope The scope to which the commands are relevant; pass null to get commands in the default bot command scope
//@language_code A two-letter ISO 639-1 country code or an empty string
//@language_code A two-letter ISO 639-1 language code or an empty string
getCommands scope:BotCommandScope language_code:string = BotCommands;
@ -5723,7 +5830,7 @@ setBotUpdatesStatus pending_update_count:int32 error_message:string = Ok;
//@description Uploads a file with a sticker; returns the uploaded file @user_id Sticker file owner; ignored for regular users @sticker Sticker file to upload
uploadStickerFile user_id:int53 sticker:InputSticker = File;
uploadStickerFile user_id:int53 sticker:inputSticker = File;
//@description Returns a suggested name for a new sticker set with a given title @title Sticker set title; 1-64 characters
getSuggestedStickerSetName title:string = Text;
@ -5735,18 +5842,17 @@ checkStickerSetName name:string = CheckStickerSetNameResult;
//@user_id Sticker set owner; ignored for regular users
//@title Sticker set title; 1-64 characters
//@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_<bot username>"* (*<bot_username>* is case insensitive) for bots; 1-64 characters
//@is_masks True, if stickers are masks. Animated stickers can't be masks
//@stickers List of stickers to be added to the set; must be non-empty. All stickers must be of the same type. For animated stickers, uploadStickerFile must be used before the sticker is shown
//@stickers List of stickers to be added to the set; must be non-empty. All stickers must have the same format. For TGS stickers, uploadStickerFile must be used before the sticker is shown
//@source Source of the sticker set; may be empty if unknown
createNewStickerSet user_id:int53 title:string name:string is_masks:Bool stickers:vector<InputSticker> source:string = StickerSet;
createNewStickerSet user_id:int53 title:string name:string stickers:vector<inputSticker> source:string = StickerSet;
//@description Adds a new sticker to a set; for bots only. Returns the sticker set
//@user_id Sticker set owner @name Sticker set name @sticker Sticker to add to the set
addStickerToSet user_id:int53 name:string sticker:InputSticker = StickerSet;
addStickerToSet user_id:int53 name:string sticker:inputSticker = StickerSet;
//@description Sets a sticker set thumbnail; for bots only. Returns the sticker set
//@user_id Sticker set owner @name Sticker set name
//@thumbnail Thumbnail to set in PNG or TGS format; pass null to remove the sticker set thumbnail. Animated thumbnail must be set for animated sticker sets and only for them
//@thumbnail Thumbnail to set in PNG, TGS, or WEBM format; pass null to remove the sticker set thumbnail. Thumbnail format must match the format of stickers in the set
setStickerSetThumbnail user_id:int53 name:string thumbnail:InputFile = StickerSet;
//@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot
@ -5786,7 +5892,7 @@ getCountryCode = Text;
getPhoneNumberInfo phone_number_prefix:string = PhoneNumberInfo;
//@description Returns information about a phone number by its prefix synchronously. getCountries must be called at least once after changing localization to the specified language if properly localized country information is expected. Can be called synchronously
//@language_code A two-letter ISO 639-1 country code for country information localization @phone_number_prefix The phone number prefix
//@language_code A two-letter ISO 639-1 language code for country information localization @phone_number_prefix The phone number prefix
getPhoneNumberInfoSync language_code:string phone_number_prefix:string = PhoneNumberInfo;
//@description Returns the link for downloading official Telegram application to be used when the current user invites friends to Telegram

View File

@ -180,7 +180,7 @@ messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int =
messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
photoEmpty#2331b22d id:long = Photo;
@ -556,7 +556,7 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
@ -1297,17 +1297,20 @@ auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut
reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;
messageReactions#87b6e36 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactons:flags.1?Vector<MessageUserReaction> = MessageReactions;
messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
messageUserReaction#932844fa user_id:long reaction:string = MessageUserReaction;
messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector<MessagePeerReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
messages.messageReactionsList#a366923c flags:# count:int reactions:Vector<MessageUserReaction> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
availableReaction#21d7c4b flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document = AvailableReaction;
availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction;
messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions;
messages.availableReactions#768e3aad hash:int reactions:Vector<AvailableReaction> = messages.AvailableReactions;
messages.translateNoResult#67ca4737 = messages.TranslatedText;
messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:string = MessagePeerReaction;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1586,12 +1589,15 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe
messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
messages.sendReaction#25690ce4 flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector<int> = Updates;
messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;
messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector<string> = Updates;
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@ -1688,7 +1694,7 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;

View File

@ -162,17 +162,19 @@ class AuthData {
return main_auth_key_.need_header() ? Slice(header_) : Slice();
}
}
void set_header(std::string header) {
header_ = std::move(header);
}
void on_api_response() {
if (use_pfs()) {
if (tmp_auth_key_.auth_flag()) {
tmp_auth_key_.set_need_header(false);
tmp_auth_key_.remove_header();
}
} else {
if (main_auth_key_.auth_flag()) {
main_auth_key_.set_need_header(false);
main_auth_key_.remove_header();
}
}
}

View File

@ -39,11 +39,15 @@ class AuthKey {
}
bool need_header() const {
return need_header_;
return have_header_ || Time::now() < header_expires_at_;
}
void set_need_header(bool need_header) {
need_header_ = need_header;
void remove_header() {
if (have_header_) {
have_header_ = false;
header_expires_at_ = Time::now() + 3;
}
}
double expires_at() const {
return expires_at_;
}
@ -86,14 +90,15 @@ class AuthKey {
created_at_ = parser.fetch_double();
}
// just in case
need_header_ = true;
have_header_ = true;
}
private:
uint64 auth_key_id_{0};
string auth_key_;
bool auth_flag_{false};
bool need_header_{true};
bool have_header_{true};
double header_expires_at_{0};
double expires_at_{0};
double created_at_{0};
};

View File

@ -14,7 +14,9 @@
#include "td/mtproto/mtproto_api.h"
#include "td/utils/common.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/Span.h"
#include "td/utils/StorerBase.h"
#include "td/utils/Time.h"
@ -103,6 +105,33 @@ class CancelVectorImpl {
vector<PacketStorer<CancelImpl>> storers_;
};
class InvokeAfter {
public:
explicit InvokeAfter(Span<uint64> ids) : ids_(ids) {
}
template <class StorerT>
void store(StorerT &storer) const {
if (ids_.empty()) {
return;
}
if (ids_.size() == 1) {
storer.store_int(static_cast<int32>(0xcb9f372d));
storer.store_long(static_cast<int64>(ids_[0]));
return;
}
// invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
storer.store_int(static_cast<int32>(0x3dc4b4f0));
storer.store_int(static_cast<int32>(0x1cb5c415));
storer.store_int(narrow_cast<int32>(ids_.size()));
for (auto id : ids_) {
storer.store_long(static_cast<int64>(id));
}
}
private:
Span<uint64> ids_;
};
class QueryImpl {
public:
QueryImpl(const MtprotoQuery &query, Slice header) : query_(query), header_(header) {
@ -112,23 +141,9 @@ class QueryImpl {
void do_store(StorerT &storer) const {
storer.store_binary(query_.message_id);
storer.store_binary(query_.seq_no);
Slice invoke_header;
// TODO(refactor):
// invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
// This code makes me very sad.
// InvokeAfterMsg is not even in mtproto_api. It is in telegram_api.
#pragma pack(push, 4)
struct {
uint32 constructor_id;
uint64 invoke_after_id;
} invoke_data;
#pragma pack(pop)
if (query_.invoke_after_id != 0) {
invoke_data.constructor_id = 0xcb9f372d;
invoke_data.invoke_after_id = query_.invoke_after_id;
invoke_header = Slice(reinterpret_cast<const uint8 *>(&invoke_data), sizeof(invoke_data));
}
InvokeAfter invoke_after(query_.invoke_after_ids);
auto invoke_after_storer = create_default_storer(invoke_after);
Slice data = query_.packet.as_slice();
mtproto_api::gzip_packed packed(data);
@ -136,9 +151,8 @@ class QueryImpl {
auto gzip_storer = create_storer(packed);
const Storer &data_storer =
query_.gzip_flag ? static_cast<const Storer &>(gzip_storer) : static_cast<const Storer &>(plain_storer);
auto invoke_header_storer = create_storer(invoke_header);
auto header_storer = create_storer(header_);
auto suff_storer = create_storer(invoke_header_storer, data_storer);
auto suff_storer = create_storer(invoke_after_storer, data_storer);
auto all_storer = create_storer(header_storer, suff_storer);
storer.store_binary(static_cast<uint32>(all_storer.size()));

View File

@ -17,7 +17,7 @@ struct MtprotoQuery {
int32 seq_no;
BufferSlice packet;
bool gzip_flag;
uint64 invoke_after_id;
std::vector<uint64> invoke_after_ids;
bool use_quick_ack;
};

View File

@ -765,7 +765,7 @@ void SessionConnection::send_crypto(const Storer &storer, uint64 quick_ack_token
}
Result<uint64> SessionConnection::send_query(BufferSlice buffer, bool gzip_flag, int64 message_id,
uint64 invoke_after_id, bool use_quick_ack) {
vector<uint64> invoke_after_ids, bool use_quick_ack) {
CHECK(mode_ != Mode::HttpLongPoll); // "LongPoll connection is only for http_wait"
if (message_id == 0) {
message_id = auth_data_->next_message_id(Time::now_cached());
@ -774,9 +774,10 @@ Result<uint64> SessionConnection::send_query(BufferSlice buffer, bool gzip_flag,
if (to_send_.empty()) {
send_before(Time::now_cached() + QUERY_DELAY);
}
to_send_.push_back(MtprotoQuery{message_id, seq_no, std::move(buffer), gzip_flag, invoke_after_id, use_quick_ack});
to_send_.push_back(
MtprotoQuery{message_id, seq_no, std::move(buffer), gzip_flag, std::move(invoke_after_ids), use_quick_ack});
VLOG(mtproto) << "Invoke query " << message_id << " of size " << to_send_.back().packet.size() << " with seq_no "
<< seq_no << " after " << invoke_after_id << (use_quick_ack ? " with quick ack" : "");
<< seq_no << " after " << invoke_after_ids << (use_quick_ack ? " with quick ack" : "");
return message_id;
}
@ -817,7 +818,7 @@ std::pair<uint64, BufferSlice> SessionConnection::encrypted_bind(int64 perm_key,
CHECK(size == real_size);
MtprotoQuery query{
auth_data_->next_message_id(Time::now_cached()), 0, object_packet.as_buffer_slice(), false, 0, false};
auth_data_->next_message_id(Time::now_cached()), 0, object_packet.as_buffer_slice(), false, {}, false};
PacketStorer<QueryImpl> query_storer(query, Slice());
PacketInfo info;
@ -935,7 +936,7 @@ void SessionConnection::flush_packet() {
v.clear();
return result;
}
LOG(WARNING) << "Too much message identifiers in container " << name << ": " << v.size() << " instead of " << size;
LOG(WARNING) << "Too many message identifiers in container " << name << ": " << v.size() << " instead of " << size;
vector<int64> result(v.end() - size, v.end());
v.resize(v.size() - size);
return result;

View File

@ -82,7 +82,7 @@ class SessionConnection final
// Interface
Result<uint64> TD_WARN_UNUSED_RESULT send_query(BufferSlice buffer, bool gzip_flag, int64 message_id = 0,
uint64 invoke_after_id = 0, bool use_quick_ack = false);
std::vector<uint64> invoke_after_id = {}, bool use_quick_ack = false);
std::pair<uint64, BufferSlice> encrypted_bind(int64 perm_key, int64 nonce, int32 expires_at);
void get_state_info(int64 message_id);

View File

@ -1505,6 +1505,8 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
int64 chat_read_mark_expire_period = 0;
int64 chat_read_mark_size_threshold = 0;
double animated_emoji_zoom = 0.0;
string default_reaction;
int64 reactions_uniq_max = 0;
if (config->get_id() == telegram_api::jsonObject::ID) {
for (auto &key_value : static_cast<telegram_api::jsonObject *>(config.get())->value_) {
Slice key = key_value->key_;
@ -1745,6 +1747,14 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
get_json_value_int(std::move(key_value->value_), "chat_read_mark_size_threshold");
continue;
}
if (key == "reactions_default") {
default_reaction = get_json_value_string(std::move(key_value->value_), "reactions_default");
continue;
}
if (key == "reactions_uniq_max") {
reactions_uniq_max = get_json_value_int(std::move(key_value->value_), "reactions_uniq_max");
continue;
}
new_values.push_back(std::move(key_value));
}
@ -1818,6 +1828,14 @@ void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &c
} else {
shared_config.set_option_integer("chat_read_mark_size_threshold", chat_read_mark_size_threshold);
}
if (!shared_config.have_option("default_reaction_need_sync")) {
shared_config.set_option_string("default_reaction", default_reaction);
}
if (reactions_uniq_max <= 0 || reactions_uniq_max == 11) {
shared_config.set_option_empty("reactions_uniq_max");
} else {
shared_config.set_option_integer("reactions_uniq_max", reactions_uniq_max);
}
shared_config.set_option_empty("default_ton_blockchain_config");
shared_config.set_option_empty("default_ton_blockchain_name");

View File

@ -3086,7 +3086,7 @@ class GetBroadcastStatsQuery final : public Td::ResultHandler {
for (auto &info : result->recent_message_interactions_) {
td_->messages_manager_->on_update_message_interaction_info({DialogId(channel_id_), MessageId(info->message_id_)},
info->view_count_, info->forward_count_, false,
nullptr);
nullptr, false, nullptr);
}
promise_.set_value(std::move(result));
}
@ -4512,7 +4512,7 @@ tl_object_ptr<telegram_api::InputPeer> ContactsManager::get_input_peer_channel(C
bool ContactsManager::have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights,
bool from_linked) const {
if (c == nullptr) {
LOG(DEBUG) << "Have no supergroup";
LOG(DEBUG) << "Have no " << channel_id;
return false;
}
if (access_rights == AccessRights::Know) {
@ -4522,7 +4522,7 @@ bool ContactsManager::have_input_peer_channel(const Channel *c, ChannelId channe
return true;
}
if (c->status.is_banned()) {
LOG(DEBUG) << "Was banned in a supergroup";
LOG(DEBUG) << "Was banned in " << channel_id;
return false;
}
if (c->status.is_member()) {
@ -4558,7 +4558,7 @@ bool ContactsManager::have_input_peer_channel(const Channel *c, ChannelId channe
}
}
}
LOG(DEBUG) << "Have no access to a private supergroup";
LOG(DEBUG) << "Have no access to " << channel_id;
return false;
}
@ -10681,6 +10681,9 @@ void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&c
td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat->notify_settings_),
"on_get_chat_full");
td_->messages_manager_->on_update_dialog_available_reactions(DialogId(chat_id),
std::move(chat->available_reactions_));
td_->messages_manager_->on_update_dialog_theme_name(DialogId(chat_id), std::move(chat->theme_emoticon_));
td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(chat_id), chat->requests_pending_,
@ -10727,6 +10730,9 @@ void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&c
td_->messages_manager_->on_update_dialog_notify_settings(DialogId(channel_id), std::move(channel->notify_settings_),
"on_get_channel_full");
td_->messages_manager_->on_update_dialog_available_reactions(DialogId(channel_id),
std::move(channel->available_reactions_));
td_->messages_manager_->on_update_dialog_theme_name(DialogId(channel_id), std::move(channel->theme_emoticon_));
td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(channel_id), channel->requests_pending_,

View File

@ -1052,6 +1052,7 @@ class ContactsManager final : public Actor {
static constexpr int32 CHAT_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 12;
static constexpr int32 CHAT_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14;
static constexpr int32 CHAT_FULL_FLAG_HAS_PENDING_REQUEST_COUNT = 1 << 17;
static constexpr int32 CHAT_FULL_FLAG_HAS_AVAILABLE_REACTIONS = 1 << 18;
static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0;
static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2;
@ -1105,6 +1106,7 @@ class ContactsManager final : public Actor {
static constexpr int32 CHANNEL_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 24;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_PENDING_REQUEST_COUNT = 1 << 28;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_DEFAULT_SEND_AS = 1 << 29;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_AVAILABLE_REACTIONS = 1 << 30;
static constexpr int32 CHAT_INVITE_FLAG_IS_CHANNEL = 1 << 0;
static constexpr int32 CHAT_INVITE_FLAG_IS_BROADCAST = 1 << 1;

View File

@ -340,8 +340,11 @@ static td_api::object_ptr<td_api::ChatEventAction> get_chat_event_action_object(
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleNoForwards>(action_ptr);
return td_api::make_object<td_api::chatEventHasProtectedContentToggled>(action->new_value_);
}
case telegram_api::channelAdminLogEventActionChangeAvailableReactions::ID:
return nullptr;
case telegram_api::channelAdminLogEventActionChangeAvailableReactions::ID: {
auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeAvailableReactions>(action_ptr);
return td_api::make_object<td_api::chatEventAvailableReactionsChanged>(std::move(action->prev_value_),
std::move(action->new_value_));
}
default:
UNREACHABLE();
return nullptr;

View File

@ -19,6 +19,7 @@
#include "td/telegram/Photo.h"
#include "td/telegram/PhotoSizeSource.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/StickerFormat.h"
#include "td/telegram/StickersManager.h"
#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
@ -124,10 +125,19 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
if ((video->flags_ & telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK) != 0) {
// video note without sound
animated = nullptr;
} else if (sticker != nullptr) {
// sticker
type_attributes--;
animated = nullptr;
video = nullptr;
} else {
// video animation
video = nullptr;
}
} else if (sticker != nullptr) {
// some stickers uploaded before release
type_attributes--;
video = nullptr;
}
}
if (animated != nullptr && audio != nullptr) {
@ -145,6 +155,7 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
FileType file_type = FileType::Document;
Slice default_extension;
bool supports_streaming = false;
StickerFormat sticker_format = StickerFormat::Unknown;
PhotoFormat thumbnail_format = PhotoFormat::Jpeg;
if (type_attributes == 1 || default_document_type != Document::Type::General) { // not a general document
if (animated != nullptr || default_document_type == Document::Type::Animation) {
@ -170,6 +181,7 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
} else if (sticker != nullptr || default_document_type == Document::Type::Sticker) {
document_type = Document::Type::Sticker;
file_type = FileType::Sticker;
sticker_format = StickerFormat::Webp;
default_extension = Slice("webp");
owner_dialog_id = DialogId();
file_name.clear();
@ -223,22 +235,21 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
PhotoSize thumbnail;
AnimationSize animated_thumbnail;
FileEncryptionKey encryption_key;
bool is_animated_sticker = false;
bool is_web = false;
bool is_web_no_proxy = false;
string url;
FileLocationSource source = FileLocationSource::FromServer;
auto fix_animated_sticker_type = [&] {
auto fix_tgs_sticker_type = [&] {
if (mime_type != "application/x-tgsticker") {
return;
}
is_animated_sticker = true;
sticker_format = StickerFormat::Tgs;
default_extension = Slice("tgs");
if (document_type == Document::Type::General) {
document_type = Document::Type::Sticker;
file_type = FileType::Sticker;
default_extension = Slice("tgs");
owner_dialog_id = DialogId();
file_name.clear();
thumbnail_format = PhotoFormat::Webp;
@ -258,7 +269,7 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
if (document_type == Document::Type::Sticker && StickersManager::has_webp_thumbnail(document->thumbs_)) {
thumbnail_format = PhotoFormat::Webp;
}
fix_animated_sticker_type();
fix_tgs_sticker_type();
if (owner_dialog_id.get_type() == DialogType::SecretChat) {
// secret_api::decryptedMessageMediaExternalDocument
@ -309,8 +320,8 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
return {};
}
// do not allow encrypted animated stickers
// fix_animated_sticker_type();
// do not allow encrypted TGS stickers
// fix_tgs_sticker_type();
if (document_type != Document::Type::VoiceNote) {
thumbnail = get_secret_thumbnail_photo_size(td_->file_manager_.get(), std::move(document->thumb_),
@ -367,8 +378,16 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
UNREACHABLE();
}
// do not allow web animated stickers
// fix_animated_sticker_type();
// do not allow web TGS stickers
// fix_tgs_sticker_type();
}
if (document_type == Document::Type::Sticker && mime_type == "video/webm") {
sticker_format = StickerFormat::Webm;
default_extension = Slice("webm");
}
if (file_type == FileType::Encrypted && document_type == Document::Type::Sticker &&
size > get_max_sticker_file_size(sticker_format, false)) {
document_type = Document::Type::General;
}
LOG(DEBUG) << "Receive document with ID = " << id << " of type " << document_type;
@ -446,7 +465,7 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo
minithumbnail = string();
}
td_->stickers_manager_->create_sticker(file_id, std::move(minithumbnail), std::move(thumbnail), dimensions,
std::move(sticker), is_animated_sticker, load_data_multipromise_ptr);
std::move(sticker), sticker_format, load_data_multipromise_ptr);
break;
case Document::Type::Video:
td_->videos_manager_->create_video(file_id, std::move(minithumbnail), std::move(thumbnail),

View File

@ -46,6 +46,9 @@ class GetGroupCallStreamQuery final : public Td::ResultHandler {
void send(InputGroupCallId input_group_call_id, DcId stream_dc_id, int64 time_offset, int32 scale, int32 channel_id,
int32 video_quality) {
int32 stream_flags = 0;
if (channel_id != 0) {
stream_flags |= telegram_api::inputGroupCallStream::VIDEO_CHANNEL_MASK;
}
auto input_stream = make_tl_object<telegram_api::inputGroupCallStream>(
stream_flags, input_group_call_id.get_input_group_call(), time_offset, scale, channel_id, video_quality);
int32 flags = 0;
@ -2773,7 +2776,7 @@ void GroupCallManager::finish_join_group_call(InputGroupCallId input_group_call_
if (group_call != nullptr && group_call->dialog_id.is_valid()) {
update_group_call_dialog(group_call, "finish_join_group_call", false);
td_->messages_manager_->reload_dialog_info_full(group_call->dialog_id);
td_->messages_manager_->reload_dialog_info_full(group_call->dialog_id, "finish_join_group_call");
}
}

View File

@ -593,7 +593,8 @@ void InlineQueriesManager::answer_inline_query(int64 inline_query_id, bool is_pe
id = std::move(sticker->id_);
thumbnail_url = std::move(sticker->thumbnail_url_);
content_url = std::move(sticker->sticker_url_);
content_type = "image/webp"; // or "application/x-tgsticker"; not used for previously uploaded files
content_type =
"image/webp"; // or "application/x-tgsticker"/"video/webm"; not used for previously uploaded files
width = sticker->sticker_width_;
height = sticker->sticker_height_;
is_gallery = true;
@ -937,13 +938,13 @@ td_api::object_ptr<td_api::file> copy(const td_api::file &obj) {
template <>
tl_object_ptr<td_api::minithumbnail> copy(const td_api::minithumbnail &obj) {
return make_tl_object<td_api::minithumbnail>(obj.width_, obj.height_, obj.data_);
return td_api::make_object<td_api::minithumbnail>(obj.width_, obj.height_, obj.data_);
}
template <>
tl_object_ptr<td_api::photoSize> copy(const td_api::photoSize &obj) {
return make_tl_object<td_api::photoSize>(obj.type_, copy(obj.photo_), obj.width_, obj.height_,
vector<int32>(obj.progressive_sizes_));
return td_api::make_object<td_api::photoSize>(obj.type_, copy(obj.photo_), obj.width_, obj.height_,
vector<int32>(obj.progressive_sizes_));
}
static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_api::photoSize> &obj) {
@ -972,20 +973,20 @@ tl_object_ptr<td_api::thumbnail> copy(const td_api::thumbnail &obj) {
}
}();
return make_tl_object<td_api::thumbnail>(std::move(format), obj.width_, obj.height_, copy(obj.file_));
return td_api::make_object<td_api::thumbnail>(std::move(format), obj.width_, obj.height_, copy(obj.file_));
}
template <>
tl_object_ptr<td_api::MaskPoint> copy(const td_api::MaskPoint &obj) {
switch (obj.get_id()) {
case td_api::maskPointForehead::ID:
return make_tl_object<td_api::maskPointForehead>();
return td_api::make_object<td_api::maskPointForehead>();
case td_api::maskPointEyes::ID:
return make_tl_object<td_api::maskPointEyes>();
return td_api::make_object<td_api::maskPointEyes>();
case td_api::maskPointMouth::ID:
return make_tl_object<td_api::maskPointMouth>();
return td_api::make_object<td_api::maskPointMouth>();
case td_api::maskPointChin::ID:
return make_tl_object<td_api::maskPointChin>();
return td_api::make_object<td_api::maskPointChin>();
default:
UNREACHABLE();
}
@ -994,12 +995,31 @@ tl_object_ptr<td_api::MaskPoint> copy(const td_api::MaskPoint &obj) {
template <>
tl_object_ptr<td_api::maskPosition> copy(const td_api::maskPosition &obj) {
return make_tl_object<td_api::maskPosition>(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_);
return td_api::make_object<td_api::maskPosition>(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_);
}
template <>
tl_object_ptr<td_api::StickerType> copy(const td_api::StickerType &obj) {
switch (obj.get_id()) {
case td_api::stickerTypeStatic::ID:
return td_api::make_object<td_api::stickerTypeStatic>();
case td_api::stickerTypeAnimated::ID:
return td_api::make_object<td_api::stickerTypeAnimated>();
case td_api::stickerTypeVideo::ID:
return td_api::make_object<td_api::stickerTypeVideo>();
case td_api::stickerTypeMask::ID: {
auto &mask_position = static_cast<const td_api::stickerTypeMask &>(obj).mask_position_;
return td_api::make_object<td_api::stickerTypeMask>(copy(mask_position));
}
default:
UNREACHABLE();
}
return nullptr;
}
template <>
tl_object_ptr<td_api::point> copy(const td_api::point &obj) {
return make_tl_object<td_api::point>(obj.x_, obj.y_);
return td_api::make_object<td_api::point>(obj.x_, obj.y_);
}
template <>
@ -1007,11 +1027,11 @@ tl_object_ptr<td_api::VectorPathCommand> copy(const td_api::VectorPathCommand &o
switch (obj.get_id()) {
case td_api::vectorPathCommandLine::ID: {
auto &command = static_cast<const td_api::vectorPathCommandLine &>(obj);
return make_tl_object<td_api::vectorPathCommandLine>(copy(command.end_point_));
return td_api::make_object<td_api::vectorPathCommandLine>(copy(command.end_point_));
}
case td_api::vectorPathCommandCubicBezierCurve::ID: {
auto &command = static_cast<const td_api::vectorPathCommandCubicBezierCurve &>(obj);
return make_tl_object<td_api::vectorPathCommandCubicBezierCurve>(
return td_api::make_object<td_api::vectorPathCommandCubicBezierCurve>(
copy(command.start_control_point_), copy(command.end_control_point_), copy(command.end_point_));
}
default:
@ -1027,7 +1047,7 @@ static tl_object_ptr<td_api::VectorPathCommand> copy_vector_path_command(
template <>
tl_object_ptr<td_api::closedVectorPath> copy(const td_api::closedVectorPath &obj) {
return make_tl_object<td_api::closedVectorPath>(transform(obj.commands_, copy_vector_path_command));
return td_api::make_object<td_api::closedVectorPath>(transform(obj.commands_, copy_vector_path_command));
}
static tl_object_ptr<td_api::closedVectorPath> copy_closed_vector_path(
@ -1037,137 +1057,139 @@ static tl_object_ptr<td_api::closedVectorPath> copy_closed_vector_path(
template <>
tl_object_ptr<td_api::animation> copy(const td_api::animation &obj) {
return make_tl_object<td_api::animation>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
obj.has_stickers_, copy(obj.minithumbnail_), copy(obj.thumbnail_),
copy(obj.animation_));
return td_api::make_object<td_api::animation>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
obj.has_stickers_, copy(obj.minithumbnail_), copy(obj.thumbnail_),
copy(obj.animation_));
}
template <>
tl_object_ptr<td_api::audio> copy(const td_api::audio &obj) {
return make_tl_object<td_api::audio>(obj.duration_, obj.title_, obj.performer_, obj.file_name_, obj.mime_type_,
copy(obj.album_cover_minithumbnail_), copy(obj.album_cover_thumbnail_),
copy(obj.audio_));
return td_api::make_object<td_api::audio>(obj.duration_, obj.title_, obj.performer_, obj.file_name_, obj.mime_type_,
copy(obj.album_cover_minithumbnail_), copy(obj.album_cover_thumbnail_),
copy(obj.audio_));
}
template <>
tl_object_ptr<td_api::document> copy(const td_api::document &obj) {
return make_tl_object<td_api::document>(obj.file_name_, obj.mime_type_, copy(obj.minithumbnail_),
copy(obj.thumbnail_), copy(obj.document_));
return td_api::make_object<td_api::document>(obj.file_name_, obj.mime_type_, copy(obj.minithumbnail_),
copy(obj.thumbnail_), copy(obj.document_));
}
template <>
tl_object_ptr<td_api::photo> copy(const td_api::photo &obj) {
return make_tl_object<td_api::photo>(obj.has_stickers_, copy(obj.minithumbnail_),
transform(obj.sizes_, copy_photo_size));
return td_api::make_object<td_api::photo>(obj.has_stickers_, copy(obj.minithumbnail_),
transform(obj.sizes_, copy_photo_size));
}
template <>
tl_object_ptr<td_api::sticker> copy(const td_api::sticker &obj) {
return make_tl_object<td_api::sticker>(
obj.set_id_, obj.width_, obj.height_, obj.emoji_, obj.is_animated_, obj.is_mask_, copy(obj.mask_position_),
transform(obj.outline_, copy_closed_vector_path), copy(obj.thumbnail_), copy(obj.sticker_));
return td_api::make_object<td_api::sticker>(obj.set_id_, obj.width_, obj.height_, obj.emoji_, copy(obj.type_),
transform(obj.outline_, copy_closed_vector_path), copy(obj.thumbnail_),
copy(obj.sticker_));
}
template <>
tl_object_ptr<td_api::video> copy(const td_api::video &obj) {
return make_tl_object<td_api::video>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
obj.has_stickers_, obj.supports_streaming_, copy(obj.minithumbnail_),
copy(obj.thumbnail_), copy(obj.video_));
return td_api::make_object<td_api::video>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
obj.has_stickers_, obj.supports_streaming_, copy(obj.minithumbnail_),
copy(obj.thumbnail_), copy(obj.video_));
}
template <>
tl_object_ptr<td_api::voiceNote> copy(const td_api::voiceNote &obj) {
return make_tl_object<td_api::voiceNote>(obj.duration_, obj.waveform_, obj.mime_type_, copy(obj.voice_));
return td_api::make_object<td_api::voiceNote>(obj.duration_, obj.waveform_, obj.mime_type_, copy(obj.voice_));
}
template <>
tl_object_ptr<td_api::contact> copy(const td_api::contact &obj) {
return make_tl_object<td_api::contact>(obj.phone_number_, obj.first_name_, obj.last_name_, obj.vcard_, obj.user_id_);
return td_api::make_object<td_api::contact>(obj.phone_number_, obj.first_name_, obj.last_name_, obj.vcard_,
obj.user_id_);
}
template <>
tl_object_ptr<td_api::location> copy(const td_api::location &obj) {
return make_tl_object<td_api::location>(obj.latitude_, obj.longitude_, obj.horizontal_accuracy_);
return td_api::make_object<td_api::location>(obj.latitude_, obj.longitude_, obj.horizontal_accuracy_);
}
template <>
tl_object_ptr<td_api::venue> copy(const td_api::venue &obj) {
return make_tl_object<td_api::venue>(copy(obj.location_), obj.title_, obj.address_, obj.provider_, obj.id_,
obj.type_);
return td_api::make_object<td_api::venue>(copy(obj.location_), obj.title_, obj.address_, obj.provider_, obj.id_,
obj.type_);
}
template <>
tl_object_ptr<td_api::formattedText> copy(const td_api::formattedText &obj) {
// there are no entities in the game text
return make_tl_object<td_api::formattedText>(obj.text_, vector<tl_object_ptr<td_api::textEntity>>());
return td_api::make_object<td_api::formattedText>(obj.text_, vector<tl_object_ptr<td_api::textEntity>>());
}
template <>
tl_object_ptr<td_api::game> copy(const td_api::game &obj) {
return make_tl_object<td_api::game>(obj.id_, obj.short_name_, obj.title_, copy(obj.text_), obj.description_,
copy(obj.photo_), copy(obj.animation_));
return td_api::make_object<td_api::game>(obj.id_, obj.short_name_, obj.title_, copy(obj.text_), obj.description_,
copy(obj.photo_), copy(obj.animation_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultArticle> copy(const td_api::inlineQueryResultArticle &obj) {
return make_tl_object<td_api::inlineQueryResultArticle>(obj.id_, obj.url_, obj.hide_url_, obj.title_,
obj.description_, copy(obj.thumbnail_));
return td_api::make_object<td_api::inlineQueryResultArticle>(obj.id_, obj.url_, obj.hide_url_, obj.title_,
obj.description_, copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultContact> copy(const td_api::inlineQueryResultContact &obj) {
return make_tl_object<td_api::inlineQueryResultContact>(obj.id_, copy(obj.contact_), copy(obj.thumbnail_));
return td_api::make_object<td_api::inlineQueryResultContact>(obj.id_, copy(obj.contact_), copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultLocation> copy(const td_api::inlineQueryResultLocation &obj) {
return make_tl_object<td_api::inlineQueryResultLocation>(obj.id_, copy(obj.location_), obj.title_,
copy(obj.thumbnail_));
return td_api::make_object<td_api::inlineQueryResultLocation>(obj.id_, copy(obj.location_), obj.title_,
copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultVenue> copy(const td_api::inlineQueryResultVenue &obj) {
return make_tl_object<td_api::inlineQueryResultVenue>(obj.id_, copy(obj.venue_), copy(obj.thumbnail_));
return td_api::make_object<td_api::inlineQueryResultVenue>(obj.id_, copy(obj.venue_), copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultGame> copy(const td_api::inlineQueryResultGame &obj) {
return make_tl_object<td_api::inlineQueryResultGame>(obj.id_, copy(obj.game_));
return td_api::make_object<td_api::inlineQueryResultGame>(obj.id_, copy(obj.game_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultAnimation> copy(const td_api::inlineQueryResultAnimation &obj) {
return make_tl_object<td_api::inlineQueryResultAnimation>(obj.id_, copy(obj.animation_), obj.title_);
return td_api::make_object<td_api::inlineQueryResultAnimation>(obj.id_, copy(obj.animation_), obj.title_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultAudio> copy(const td_api::inlineQueryResultAudio &obj) {
return make_tl_object<td_api::inlineQueryResultAudio>(obj.id_, copy(obj.audio_));
return td_api::make_object<td_api::inlineQueryResultAudio>(obj.id_, copy(obj.audio_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultDocument> copy(const td_api::inlineQueryResultDocument &obj) {
return make_tl_object<td_api::inlineQueryResultDocument>(obj.id_, copy(obj.document_), obj.title_, obj.description_);
return td_api::make_object<td_api::inlineQueryResultDocument>(obj.id_, copy(obj.document_), obj.title_,
obj.description_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultPhoto> copy(const td_api::inlineQueryResultPhoto &obj) {
return make_tl_object<td_api::inlineQueryResultPhoto>(obj.id_, copy(obj.photo_), obj.title_, obj.description_);
return td_api::make_object<td_api::inlineQueryResultPhoto>(obj.id_, copy(obj.photo_), obj.title_, obj.description_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultSticker> copy(const td_api::inlineQueryResultSticker &obj) {
return make_tl_object<td_api::inlineQueryResultSticker>(obj.id_, copy(obj.sticker_));
return td_api::make_object<td_api::inlineQueryResultSticker>(obj.id_, copy(obj.sticker_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultVideo> copy(const td_api::inlineQueryResultVideo &obj) {
return make_tl_object<td_api::inlineQueryResultVideo>(obj.id_, copy(obj.video_), obj.title_, obj.description_);
return td_api::make_object<td_api::inlineQueryResultVideo>(obj.id_, copy(obj.video_), obj.title_, obj.description_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultVoiceNote> copy(const td_api::inlineQueryResultVoiceNote &obj) {
return make_tl_object<td_api::inlineQueryResultVoiceNote>(obj.id_, copy(obj.voice_note_), obj.title_);
return td_api::make_object<td_api::inlineQueryResultVoiceNote>(obj.id_, copy(obj.voice_note_), obj.title_);
}
static tl_object_ptr<td_api::InlineQueryResult> copy_result(const tl_object_ptr<td_api::InlineQueryResult> &obj_ptr) {
@ -1178,9 +1200,9 @@ static tl_object_ptr<td_api::InlineQueryResult> copy_result(const tl_object_ptr<
template <>
tl_object_ptr<td_api::inlineQueryResults> copy(const td_api::inlineQueryResults &obj) {
return make_tl_object<td_api::inlineQueryResults>(obj.inline_query_id_, obj.next_offset_,
transform(obj.results_, copy_result), obj.switch_pm_text_,
obj.switch_pm_parameter_);
return td_api::make_object<td_api::inlineQueryResults>(obj.inline_query_id_, obj.next_offset_,
transform(obj.results_, copy_result), obj.switch_pm_text_,
obj.switch_pm_parameter_);
}
tl_object_ptr<td_api::inlineQueryResults> InlineQueriesManager::decrease_pending_request_count(uint64 query_hash) {

View File

@ -53,6 +53,7 @@
#include "td/telegram/secret_api.hpp"
#include "td/telegram/SecureValue.h"
#include "td/telegram/SecureValue.hpp"
#include "td/telegram/StickerFormat.h"
#include "td/telegram/StickersManager.h"
#include "td/telegram/StickersManager.hpp"
#include "td/telegram/Td.h"
@ -448,7 +449,7 @@ class MessageChatSetTtl final : public MessageContent {
class MessageUnsupported final : public MessageContent {
public:
static constexpr int32 CURRENT_VERSION = 9;
static constexpr int32 CURRENT_VERSION = 10;
int32 version = CURRENT_VERSION;
MessageUnsupported() = default;
@ -1762,8 +1763,8 @@ static Result<InputMessageContent> create_input_message_content(
td->stickers_manager_->create_sticker(
file_id, string(), thumbnail,
get_dimensions(input_sticker->width_, input_sticker->height_, "inputMessageSticker"), nullptr, false,
nullptr);
get_dimensions(input_sticker->width_, input_sticker->height_, "inputMessageSticker"), nullptr,
StickerFormat::Unknown, nullptr);
content = make_unique<MessageSticker>(file_id);
break;
@ -5652,6 +5653,10 @@ bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogActi
return !td->stickers_manager_->is_sent_animated_emoji_click(dialog_id, remove_emoji_modifiers(emoji));
}
bool is_active_reaction(Td *td, const string &reaction) {
return td->stickers_manager_->is_active_reaction(reaction);
}
void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date) {
send_closure(G()->top_dialog_manager(), &TopDialogManager::on_dialog_used, category, dialog_id, date);
}

View File

@ -248,6 +248,8 @@ void on_sent_message_content(Td *td, const MessageContent *content);
bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogAction &action);
bool is_active_reaction(Td *td, const string &reaction);
void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date);
void update_used_hashtags(Td *td, const MessageContent *content);

View File

@ -318,7 +318,7 @@ static vector<Slice> match_bot_commands(Slice str) {
static bool is_hashtag_letter(uint32 c, UnicodeSimpleCategory &category) {
category = get_unicode_simple_category(c);
if (c == '_' || c == 0x200c) {
if (c == '_' || c == 0x200c || c == 0xb7) {
return true;
}
switch (category) {
@ -929,7 +929,7 @@ static bool is_valid_bank_card(Slice str) {
}
bool is_email_address(Slice str) {
// /^([a-z0-9_-]{0,26}[.+]){0,10}[a-z0-9_-]{1,35}@(([a-z0-9][a-z0-9_-]{0,28})?[a-z0-9][.]){1,6}[a-z]{2,6}$/i
// /^([a-z0-9_-]{0,26}[.+]){0,10}[a-z0-9_-]{1,35}@(([a-z0-9][a-z0-9_-]{0,28})?[a-z0-9][.]){1,6}[a-z]{2,8}$/i
Slice userdata;
Slice domain;
std::tie(userdata, domain) = split(str, '@');
@ -966,7 +966,7 @@ bool is_email_address(Slice str) {
if (domain_parts.size() <= 1 || domain_parts.size() > 7) {
return false;
}
if (domain_parts.back().size() <= 1 || domain_parts.back().size() >= 7) {
if (domain_parts.back().size() <= 1 || domain_parts.back().size() >= 9) {
return false;
}
for (auto c : domain_parts.back()) {

View File

@ -0,0 +1,472 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/MessageReaction.h"
#include "td/telegram/AccessRights.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/MessageSender.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/ServerMessageId.h"
#include "td/telegram/Td.h"
#include "td/telegram/UpdatesManager.h"
#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
#include "td/utils/logging.h"
#include "td/utils/Status.h"
#include <algorithm>
#include <unordered_set>
#include <utility>
namespace td {
class GetMessagesReactionsQuery final : public Td::ResultHandler {
DialogId dialog_id_;
vector<MessageId> message_ids_;
public:
void send(DialogId dialog_id, vector<MessageId> &&message_ids) {
dialog_id_ = dialog_id;
message_ids_ = std::move(message_ids);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
CHECK(input_peer != nullptr);
send_query(G()->net_query_creator().create(telegram_api::messages_getMessagesReactions(
std::move(input_peer), MessagesManager::get_server_message_ids(message_ids_))));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getMessagesReactions>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetMessagesReactionsQuery: " << to_string(ptr);
if (ptr->get_id() == telegram_api::updates::ID) {
auto &updates = static_cast<telegram_api::updates *>(ptr.get())->updates_;
std::unordered_set<MessageId, MessageIdHash> skipped_message_ids(message_ids_.begin(), message_ids_.end());
for (const auto &update : updates) {
if (update->get_id() == telegram_api::updateMessageReactions::ID) {
auto update_message_reactions = static_cast<const telegram_api::updateMessageReactions *>(update.get());
if (DialogId(update_message_reactions->peer_) == dialog_id_) {
skipped_message_ids.erase(MessageId(ServerMessageId(update_message_reactions->msg_id_)));
}
}
}
for (auto message_id : skipped_message_ids) {
td_->messages_manager_->on_update_message_reactions({dialog_id_, message_id}, nullptr);
}
}
td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesReactionsQuery");
}
};
class SendReactionQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
MessageId message_id_;
public:
explicit SendReactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(FullMessageId full_message_id, string reaction, bool is_big) {
dialog_id_ = full_message_id.get_dialog_id();
message_id_ = full_message_id.get_message_id();
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
int32 flags = 0;
if (!reaction.empty()) {
flags |= telegram_api::messages_sendReaction::REACTION_MASK;
if (is_big) {
flags |= telegram_api::messages_sendReaction::BIG_MASK;
}
}
send_query(G()->net_query_creator().create(telegram_api::messages_sendReaction(
flags, false /*ignored*/, std::move(input_peer), message_id_.get_server_message_id().get(), reaction)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendReaction>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendReactionQuery: " << to_string(ptr);
td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendReactionQuery");
promise_.set_error(std::move(status));
}
};
class GetMessageReactionsListQuery final : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::addedReactions>> promise_;
DialogId dialog_id_;
MessageId message_id_;
string reaction_;
string offset_;
public:
explicit GetMessageReactionsListQuery(Promise<td_api::object_ptr<td_api::addedReactions>> &&promise)
: promise_(std::move(promise)) {
}
void send(FullMessageId full_message_id, string reaction, string offset, int32 limit) {
dialog_id_ = full_message_id.get_dialog_id();
message_id_ = full_message_id.get_message_id();
reaction_ = std::move(reaction);
offset_ = std::move(offset);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
if (input_peer == nullptr) {
return on_error(Status::Error(400, "Can't access the chat"));
}
int32 flags = 0;
if (!reaction_.empty()) {
flags |= telegram_api::messages_getMessageReactionsList::REACTION_MASK;
}
if (!offset_.empty()) {
flags |= telegram_api::messages_getMessageReactionsList::OFFSET_MASK;
}
send_query(G()->net_query_creator().create(telegram_api::messages_getMessageReactionsList(
flags, std::move(input_peer), message_id_.get_server_message_id().get(), reaction_, offset_, limit)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getMessageReactionsList>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetMessageReactionsListQuery: " << to_string(ptr);
td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetMessageReactionsListQuery");
td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetMessageReactionsListQuery");
int32 total_count = ptr->count_;
if (total_count < static_cast<int32>(ptr->reactions_.size())) {
LOG(ERROR) << "Receive invalid total_count in " << to_string(ptr);
total_count = static_cast<int32>(ptr->reactions_.size());
}
vector<td_api::object_ptr<td_api::addedReaction>> reactions;
for (auto &reaction : ptr->reactions_) {
DialogId dialog_id(reaction->peer_id_);
if (!dialog_id.is_valid() || (!reaction_.empty() && reaction_ != reaction->reaction_)) {
LOG(ERROR) << "Receive unexpected " << to_string(reaction);
continue;
}
auto message_sender = get_min_message_sender_object(td_, dialog_id, "GetMessageReactionsListQuery");
if (message_sender != nullptr) {
reactions.push_back(td_api::make_object<td_api::addedReaction>(reaction->reaction_, std::move(message_sender)));
}
}
promise_.set_value(
td_api::make_object<td_api::addedReactions>(total_count, std::move(reactions), ptr->next_offset_));
}
void on_error(Status status) final {
td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessageReactionsListQuery");
promise_.set_error(std::move(status));
}
};
void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool can_get_added_reactions) {
if (is_chosen_ == is_chosen) {
return;
}
is_chosen_ = is_chosen;
if (chooser_dialog_id.is_valid()) {
choose_count_ += is_chosen_ ? 1 : -1;
if (can_get_added_reactions) {
td::remove(recent_chooser_dialog_ids_, chooser_dialog_id);
if (is_chosen_) {
recent_chooser_dialog_ids_.insert(recent_chooser_dialog_ids_.begin(), chooser_dialog_id);
if (recent_chooser_dialog_ids_.size() > MAX_RECENT_CHOOSERS) {
recent_chooser_dialog_ids_.resize(MAX_RECENT_CHOOSERS);
}
}
}
}
}
td_api::object_ptr<td_api::messageReaction> MessageReaction::get_message_reaction_object(Td *td) const {
CHECK(!is_empty());
vector<td_api::object_ptr<td_api::MessageSender>> recent_choosers;
for (auto dialog_id : recent_chooser_dialog_ids_) {
auto recent_chooser = get_min_message_sender_object(td, dialog_id, "get_message_reaction_object");
if (recent_chooser != nullptr) {
recent_choosers.push_back(std::move(recent_chooser));
}
}
return td_api::make_object<td_api::messageReaction>(reaction_, choose_count_, is_chosen_, std::move(recent_choosers));
}
bool operator==(const MessageReaction &lhs, const MessageReaction &rhs) {
return lhs.reaction_ == rhs.reaction_ && lhs.choose_count_ == rhs.choose_count_ && lhs.is_chosen_ == rhs.is_chosen_ &&
lhs.recent_chooser_dialog_ids_ == rhs.recent_chooser_dialog_ids_;
}
StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &reaction) {
string_builder << '[' << reaction.reaction_ << (reaction.is_chosen_ ? " X " : " x ") << reaction.choose_count_;
if (!reaction.recent_chooser_dialog_ids_.empty()) {
string_builder << " by " << reaction.recent_chooser_dialog_ids_;
}
return string_builder << ']';
}
td_api::object_ptr<td_api::unreadReaction> UnreadMessageReaction::get_unread_reaction_object(Td *td) const {
auto sender_id = get_min_message_sender_object(td, sender_dialog_id_, "get_unread_reaction_object");
if (sender_id == nullptr) {
return nullptr;
}
return td_api::make_object<td_api::unreadReaction>(reaction_, std::move(sender_id), is_big_);
}
bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs) {
return lhs.reaction_ == rhs.reaction_ && lhs.sender_dialog_id_ == rhs.sender_dialog_id_ && lhs.is_big_ == rhs.is_big_;
}
StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &unread_reaction) {
return string_builder << '[' << unread_reaction.reaction_ << (unread_reaction.is_big_ ? " BY " : " by ")
<< unread_reaction.sender_dialog_id_ << ']';
}
unique_ptr<MessageReactions> MessageReactions::get_message_reactions(
Td *td, tl_object_ptr<telegram_api::messageReactions> &&reactions, bool is_bot) {
if (reactions == nullptr || is_bot) {
return nullptr;
}
auto result = make_unique<MessageReactions>();
result->can_get_added_reactions_ = reactions->can_see_list_;
result->is_min_ = reactions->min_;
std::unordered_set<string> reaction_strings;
std::unordered_set<DialogId, DialogIdHash> recent_choosers;
for (auto &reaction_count : reactions->results_) {
if (reaction_count->count_ <= 0 || reaction_count->count_ >= MessageReaction::MAX_CHOOSE_COUNT) {
LOG(ERROR) << "Receive reaction " << reaction_count->reaction_ << " with invalid count "
<< reaction_count->count_;
continue;
}
if (!reaction_strings.insert(reaction_count->reaction_).second) {
LOG(ERROR) << "Receive duplicate reaction " << reaction_count->reaction_;
continue;
}
vector<DialogId> recent_chooser_dialog_ids;
vector<std::pair<ChannelId, MinChannel>> recent_chooser_min_channels;
for (auto &peer_reaction : reactions->recent_reactions_) {
if (peer_reaction->reaction_ == reaction_count->reaction_) {
DialogId dialog_id(peer_reaction->peer_id_);
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << dialog_id << " as a recent chooser";
continue;
}
if (!recent_choosers.insert(dialog_id).second) {
LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent chooser";
continue;
}
if (!td->messages_manager_->have_dialog_info(dialog_id)) {
auto dialog_type = dialog_id.get_type();
if (dialog_type == DialogType::User) {
auto user_id = dialog_id.get_user_id();
if (!td->contacts_manager_->have_min_user(user_id)) {
LOG(ERROR) << "Have no info about " << user_id;
continue;
}
} else if (dialog_type == DialogType::Channel) {
auto channel_id = dialog_id.get_channel_id();
auto min_channel = td->contacts_manager_->get_min_channel(channel_id);
if (min_channel == nullptr) {
LOG(ERROR) << "Have no info about reacted " << channel_id;
continue;
}
recent_chooser_min_channels.emplace_back(channel_id, *min_channel);
} else {
LOG(ERROR) << "Have no info about reacted " << dialog_id;
continue;
}
}
recent_chooser_dialog_ids.push_back(dialog_id);
if (peer_reaction->unread_) {
result->unread_reactions_.emplace_back(std::move(peer_reaction->reaction_), dialog_id, peer_reaction->big_);
}
if (recent_chooser_dialog_ids.size() == MessageReaction::MAX_RECENT_CHOOSERS) {
break;
}
}
}
result->reactions_.emplace_back(std::move(reaction_count->reaction_), reaction_count->count_,
reaction_count->chosen_, std::move(recent_chooser_dialog_ids),
std::move(recent_chooser_min_channels));
}
return result;
}
MessageReaction *MessageReactions::get_reaction(const string &reaction) {
for (auto &added_reaction : reactions_) {
if (added_reaction.get_reaction() == reaction) {
return &added_reaction;
}
}
return nullptr;
}
const MessageReaction *MessageReactions::get_reaction(const string &reaction) const {
for (auto &added_reaction : reactions_) {
if (added_reaction.get_reaction() == reaction) {
return &added_reaction;
}
}
return nullptr;
}
void MessageReactions::update_from(const MessageReactions &old_reactions) {
if (is_min_ && !old_reactions.is_min_) {
// chosen reaction was known, keep it
is_min_ = false;
for (const auto &old_reaction : old_reactions.reactions_) {
if (old_reaction.is_chosen()) {
auto *reaction = get_reaction(old_reaction.get_reaction());
if (reaction != nullptr) {
reaction->set_is_chosen(true, DialogId(), false);
}
}
}
}
}
void MessageReactions::sort_reactions(const std::unordered_map<string, size_t> &active_reaction_pos) {
std::sort(reactions_.begin(), reactions_.end(),
[&active_reaction_pos](const MessageReaction &lhs, const MessageReaction &rhs) {
if (lhs.get_choose_count() != rhs.get_choose_count()) {
return lhs.get_choose_count() > rhs.get_choose_count();
}
auto lhs_it = active_reaction_pos.find(lhs.get_reaction());
auto lhs_pos = lhs_it != active_reaction_pos.end() ? lhs_it->second : active_reaction_pos.size();
auto rhs_it = active_reaction_pos.find(rhs.get_reaction());
auto rhs_pos = rhs_it != active_reaction_pos.end() ? rhs_it->second : active_reaction_pos.size();
if (lhs_pos != rhs_pos) {
return lhs_pos < rhs_pos;
}
return lhs.get_reaction() < rhs.get_reaction();
});
}
bool MessageReactions::need_update_message_reactions(const MessageReactions *old_reactions,
const MessageReactions *new_reactions) {
if (old_reactions == nullptr) {
// add reactions
return new_reactions != nullptr;
}
if (new_reactions == nullptr) {
// remove reactions when they are disabled
return true;
}
// unread_reactions_ are updated independently; compare all other fields
return old_reactions->reactions_ != new_reactions->reactions_ || old_reactions->is_min_ != new_reactions->is_min_ ||
old_reactions->can_get_added_reactions_ != new_reactions->can_get_added_reactions_ ||
old_reactions->need_polling_ != new_reactions->need_polling_;
}
bool MessageReactions::need_update_unread_reactions(const MessageReactions *old_reactions,
const MessageReactions *new_reactions) {
if (old_reactions == nullptr || old_reactions->unread_reactions_.empty()) {
return !(new_reactions == nullptr || new_reactions->unread_reactions_.empty());
}
return new_reactions == nullptr || old_reactions->unread_reactions_ != new_reactions->unread_reactions_;
}
StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions) {
return string_builder << (reactions.is_min_ ? "Min" : "") << "MessageReactions{" << reactions.reactions_
<< " with unread " << reactions.unread_reactions_
<< " and can_get_added_reactions = " << reactions.can_get_added_reactions_;
}
StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<MessageReactions> &reactions) {
if (reactions == nullptr) {
return string_builder << "null";
}
return string_builder << *reactions;
}
void reload_message_reactions(Td *td, DialogId dialog_id, vector<MessageId> &&message_ids) {
if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read) || message_ids.empty()) {
return;
}
for (const auto &message_id : message_ids) {
CHECK(message_id.is_valid());
CHECK(message_id.is_server());
}
td->create_handler<GetMessagesReactionsQuery>()->send(dialog_id, std::move(message_ids));
}
void set_message_reaction(Td *td, FullMessageId full_message_id, string reaction, bool is_big,
Promise<Unit> &&promise) {
td->create_handler<SendReactionQuery>(std::move(promise))->send(full_message_id, std::move(reaction), is_big);
}
void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit,
Promise<td_api::object_ptr<td_api::addedReactions>> &&promise) {
if (!td->messages_manager_->have_message_force(full_message_id, "get_message_added_reactions")) {
return promise.set_error(Status::Error(400, "Message not found"));
}
auto message_id = full_message_id.get_message_id();
if (full_message_id.get_dialog_id().get_type() == DialogType::SecretChat || !message_id.is_valid() ||
!message_id.is_server()) {
return promise.set_value(td_api::make_object<td_api::addedReactions>(0, Auto(), string()));
}
if (limit <= 0) {
return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
static constexpr int32 MAX_GET_ADDED_REACTIONS = 100; // server side limit
if (limit > MAX_GET_ADDED_REACTIONS) {
limit = MAX_GET_ADDED_REACTIONS;
}
td->create_handler<GetMessageReactionsListQuery>(std::move(promise))
->send(full_message_id, std::move(reaction), std::move(offset), limit);
}
} // namespace td

View File

@ -0,0 +1,176 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/ChannelId.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/FullMessageId.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MinChannel.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
#include <unordered_map>
#include <utility>
namespace td {
class Td;
class MessageReaction {
string reaction_;
int32 choose_count_ = 0;
bool is_chosen_ = false;
vector<DialogId> recent_chooser_dialog_ids_;
vector<std::pair<ChannelId, MinChannel>> recent_chooser_min_channels_;
friend bool operator==(const MessageReaction &lhs, const MessageReaction &rhs);
friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &message_reaction);
public:
static constexpr size_t MAX_RECENT_CHOOSERS = 3;
static constexpr int32 MAX_CHOOSE_COUNT = 2147483640;
MessageReaction() = default;
MessageReaction(string reaction, int32 choose_count, bool is_chosen, vector<DialogId> &&recent_chooser_dialog_ids,
vector<std::pair<ChannelId, MinChannel>> &&recent_chooser_min_channels)
: reaction_(std::move(reaction))
, choose_count_(choose_count)
, is_chosen_(is_chosen)
, recent_chooser_dialog_ids_(std::move(recent_chooser_dialog_ids))
, recent_chooser_min_channels_(std::move(recent_chooser_min_channels)) {
}
bool is_empty() const {
return choose_count_ <= 0;
}
const string &get_reaction() const {
return reaction_;
}
bool is_chosen() const {
return is_chosen_;
}
void set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool can_get_added_reactions);
int32 get_choose_count() const {
return choose_count_;
}
const vector<DialogId> &get_recent_chooser_dialog_ids() const {
return recent_chooser_dialog_ids_;
}
const vector<std::pair<ChannelId, MinChannel>> &get_recent_chooser_min_channels() const {
return recent_chooser_min_channels_;
}
td_api::object_ptr<td_api::messageReaction> get_message_reaction_object(Td *td) const;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
bool operator==(const MessageReaction &lhs, const MessageReaction &rhs);
inline bool operator!=(const MessageReaction &lhs, const MessageReaction &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &reaction);
class UnreadMessageReaction {
string reaction_;
DialogId sender_dialog_id_;
bool is_big_ = false;
friend bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs);
friend StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &message_reaction);
public:
UnreadMessageReaction() = default;
UnreadMessageReaction(string reaction, DialogId sender_dialog_id, bool is_big)
: reaction_(std::move(reaction)), sender_dialog_id_(sender_dialog_id), is_big_(is_big) {
}
td_api::object_ptr<td_api::unreadReaction> get_unread_reaction_object(Td *td) const;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs);
inline bool operator!=(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &unread_reaction);
struct MessageReactions {
vector<MessageReaction> reactions_;
vector<UnreadMessageReaction> unread_reactions_;
bool is_min_ = false;
bool need_polling_ = true;
bool can_get_added_reactions_ = false;
MessageReactions() = default;
static unique_ptr<MessageReactions> get_message_reactions(Td *td,
tl_object_ptr<telegram_api::messageReactions> &&reactions,
bool is_bot);
MessageReaction *get_reaction(const string &reaction);
const MessageReaction *get_reaction(const string &reaction) const;
void update_from(const MessageReactions &old_reactions);
void sort_reactions(const std::unordered_map<string, size_t> &active_reaction_pos);
static bool need_update_message_reactions(const MessageReactions *old_reactions,
const MessageReactions *new_reactions);
static bool need_update_unread_reactions(const MessageReactions *old_reactions,
const MessageReactions *new_reactions);
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions);
StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<MessageReactions> &reactions);
void reload_message_reactions(Td *td, DialogId dialog_id, vector<MessageId> &&message_ids);
void set_message_reaction(Td *td, FullMessageId full_message_id, string reaction, bool is_big, Promise<Unit> &&promise);
void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit,
Promise<td_api::object_ptr<td_api::addedReactions>> &&promise);
} // namespace td

View File

@ -0,0 +1,112 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/MessageReaction.h"
#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
template <class StorerT>
void MessageReaction::store(StorerT &storer) const {
CHECK(!is_empty());
bool has_recent_chooser_dialog_ids = !recent_chooser_dialog_ids_.empty();
bool has_recent_chooser_min_channels = !recent_chooser_min_channels_.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(is_chosen_);
STORE_FLAG(has_recent_chooser_dialog_ids);
STORE_FLAG(has_recent_chooser_min_channels);
END_STORE_FLAGS();
td::store(reaction_, storer);
td::store(choose_count_, storer);
if (has_recent_chooser_dialog_ids) {
td::store(recent_chooser_dialog_ids_, storer);
}
if (has_recent_chooser_min_channels) {
td::store(recent_chooser_min_channels_, storer);
}
}
template <class ParserT>
void MessageReaction::parse(ParserT &parser) {
bool has_recent_chooser_dialog_ids;
bool has_recent_chooser_min_channels;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_chosen_);
PARSE_FLAG(has_recent_chooser_dialog_ids);
PARSE_FLAG(has_recent_chooser_min_channels);
END_PARSE_FLAGS();
td::parse(reaction_, parser);
td::parse(choose_count_, parser);
if (has_recent_chooser_dialog_ids) {
td::parse(recent_chooser_dialog_ids_, parser);
}
if (has_recent_chooser_min_channels) {
td::parse(recent_chooser_min_channels_, parser);
}
CHECK(!is_empty());
}
template <class StorerT>
void UnreadMessageReaction::store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(is_big_);
END_STORE_FLAGS();
td::store(reaction_, storer);
td::store(sender_dialog_id_, storer);
}
template <class ParserT>
void UnreadMessageReaction::parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_big_);
END_PARSE_FLAGS();
td::parse(reaction_, parser);
td::parse(sender_dialog_id_, parser);
}
template <class StorerT>
void MessageReactions::store(StorerT &storer) const {
bool has_reactions = !reactions_.empty();
bool has_unread_reactions = !unread_reactions_.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(is_min_);
STORE_FLAG(need_polling_);
STORE_FLAG(can_get_added_reactions_);
STORE_FLAG(has_unread_reactions);
STORE_FLAG(has_reactions);
END_STORE_FLAGS();
if (has_reactions) {
td::store(reactions_, storer);
}
if (has_unread_reactions) {
td::store(unread_reactions_, storer);
}
}
template <class ParserT>
void MessageReactions::parse(ParserT &parser) {
bool has_reactions;
bool has_unread_reactions;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_min_);
PARSE_FLAG(need_polling_);
PARSE_FLAG(can_get_added_reactions_);
PARSE_FLAG(has_unread_reactions);
PARSE_FLAG(has_reactions);
END_PARSE_FLAGS();
if (has_reactions) {
td::parse(reactions_, parser);
}
if (has_unread_reactions) {
td::parse(unread_reactions_, parser);
}
}
} // namespace td

View File

@ -7,6 +7,7 @@
#include "td/telegram/MessageReplyInfo.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/MessageSender.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/ServerMessageId.h"
#include "td/telegram/Td.h"
@ -197,28 +198,9 @@ td_api::object_ptr<td_api::messageReplyInfo> MessageReplyInfo::get_message_reply
vector<td_api::object_ptr<td_api::MessageSender>> recent_repliers;
for (auto dialog_id : recent_replier_dialog_ids) {
auto dialog_type = dialog_id.get_type();
if (dialog_type == DialogType::User) {
auto user_id = dialog_id.get_user_id();
if (td->contacts_manager_->have_min_user(user_id)) {
recent_repliers.push_back(td_api::make_object<td_api::messageSenderUser>(
td->contacts_manager_->get_user_id_object(user_id, "get_message_reply_info_object")));
} else {
LOG(ERROR) << "Skip unknown replied " << user_id;
}
} else {
if (!td->messages_manager_->have_dialog(dialog_id) &&
(td->messages_manager_->have_dialog_info(dialog_id) ||
(dialog_type == DialogType::Channel &&
td->contacts_manager_->have_min_channel(dialog_id.get_channel_id())))) {
LOG(INFO) << "Force creation of " << dialog_id;
td->messages_manager_->force_create_dialog(dialog_id, "get_message_reply_info_object", true);
}
if (td->messages_manager_->have_dialog(dialog_id)) {
recent_repliers.push_back(td_api::make_object<td_api::messageSenderChat>(dialog_id.get()));
} else {
LOG(ERROR) << "Skip unknown replied " << dialog_id;
}
auto recent_replier = get_min_message_sender_object(td, dialog_id, "get_message_reply_info_object");
if (recent_replier != nullptr) {
recent_repliers.push_back(std::move(recent_replier));
}
}
return td_api::make_object<td_api::messageReplyInfo>(reply_count, std::move(recent_repliers),

View File

@ -47,6 +47,7 @@ tl_object_ptr<telegram_api::MessagesFilter> get_input_messages_filter(MessageSea
return make_tl_object<telegram_api::inputMessagesFilterPinned>();
case MessageSearchFilter::UnreadMention:
case MessageSearchFilter::FailedToSend:
case MessageSearchFilter::UnreadReaction:
default:
UNREACHABLE();
return nullptr;
@ -90,6 +91,8 @@ MessageSearchFilter get_message_search_filter(const tl_object_ptr<td_api::Search
return MessageSearchFilter::FailedToSend;
case td_api::searchMessagesFilterPinned::ID:
return MessageSearchFilter::Pinned;
case td_api::searchMessagesFilterUnreadReaction::ID:
return MessageSearchFilter::UnreadReaction;
default:
UNREACHABLE();
return MessageSearchFilter::Empty;
@ -134,6 +137,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageSearchFilter fil
return string_builder << "FailedToSend";
case MessageSearchFilter::Pinned:
return string_builder << "Pinned";
case MessageSearchFilter::UnreadReaction:
return string_builder << "UnreadReaction";
default:
UNREACHABLE();
return string_builder;

View File

@ -34,6 +34,7 @@ enum class MessageSearchFilter : int32 {
UnreadMention,
FailedToSend,
Pinned,
UnreadReaction,
Size
};

View File

@ -59,6 +59,29 @@ td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, Dial
return get_message_sender_object(td, UserId(), dialog_id, source);
}
td_api::object_ptr<td_api::MessageSender> get_min_message_sender_object(Td *td, DialogId dialog_id,
const char *source) {
auto dialog_type = dialog_id.get_type();
if (dialog_type == DialogType::User) {
auto user_id = dialog_id.get_user_id();
if (td->contacts_manager_->have_min_user(user_id)) {
return td_api::make_object<td_api::messageSenderUser>(td->contacts_manager_->get_user_id_object(user_id, source));
}
} else {
if (!td->messages_manager_->have_dialog(dialog_id) &&
(td->messages_manager_->have_dialog_info(dialog_id) ||
(dialog_type == DialogType::Channel && td->contacts_manager_->have_min_channel(dialog_id.get_channel_id())))) {
LOG(INFO) << "Force creation of " << dialog_id;
td->messages_manager_->force_create_dialog(dialog_id, source, true);
}
if (td->messages_manager_->have_dialog(dialog_id)) {
return td_api::make_object<td_api::messageSenderChat>(dialog_id.get());
}
}
LOG(ERROR) << "Can't return unknown " << dialog_id << " from " << source;
return nullptr;
}
vector<DialogId> get_message_sender_dialog_ids(Td *td,
const vector<telegram_api::object_ptr<telegram_api::Peer>> &peers) {
vector<DialogId> message_sender_dialog_ids;

View File

@ -29,6 +29,8 @@ td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, User
td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, DialogId dialog_id, const char *source);
td_api::object_ptr<td_api::MessageSender> get_min_message_sender_object(Td *td, DialogId dialog_id, const char *source);
vector<DialogId> get_message_sender_dialog_ids(Td *td,
const vector<telegram_api::object_ptr<telegram_api::Peer>> &peers);

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,7 @@
#include "td/telegram/secret_api.h"
#include "td/telegram/SecretChatId.h"
#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/SequenceDispatcher.h"
#include "td/telegram/ServerMessageId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
@ -91,7 +92,7 @@ class DialogFilter;
class DraftMessage;
struct InputMessageContent;
class MessageContent;
class MultiSequenceDispatcher;
struct MessageReactions;
class Td;
class MessagesManager final : public Actor {
@ -115,6 +116,7 @@ class MessagesManager final : public Actor {
static constexpr int32 MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID = 1 << 17;
static constexpr int32 MESSAGE_FLAG_IS_FROM_SCHEDULED = 1 << 18;
static constexpr int32 MESSAGE_FLAG_IS_LEGACY = 1 << 19;
static constexpr int32 MESSAGE_FLAG_HAS_REACTIONS = 1 << 20;
static constexpr int32 MESSAGE_FLAG_HIDE_EDIT_DATE = 1 << 21;
static constexpr int32 MESSAGE_FLAG_IS_RESTRICTED = 1 << 22;
static constexpr int32 MESSAGE_FLAG_HAS_REPLY_INFO = 1 << 23;
@ -336,9 +338,13 @@ class MessagesManager final : public Actor {
void on_update_message_forward_count(FullMessageId full_message_id, int32 forward_count);
void on_update_message_reactions(FullMessageId full_message_id,
tl_object_ptr<telegram_api::messageReactions> &&reactions);
void on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count,
bool has_reply_info,
tl_object_ptr<telegram_api::messageReplies> &&reply_info);
bool has_reply_info, tl_object_ptr<telegram_api::messageReplies> &&reply_info,
bool has_reactions,
tl_object_ptr<telegram_api::messageReactions> &&reactions);
void on_update_live_location_viewed(FullMessageId full_message_id);
@ -382,6 +388,8 @@ class MessagesManager final : public Actor {
void read_all_dialog_mentions(DialogId dialog_id, Promise<Unit> &&promise);
void read_all_dialog_reactions(DialogId dialog_id, Promise<Unit> &&promise);
Status add_recently_found_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;
Status remove_recently_found_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;
@ -506,6 +514,10 @@ class MessagesManager final : public Actor {
void set_dialog_description(DialogId dialog_id, const string &description, Promise<Unit> &&promise);
void set_active_reactions(vector<string> active_reactions);
void set_dialog_available_reactions(DialogId dialog_id, vector<string> available_reactions, Promise<Unit> &&promise);
void set_dialog_permissions(DialogId dialog_id, const td_api::object_ptr<td_api::chatPermissions> &permissions,
Promise<Unit> &&promise);
@ -528,7 +540,7 @@ class MessagesManager final : public Actor {
bool have_dialog_info(DialogId dialog_id) const;
bool have_dialog_info_force(DialogId dialog_id) const;
void reload_dialog_info_full(DialogId dialog_id);
void reload_dialog_info_full(DialogId dialog_id, const char *source);
void on_dialog_info_full_invalidated(DialogId dialog_id);
@ -601,6 +613,9 @@ class MessagesManager final : public Actor {
void get_message_viewers(FullMessageId full_message_id, Promise<td_api::object_ptr<td_api::users>> &&promise);
void translate_text(const string &text, const string &from_language_code, const string &to_language_code,
Promise<td_api::object_ptr<td_api::text>> &&promise);
bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds);
bool is_deleted_secret_chat(DialogId dialog_id) const;
@ -769,6 +784,10 @@ class MessagesManager final : public Actor {
vector<MessageId> get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result,
Promise<Unit> &&promise);
Result<vector<string>> get_message_available_reactions(FullMessageId full_message_id);
void set_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, Promise<Unit> &&promise);
void get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit,
Promise<td_api::object_ptr<td_api::foundMessages>> &&promise);
@ -827,6 +846,8 @@ class MessagesManager final : public Actor {
void on_update_scope_notify_settings(NotificationSettingsScope scope,
tl_object_ptr<telegram_api::peerNotifySettings> &&peer_notify_settings);
void on_update_dialog_available_reactions(DialogId dialog_id, vector<string> &&available_reactions);
void hide_dialog_action_bar(DialogId dialog_id);
void remove_dialog_action_bar(DialogId dialog_id, Promise<Unit> &&promise);
@ -989,6 +1010,7 @@ class MessagesManager final : public Actor {
int32 view_count = 0;
int32 forward_count = 0;
tl_object_ptr<telegram_api::messageReplies> reply_info;
tl_object_ptr<telegram_api::messageReactions> reactions;
int32 flags = 0;
int32 edit_date = 0;
vector<RestrictionReason> restriction_reasons;
@ -1121,6 +1143,7 @@ class MessagesManager final : public Actor {
int32 view_count = 0;
int32 forward_count = 0;
MessageReplyInfo reply_info;
unique_ptr<MessageReactions> reactions;
unique_ptr<DraftMessage> thread_draft_message;
int32 interaction_info_update_date = 0;
@ -1202,12 +1225,14 @@ class MessagesManager final : public Actor {
int32 server_unread_count = 0;
int32 local_unread_count = 0;
int32 unread_mention_count = 0;
int32 unread_reaction_count = 0;
int32 last_read_inbox_message_date = 0; // secret chats only
MessageId last_read_inbox_message_id;
MessageId last_read_outbox_message_id;
MessageId last_pinned_message_id;
MessageId reply_markup_message_id;
DialogNotificationSettings notification_settings;
vector<string> available_reactions;
MessageTtl message_ttl;
unique_ptr<DraftMessage> draft_message;
unique_ptr<DialogActionBar> action_bar;
@ -1295,6 +1320,7 @@ class MessagesManager final : public Actor {
bool has_bots = false;
bool is_has_bots_inited = false;
bool is_theme_name_inited = false;
bool is_available_reactions_inited = false;
bool increment_view_counter = false;
@ -1677,6 +1703,7 @@ class MessagesManager final : public Actor {
class ForwardMessagesLogEvent;
class GetChannelDifferenceLogEvent;
class ReadAllDialogMentionsOnServerLogEvent;
class ReadAllDialogReactionsOnServerLogEvent;
class ReadHistoryInSecretChatLogEvent;
class ReadHistoryOnServerLogEvent;
class ReadMessageContentsOnServerLogEvent;
@ -2047,6 +2074,8 @@ class MessagesManager final : public Actor {
void read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 log_event_id, Promise<Unit> &&promise);
void read_all_dialog_reactions_on_server(DialogId dialog_id, uint64 log_event_id, Promise<Unit> &&promise);
void unpin_all_dialog_messages_on_server(DialogId dialog_id, uint64 log_event_id, Promise<Unit> &&promise);
using AffectedHistoryQuery = std::function<void(DialogId, Promise<AffectedHistory>)>;
@ -2074,12 +2103,17 @@ class MessagesManager final : public Actor {
void on_pending_message_views_timeout(DialogId dialog_id);
void update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count,
bool has_reply_info, tl_object_ptr<telegram_api::messageReplies> &&reply_info);
bool has_reply_info, tl_object_ptr<telegram_api::messageReplies> &&reply_info,
bool has_reactions, tl_object_ptr<telegram_api::messageReactions> &&reactions);
bool is_active_message_reply_info(DialogId dialog_id, const MessageReplyInfo &info) const;
bool is_visible_message_reply_info(DialogId dialog_id, const Message *m) const;
bool is_visible_message_reactions(DialogId dialog_id, const Message *m) const;
bool has_unread_message_reactions(DialogId dialog_id, const Message *m) const;
void on_message_reply_info_changed(DialogId dialog_id, const Message *m) const;
Result<FullMessageId> get_top_thread_full_message_id(DialogId dialog_id, const Message *m) const;
@ -2087,11 +2121,17 @@ class MessagesManager final : public Actor {
td_api::object_ptr<td_api::messageInteractionInfo> get_message_interaction_info_object(DialogId dialog_id,
const Message *m) const;
vector<td_api::object_ptr<td_api::unreadReaction>> get_unread_reactions_object(DialogId dialog_id,
const Message *m) const;
bool update_message_interaction_info(DialogId dialog_id, Message *m, int32 view_count, int32 forward_count,
bool has_reply_info, MessageReplyInfo &&reply_info, const char *source);
bool has_reply_info, MessageReplyInfo &&reply_info, bool has_reactions,
unique_ptr<MessageReactions> &&reactions, const char *source);
bool update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, const char *source);
bool remove_message_unread_reactions(Dialog *d, Message *m, const char *source);
void read_message_content_from_updates(MessageId message_id);
void read_channel_message_content_from_updates(Dialog *d, MessageId message_id);
@ -2362,6 +2402,8 @@ class MessagesManager final : public Actor {
void send_update_message_interaction_info(DialogId dialog_id, const Message *m) const;
void send_update_message_unread_reactions(DialogId dialog_id, const Message *m, int32 unread_reaction_count) const;
void send_update_message_live_location_viewed(FullMessageId full_message_id);
void send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids, bool is_permanent,
@ -2389,6 +2431,8 @@ class MessagesManager final : public Actor {
void send_update_chat_unread_mention_count(const Dialog *d);
void send_update_chat_unread_reaction_count(const Dialog *d);
void send_update_chat_position(DialogListId dialog_list_id, const Dialog *d, const char *source) const;
void send_update_chat_online_member_count(DialogId dialog_id, int32 online_member_count) const;
@ -2397,6 +2441,8 @@ class MessagesManager final : public Actor {
void send_update_chat_action_bar(Dialog *d);
void send_update_chat_available_reactions(const Dialog *d);
void send_update_secret_chats_with_user_theme(const Dialog *d) const;
void send_update_chat_theme(const Dialog *d);
@ -2479,6 +2525,8 @@ class MessagesManager final : public Actor {
static void set_dialog_unread_mention_count(Dialog *d, int32 unread_mention_count);
static void set_dialog_unread_reaction_count(Dialog *d, int32 unread_reaction_count);
void set_dialog_is_empty(Dialog *d, const char *source);
void remove_dialog_newer_messages(Dialog *d, MessageId from_message_id, const char *source);
@ -2569,6 +2617,23 @@ class MessagesManager final : public Actor {
bool update_dialog_silent_send_message(Dialog *d, bool silent_send_message);
vector<string> get_message_available_reactions(const Dialog *d, const Message *m);
void on_set_message_reaction(FullMessageId full_message_id, Result<Unit> result, Promise<Unit> promise);
void set_dialog_available_reactions(Dialog *d, vector<string> &&available_reactions);
void update_dialog_message_reactions_visibility(Dialog *d);
vector<string> get_active_reactions(const vector<string> &available_reactions) const;
static vector<string> get_active_reactions(const vector<string> &available_reactions,
const vector<string> &active_reactions);
vector<string> get_dialog_active_reactions(const Dialog *d) const;
vector<string> get_message_active_reactions(const Dialog *d, const Message *m) const;
bool is_dialog_action_unneeded(DialogId dialog_id) const;
void on_send_dialog_action_timeout(DialogId dialog_id);
@ -3001,7 +3066,7 @@ class MessagesManager final : public Actor {
vector<tl_object_ptr<telegram_api::Update>> &&other_updates);
void on_get_channel_dialog(DialogId dialog_id, MessageId last_message_id, MessageId read_inbox_max_message_id,
int32 server_unread_count, int32 unread_mention_count,
int32 server_unread_count, int32 unread_mention_count, int32 unread_reaction_count,
MessageId read_outbox_max_message_id,
vector<tl_object_ptr<telegram_api::Message>> &&messages);
@ -3148,6 +3213,8 @@ class MessagesManager final : public Actor {
static uint64 save_read_all_dialog_mentions_on_server_log_event(DialogId dialog_id);
static uint64 save_read_all_dialog_reactions_on_server_log_event(DialogId dialog_id);
static uint64 save_toggle_dialog_is_pinned_on_server_log_event(DialogId dialog_id, bool is_pinned);
static uint64 save_reorder_pinned_dialogs_on_server_log_event(FolderId folder_id, const vector<DialogId> &dialog_ids);
@ -3568,6 +3635,15 @@ class MessagesManager final : public Actor {
std::unordered_map<DialogId, MessageId, DialogIdHash> previous_repaired_read_inbox_max_message_id_;
struct PendingReaction {
int32 query_count = 0;
bool was_updated = false;
};
std::unordered_map<FullMessageId, PendingReaction, FullMessageIdHash> pending_reactions_;
vector<string> active_reactions_;
std::unordered_map<string, size_t> active_reaction_pos_;
uint32 scheduled_messages_sync_generation_ = 1;
int64 authorization_date_ = 0;

View File

@ -3259,7 +3259,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo
loc_args.clear();
}
if (loc_args.size() > 1) {
return Status::Error("Receive too much arguments");
return Status::Error("Receive too many arguments");
}
if (loc_args.size() == 1) {
@ -4099,6 +4099,9 @@ void NotificationManager::try_send_update_active_notifications() {
}
void NotificationManager::on_binlog_events(vector<BinlogEvent> &&events) {
if (G()->close_flag()) {
return;
}
VLOG(notifications) << "Begin to process " << events.size() << " binlog events";
for (auto &event : events) {
if (!G()->parameters().use_message_db || is_disabled() || max_notification_group_count_ == 0) {

View File

@ -24,8 +24,11 @@
#include "td/telegram/SuggestedAction.h"
#include "td/telegram/Td.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/TopDialogManager.h"
#include "td/utils/buffer.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
@ -35,6 +38,36 @@
namespace td {
class SetDefaultReactionQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit SetDefaultReactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(const string &reaction) {
send_query(G()->net_query_creator().create(telegram_api::messages_setDefaultReaction(reaction)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setDefaultReaction>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
if (result_ptr.ok()) {
promise_.set_value(Unit());
} else {
on_error(Status::Error(400, "Receive false"));
}
}
void on_error(Status status) final {
LOG(INFO) << "Failed to set default reaction: " << status;
promise_.set_error(std::move(status));
}
};
OptionManager::OptionManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
send_unix_time_update();
}
@ -80,11 +113,12 @@ bool OptionManager::is_internal_option(Slice name) {
case 'b':
return name == "base_language_pack_version";
case 'c':
return name == "call_ring_timeout_ms" || name == "call_receive_timeout_ms" ||
return name == "call_receive_timeout_ms" || name == "call_ring_timeout_ms" ||
name == "channels_read_media_period" || name == "chat_read_mark_expire_period" ||
name == "chat_read_mark_size_threshold";
case 'd':
return name == "dc_txt_domain_name" || name == "dice_emojis" || name == "dice_success_values";
return name == "dc_txt_domain_name" || name == "default_reaction_needs_sync" || name == "dice_emojis" ||
name == "dice_success_values";
case 'e':
return name == "edit_time_limit" || name == "emoji_sounds";
case 'i':
@ -96,10 +130,10 @@ bool OptionManager::is_internal_option(Slice name) {
case 'n':
return name == "notification_cloud_delay_ms" || name == "notification_default_delay_ms";
case 'o':
return name == "online_update_period_ms" || name == "online_cloud_timeout_ms" || name == "otherwise_relogin_days";
return name == "online_cloud_timeout_ms" || name == "online_update_period_ms" || name == "otherwise_relogin_days";
case 'r':
return name == "revoke_pm_inbox" || name == "revoke_time_limit" || name == "revoke_pm_time_limit" ||
name == "rating_e_decay" || name == "recent_stickers_limit";
return name == "rating_e_decay" || name == "reactions_uniq_max" || name == "recent_stickers_limit" ||
name == "revoke_pm_inbox" || name == "revoke_time_limit" || name == "revoke_pm_time_limit";
case 's':
return name == "saved_animations_limit" || name == "session_count";
case 'v':
@ -144,6 +178,9 @@ void OptionManager::on_option_updated(const string &name) {
}
break;
case 'd':
if (name == "default_reaction_needs_sync" && G()->shared_config().get_option_boolean(name)) {
set_default_reaction();
}
if (name == "dice_emojis") {
send_closure(td_->stickers_manager_actor_, &StickersManager::on_update_dice_emojis);
}
@ -342,7 +379,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
if (value_constructor_id != td_api::optionValueInteger::ID &&
value_constructor_id != td_api::optionValueEmpty::ID) {
promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" must have integer value"));
return true;
return false;
}
if (value_constructor_id == td_api::optionValueEmpty::ID) {
G()->shared_config().set_option_empty(option_name);
@ -352,7 +389,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
promise.set_error(Status::Error(400, PSLICE() << "Option's \"" << name << "\" value " << int_value
<< " is outside of the valid range [" << min_value << ", "
<< max_value << "]"));
return true;
return false;
}
G()->shared_config().set_option_integer(name, int_value);
}
@ -367,7 +404,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
if (value_constructor_id != td_api::optionValueBoolean::ID &&
value_constructor_id != td_api::optionValueEmpty::ID) {
promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" must have boolean value"));
return true;
return false;
}
if (value_constructor_id == td_api::optionValueEmpty::ID) {
G()->shared_config().set_option_empty(name);
@ -385,7 +422,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
}
if (value_constructor_id != td_api::optionValueString::ID && value_constructor_id != td_api::optionValueEmpty::ID) {
promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" must have string value"));
return true;
return false;
}
if (value_constructor_id == td_api::optionValueEmpty::ID) {
G()->shared_config().set_option_empty(name);
@ -398,7 +435,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
G()->shared_config().set_option_string(name, str_value);
} else {
promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" can't have specified value"));
return true;
return false;
}
}
}
@ -439,6 +476,12 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
}
break;
case 'd':
if (!is_bot && set_string_option("default_reaction", [td = td_](Slice value) {
return td->stickers_manager_->is_active_reaction(value.str());
})) {
G()->shared_config().set_option_boolean("default_reaction_needs_sync", true);
return;
}
if (!is_bot && set_boolean_option("disable_animated_emoji")) {
return;
}
@ -654,7 +697,9 @@ void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::Op
}
}
promise.set_error(Status::Error(400, "Option can't be set"));
if (promise) {
promise.set_error(Status::Error(400, "Option can't be set"));
}
}
td_api::object_ptr<td_api::OptionValue> OptionManager::get_option_value_object(Slice value) {
@ -697,4 +742,23 @@ void OptionManager::get_current_state(vector<td_api::object_ptr<td_api::Update>>
}
}
void OptionManager::set_default_reaction() {
auto promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result<Unit> &&result) {
send_closure(actor_id, &OptionManager::on_set_default_reaction, result.is_ok());
});
td_->create_handler<SetDefaultReactionQuery>(std::move(promise))
->send(G()->shared_config().get_option_string("default_reaction"));
}
void OptionManager::on_set_default_reaction(bool success) {
if (G()->close_flag() && !success) {
return;
}
G()->shared_config().set_option_empty("default_reaction_needs_sync");
if (!success) {
send_closure(G()->config_manager(), &ConfigManager::reget_app_config, Promise<Unit>());
}
}
} // namespace td

View File

@ -51,6 +51,10 @@ class OptionManager final : public Actor {
void send_unix_time_update();
void set_default_reaction();
void on_set_default_reaction(bool success);
Td *td_;
ActorShared<> parent_;

View File

@ -113,6 +113,8 @@ static td_api::object_ptr<td_api::ThumbnailFormat> get_thumbnail_format_object(P
return td_api::make_object<td_api::thumbnailFormatTgs>();
case PhotoFormat::Mpeg4:
return td_api::make_object<td_api::thumbnailFormatMpeg4>();
case PhotoFormat::Webm:
return td_api::make_object<td_api::thumbnailFormatWebm>();
default:
UNREACHABLE();
return nullptr;
@ -133,6 +135,8 @@ static StringBuilder &operator<<(StringBuilder &string_builder, PhotoFormat form
return string_builder << "tgs";
case PhotoFormat::Mpeg4:
return string_builder << "mp4";
case PhotoFormat::Webm:
return string_builder << "webm";
default:
UNREACHABLE();
return string_builder;
@ -426,7 +430,7 @@ Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSo
}
case telegram_api::photoPathSize::ID: {
auto size = move_tl_object_as<telegram_api::photoPathSize>(size_ptr);
if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp) {
if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp && format != PhotoFormat::Webm) {
LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo " << id << " from " << source << " of format "
<< format;
return std::move(res);

View File

@ -107,7 +107,7 @@ bool operator!=(const DialogPhoto &lhs, const DialogPhoto &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo);
enum class PhotoFormat : int32 { Jpeg, Png, Webp, Gif, Tgs, Mpeg4 };
enum class PhotoFormat : int32 { Jpeg, Png, Webp, Gif, Tgs, Mpeg4, Webm };
PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id,
int32 width, int32 height);

View File

@ -452,7 +452,9 @@ vector<int32> PollManager::get_vote_percentage(const vector<int32> &voter_counts
std::unordered_map<int32, Option> options;
for (size_t i = 0; i < result.size(); i++) {
auto &option = options[voter_counts[i]];
option.pos = narrow_cast<int32>(i);
if (option.pos == -1) {
option.pos = narrow_cast<int32>(i);
}
option.count++;
}
vector<Option> sorted_options;
@ -474,7 +476,11 @@ vector<int32> PollManager::get_vote_percentage(const vector<int32> &voter_counts
// prefer options with smallest gap
return gap[lhs.pos] < gap[rhs.pos];
}
return lhs.count > rhs.count; // prefer more popular options
if (lhs.count != rhs.count) {
// prefer more popular options
return lhs.count > rhs.count;
}
return lhs.pos < rhs.pos; // prefer the first encountered option
});
// dynamic programming or brute force can give perfect result, but for now we use simple gready approach
@ -1543,7 +1549,7 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
}
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;
LOG(ERROR) << "Have too many " << poll_result->voters_ << " poll voters for an option in " << poll_id;
poll_result->voters_ = max_voter_count;
}
if (poll_result->voters_ != option.voter_count) {
@ -1671,6 +1677,9 @@ void PollManager::on_get_poll_vote(PollId poll_id, UserId user_id, vector<Buffer
}
void PollManager::on_binlog_events(vector<BinlogEvent> &&events) {
if (G()->close_flag()) {
return;
}
for (auto &event : events) {
switch (event.type_) {
case LogEvent::HandlerType::SetPollAnswer: {

View File

@ -11,11 +11,14 @@
#include "td/actor/PromiseFuture.h"
#include "td/utils/algorithm.h"
#include "td/utils/ChainScheduler.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include <limits>
@ -167,7 +170,11 @@ void SequenceDispatcher::loop() {
if (last_sent_i_ != std::numeric_limits<size_t>::max() && data_[last_sent_i_].state_ == State::Wait) {
invoke_after = data_[last_sent_i_].net_query_ref_;
}
data_[next_i_].query_->set_invoke_after(invoke_after);
if (!invoke_after.empty()) {
data_[next_i_].query_->set_invoke_after({invoke_after});
} else {
data_[next_i_].query_->set_invoke_after({});
}
data_[next_i_].query_->last_timeout_ = 0;
VLOG(net_query) << "Send " << data_[next_i_].query_;
@ -241,9 +248,12 @@ void SequenceDispatcher::close_silent() {
}
/*** MultiSequenceDispatcher ***/
void MultiSequenceDispatcher::send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback,
uint64 sequence_id) {
CHECK(sequence_id != 0);
void MultiSequenceDispatcherOld::send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback,
Span<uint64> chains) {
CHECK(all_of(chains, [](auto chain_id) { return chain_id != 0; }));
CHECK(!chains.empty());
auto sequence_id = chains[0];
auto it_ok = dispatchers_.emplace(sequence_id, Data{0, ActorOwn<SequenceDispatcher>()});
auto &data = it_ok.first->second;
if (it_ok.second) {
@ -255,13 +265,13 @@ void MultiSequenceDispatcher::send_with_callback(NetQueryPtr query, ActorShared<
send_closure(data.dispatcher_, &SequenceDispatcher::send_with_callback, std::move(query), std::move(callback));
}
void MultiSequenceDispatcher::on_result() {
void MultiSequenceDispatcherOld::on_result() {
auto it = dispatchers_.find(get_link_token());
CHECK(it != dispatchers_.end());
it->second.cnt_--;
}
void MultiSequenceDispatcher::ready_to_close() {
void MultiSequenceDispatcherOld::ready_to_close() {
auto it = dispatchers_.find(get_link_token());
CHECK(it != dispatchers_.end());
if (it->second.cnt_ == 0) {
@ -270,4 +280,107 @@ void MultiSequenceDispatcher::ready_to_close() {
}
}
class MultiSequenceDispatcherNewImpl final : public MultiSequenceDispatcherNew {
public:
void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback, Span<uint64> chains) final {
CHECK(all_of(chains, [](auto chain_id) { return chain_id != 0; }));
if (!chains.empty()) {
query->set_session_rand(static_cast<uint32>(chains[0] >> 10));
}
Node node;
node.net_query = std::move(query);
node.net_query->debug("Waiting at SequenceDispatcher");
node.net_query_ref = node.net_query.get_weak();
node.callback = std::move(callback);
scheduler_.create_task(chains, std::move(node));
loop();
}
private:
struct Node {
NetQueryRef net_query_ref;
NetQueryPtr net_query;
ActorShared<NetQueryCallback> callback;
friend StringBuilder &operator<<(StringBuilder &sb, const Node &node) {
return sb << node.net_query;
}
};
ChainScheduler<Node> scheduler_;
using TaskId = ChainScheduler<NetQueryPtr>::TaskId;
using ChainId = ChainScheduler<NetQueryPtr>::ChainId;
void on_result(NetQueryPtr query) final {
auto task_id = TaskId(get_link_token());
auto &node = *scheduler_.get_task_extra(task_id);
if (query->is_error() && (query->error().code() == NetQuery::ResendInvokeAfter ||
(query->error().code() == 400 && (query->error().message() == "MSG_WAIT_FAILED" ||
query->error().message() == "MSG_WAIT_TIMEOUT")))) {
VLOG(net_query) << "Resend " << query;
query->resend();
return on_resend(std::move(query));
}
auto promise = promise_send_closure(actor_shared(this, task_id), &MultiSequenceDispatcherNewImpl::on_resend);
send_closure(node.callback, &NetQueryCallback::on_result_resendable, std::move(query), std::move(promise));
}
void on_resend(Result<NetQueryPtr> query) {
auto task_id = TaskId(get_link_token());
auto &node = *scheduler_.get_task_extra(task_id);
if (query.is_error()) {
scheduler_.finish_task(task_id);
} else {
node.net_query = query.move_as_ok();
node.net_query->debug("Waiting at SequenceDispatcher");
node.net_query_ref = node.net_query.get_weak();
scheduler_.reset_task(task_id);
}
loop();
}
void loop() final {
flush_pending_queries();
}
void tear_down() final {
// Leaves scheduler_ in an invalid state, but we are closing anyway
scheduler_.for_each([&](Node &node) {
if (node.net_query.empty()) {
return;
}
node.net_query->set_error(Global::request_aborted_error());
});
}
void flush_pending_queries() {
while (true) {
auto o_task = scheduler_.start_next_task();
if (!o_task) {
break;
}
auto task = o_task.unwrap();
auto &node = *scheduler_.get_task_extra(task.task_id);
CHECK(!node.net_query.empty());
auto query = std::move(node.net_query);
vector<NetQueryRef> parents;
for (auto parent_id : task.parents) {
auto &parent_node = *scheduler_.get_task_extra(parent_id);
parents.push_back(parent_node.net_query_ref);
CHECK(!parent_node.net_query_ref.empty());
}
query->set_invoke_after(std::move(parents));
query->last_timeout_ = 0; // TODO: flood
VLOG(net_query) << "Send " << query;
query->debug("send to Td::send_with_callback");
G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, task.task_id));
}
}
};
ActorOwn<MultiSequenceDispatcherNew> MultiSequenceDispatcherNew::create(Slice name) {
return ActorOwn<MultiSequenceDispatcherNew>(create_actor<MultiSequenceDispatcherNewImpl>(name));
}
} // namespace td

View File

@ -12,6 +12,8 @@
#include "td/utils/common.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/Span.h"
#include <limits>
#include <unordered_map>
@ -46,7 +48,7 @@ class SequenceDispatcher final : public NetQueryCallback {
ActorShared<Parent> parent_;
size_t id_offset_ = 1;
std::vector<Data> data_;
vector<Data> data_;
size_t finish_i_ = 0; // skip state_ == State::Finish
size_t next_i_ = 0;
size_t last_sent_i_ = std::numeric_limits<size_t>::max();
@ -73,9 +75,12 @@ class SequenceDispatcher final : public NetQueryCallback {
void tear_down() final;
};
class MultiSequenceDispatcher final : public SequenceDispatcher::Parent {
class MultiSequenceDispatcherOld final : public SequenceDispatcher::Parent {
public:
void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback, uint64 sequence_id);
void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback, Span<uint64> chains);
static ActorOwn<MultiSequenceDispatcherOld> create(Slice name) {
return create_actor<MultiSequenceDispatcherOld>(name);
}
private:
struct Data {
@ -87,4 +92,14 @@ class MultiSequenceDispatcher final : public SequenceDispatcher::Parent {
void ready_to_close() final;
};
using ChainId = uint64;
using ChainIds = vector<ChainId>;
class MultiSequenceDispatcherNew : public NetQueryCallback {
public:
virtual void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback, Span<uint64> chains) = 0;
static ActorOwn<MultiSequenceDispatcherNew> create(Slice name);
};
using MultiSequenceDispatcher = MultiSequenceDispatcherOld;
} // namespace td

View File

@ -26,8 +26,6 @@
#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include <limits>
namespace td {
class GetSponsoredMessagesQuery final : public Td::ResultHandler {

View File

@ -0,0 +1,141 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/StickerFormat.h"
#include "td/utils/logging.h"
namespace td {
StickerFormat get_sticker_format(Slice mime_type) {
if (mime_type == "application/x-tgsticker") {
return StickerFormat::Tgs;
}
if (mime_type == "image/webp") {
return StickerFormat::Webp;
}
if (mime_type == "video/webm") {
return StickerFormat::Webm;
}
return StickerFormat::Unknown;
}
td_api::object_ptr<td_api::StickerType> get_sticker_type_object(
StickerFormat sticker_format, bool is_masks, td_api::object_ptr<td_api::maskPosition> mask_position) {
switch (sticker_format) {
case StickerFormat::Unknown:
LOG(ERROR) << "Have a sticker of unknown format";
return td_api::make_object<td_api::stickerTypeStatic>();
case StickerFormat::Webp:
if (is_masks) {
return td_api::make_object<td_api::stickerTypeMask>(std::move(mask_position));
}
return td_api::make_object<td_api::stickerTypeStatic>();
case StickerFormat::Tgs:
return td_api::make_object<td_api::stickerTypeAnimated>();
case StickerFormat::Webm:
return td_api::make_object<td_api::stickerTypeVideo>();
default:
UNREACHABLE();
return nullptr;
}
}
string get_sticker_format_mime_type(StickerFormat sticker_format) {
switch (sticker_format) {
case StickerFormat::Unknown:
case StickerFormat::Webp:
return "image/webp";
case StickerFormat::Tgs:
return "application/x-tgsticker";
case StickerFormat::Webm:
return "video/webm";
default:
UNREACHABLE();
return string();
}
}
Slice get_sticker_format_extension(StickerFormat sticker_format) {
switch (sticker_format) {
case StickerFormat::Unknown:
return Slice();
case StickerFormat::Webp:
return Slice(".webp");
case StickerFormat::Tgs:
return Slice(".tgs");
case StickerFormat::Webm:
return Slice(".webm");
default:
UNREACHABLE();
return Slice();
}
}
bool is_sticker_format_animated(StickerFormat sticker_format) {
switch (sticker_format) {
case StickerFormat::Unknown:
return false;
case StickerFormat::Webp:
return false;
case StickerFormat::Tgs:
return true;
case StickerFormat::Webm:
return true;
default:
UNREACHABLE();
return false;
}
}
bool is_sticker_format_vector(StickerFormat sticker_format) {
switch (sticker_format) {
case StickerFormat::Unknown:
return false;
case StickerFormat::Webp:
return false;
case StickerFormat::Tgs:
return true;
case StickerFormat::Webm:
return false;
default:
UNREACHABLE();
return false;
}
}
int64 get_max_sticker_file_size(StickerFormat sticker_format, bool for_thumbnail) {
switch (sticker_format) {
case StickerFormat::Unknown:
case StickerFormat::Webp:
return for_thumbnail ? (1 << 17) : (1 << 19);
case StickerFormat::Tgs:
return for_thumbnail ? (1 << 15) : (1 << 16);
case StickerFormat::Webm:
return for_thumbnail ? (1 << 15) : (1 << 18);
default:
UNREACHABLE();
return 0;
}
}
StringBuilder &operator<<(StringBuilder &string_builder, StickerFormat sticker_format) {
switch (sticker_format) {
case StickerFormat::Unknown:
return string_builder << "unknown";
case StickerFormat::Webp:
return string_builder << "WEBP";
case StickerFormat::Tgs:
return string_builder << "TGS";
case StickerFormat::Webm:
return string_builder << "WEBM";
default:
UNREACHABLE();
return string_builder;
}
}
} // namespace td

View File

@ -0,0 +1,37 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/td_api.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
namespace td {
// update store_sticker/store_sticker_set when this type changes
enum class StickerFormat : int32 { Unknown, Webp, Tgs, Webm };
StickerFormat get_sticker_format(Slice mime_type);
td_api::object_ptr<td_api::StickerType> get_sticker_type_object(StickerFormat sticker_format, bool is_masks,
td_api::object_ptr<td_api::maskPosition> mask_position);
string get_sticker_format_mime_type(StickerFormat sticker_format);
Slice get_sticker_format_extension(StickerFormat sticker_format);
bool is_sticker_format_animated(StickerFormat sticker_format);
bool is_sticker_format_vector(StickerFormat sticker_format);
int64 get_max_sticker_file_size(StickerFormat sticker_format, bool for_thumbnail);
StringBuilder &operator<<(StringBuilder &string_builder, StickerFormat sticker_format);
} // namespace td

View File

@ -69,6 +69,29 @@
namespace td {
class GetAvailableReactionsQuery final : public Td::ResultHandler {
public:
void send(int32 hash) {
send_query(G()->net_query_creator().create(telegram_api::messages_getAvailableReactions(hash)));
}
void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getAvailableReactions>(packet);
if (result_ptr.is_error()) {
return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetAvailableReactionsQuery: " << to_string(ptr);
td_->stickers_manager_->on_get_available_reactions(std::move(ptr));
}
void on_error(Status status) final {
LOG(INFO) << "Receive error for GetAvailableReactionsQuery: " << status;
td_->stickers_manager_->on_get_available_reactions(nullptr);
}
};
class GetAllStickersQuery final : public Td::ResultHandler {
bool is_masks_;
@ -974,24 +997,27 @@ class CreateNewStickerSetQuery final : public Td::ResultHandler {
}
void send(tl_object_ptr<telegram_api::InputUser> &&input_user, const string &title, const string &short_name,
bool is_masks, bool is_animated, vector<tl_object_ptr<telegram_api::inputStickerSetItem>> &&input_stickers,
const string &software) {
bool is_masks, StickerFormat sticker_format,
vector<tl_object_ptr<telegram_api::inputStickerSetItem>> &&input_stickers, const string &software) {
CHECK(input_user != nullptr);
int32 flags = 0;
if (is_masks) {
flags |= telegram_api::stickers_createStickerSet::MASKS_MASK;
}
if (is_animated) {
if (sticker_format == StickerFormat::Tgs) {
flags |= telegram_api::stickers_createStickerSet::ANIMATED_MASK;
}
if (sticker_format == StickerFormat::Webm) {
flags |= telegram_api::stickers_createStickerSet::VIDEOS_MASK;
}
if (!software.empty()) {
flags |= telegram_api::stickers_createStickerSet::SOFTWARE_MASK;
}
send_query(G()->net_query_creator().create(
telegram_api::stickers_createStickerSet(flags, false /*ignored*/, false /*ignored*/, std::move(input_user),
title, short_name, nullptr, std::move(input_stickers), software)));
send_query(G()->net_query_creator().create(telegram_api::stickers_createStickerSet(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_user), title, short_name,
nullptr, std::move(input_stickers), software)));
}
void on_result(BufferSlice packet) final {
@ -1286,6 +1312,8 @@ void StickersManager::init() {
}
send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object());
load_reactions();
on_update_dice_success_values();
on_update_emoji_sounds();
@ -1313,6 +1341,15 @@ void StickersManager::init() {
G()->shared_config().set_option_empty("animated_emoji_sticker_set_name"); // legacy
}
void StickersManager::reload_reactions() {
CHECK(!td_->auth_manager_->is_bot());
if (reactions_.are_being_reloaded_ || G()->close_flag()) {
return;
}
reactions_.are_being_reloaded_ = true;
td_->create_handler<GetAvailableReactionsQuery>()->send(reactions_.hash_);
}
StickersManager::SpecialStickerSet &StickersManager::add_special_sticker_set(const SpecialStickerSetType &type) {
auto &result = special_sticker_sets_[type];
if (result.type_.is_empty()) {
@ -1808,14 +1845,15 @@ tl_object_ptr<td_api::sticker> StickersManager::get_sticker_object(FileId file_i
int32 width = sticker->dimensions.width;
int32 height = sticker->dimensions.height;
double zoom = 1.0;
if (sticker->is_animated && (for_animated_emoji || for_clicked_animated_emoji)) {
if (is_sticker_format_vector(sticker->format) && (for_animated_emoji || for_clicked_animated_emoji)) {
zoom = for_clicked_animated_emoji ? 3 * animated_emoji_zoom_ : animated_emoji_zoom_;
width = static_cast<int32>(width * zoom + 0.5);
height = static_cast<int32>(height * zoom + 0.5);
}
return make_tl_object<td_api::sticker>(
sticker->set_id.get(), width, height, sticker->alt, sticker->is_animated, sticker->is_mask,
std::move(mask_position), get_sticker_minithumbnail(sticker->minithumbnail, sticker->set_id, document_id, zoom),
sticker->set_id.get(), width, height, sticker->alt,
get_sticker_type_object(sticker->format, sticker->is_mask, std::move(mask_position)),
get_sticker_minithumbnail(sticker->minithumbnail, sticker->set_id, document_id, zoom),
std::move(thumbnail_object), td_->file_manager_->get_file_object(file_id));
}
@ -1898,6 +1936,22 @@ int32 StickersManager::get_dice_success_animation_frame_number(const string &emo
return result.first == value ? result.second : std::numeric_limits<int32>::max();
}
PhotoFormat StickersManager::get_sticker_set_thumbnail_format(StickerFormat sticker_format) {
switch (sticker_format) {
case StickerFormat::Unknown:
return PhotoFormat::Webp;
case StickerFormat::Webp:
return PhotoFormat::Webp;
case StickerFormat::Tgs:
return PhotoFormat::Tgs;
case StickerFormat::Webm:
return PhotoFormat::Webm;
default:
UNREACHABLE();
return PhotoFormat::Webp;
}
}
tl_object_ptr<td_api::stickerSet> StickersManager::get_sticker_set_object(StickerSetId sticker_set_id) const {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
@ -1916,13 +1970,14 @@ tl_object_ptr<td_api::stickerSet> StickersManager::get_sticker_set_object(Sticke
}
emojis.push_back(make_tl_object<td_api::emojis>(std::move(sticker_emojis)));
}
auto thumbnail = get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail,
sticker_set->is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp);
auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set->sticker_format);
auto thumbnail = get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail, thumbnail_format);
return make_tl_object<td_api::stickerSet>(
sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail),
get_sticker_minithumbnail(sticker_set->minithumbnail, sticker_set->id, -2, 1.0),
sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official,
sticker_set->is_animated, sticker_set->is_masks, sticker_set->is_viewed, std::move(stickers), std::move(emojis));
get_sticker_type_object(sticker_set->sticker_format, sticker_set->is_masks, nullptr), sticker_set->is_viewed,
std::move(stickers), std::move(emojis));
}
tl_object_ptr<td_api::stickerSets> StickersManager::get_sticker_sets_object(int32 total_count,
@ -1962,13 +2017,13 @@ tl_object_ptr<td_api::stickerSetInfo> StickersManager::get_sticker_set_info_obje
}
}
auto thumbnail = get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail,
sticker_set->is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp);
auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set->sticker_format);
auto thumbnail = get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail, thumbnail_format);
return make_tl_object<td_api::stickerSetInfo>(
sticker_set->id.get(), sticker_set->title, sticker_set->short_name, std::move(thumbnail),
get_sticker_minithumbnail(sticker_set->minithumbnail, sticker_set->id, -3, 1.0),
sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official,
sticker_set->is_animated, sticker_set->is_masks, sticker_set->is_viewed,
get_sticker_type_object(sticker_set->sticker_format, sticker_set->is_masks, nullptr), sticker_set->is_viewed,
sticker_set->was_loaded ? narrow_cast<int32>(sticker_set->sticker_ids.size()) : sticker_set->sticker_count,
std::move(stickers));
}
@ -2109,8 +2164,8 @@ FileId StickersManager::on_get_sticker(unique_ptr<Sticker> new_sticker, bool rep
<< s->m_thumbnail << " to " << new_sticker->m_thumbnail;
s->m_thumbnail = std::move(new_sticker->m_thumbnail);
}
if (s->is_animated != new_sticker->is_animated && new_sticker->is_animated) {
s->is_animated = new_sticker->is_animated;
if (s->format != new_sticker->format && new_sticker->format != StickerFormat::Unknown) {
s->format = new_sticker->format;
}
if (s->is_mask != new_sticker->is_mask && new_sticker->is_mask) {
s->is_mask = new_sticker->is_mask;
@ -2142,8 +2197,11 @@ bool StickersManager::has_webp_thumbnail(const vector<tl_object_ptr<telegram_api
return true;
}
std::pair<int64, FileId> StickersManager::on_get_sticker_document(
tl_object_ptr<telegram_api::Document> &&document_ptr) {
std::pair<int64, FileId> StickersManager::on_get_sticker_document(tl_object_ptr<telegram_api::Document> &&document_ptr,
StickerFormat expected_format) {
if (document_ptr == nullptr) {
return {};
}
int32 document_constructor_id = document_ptr->get_id();
if (document_constructor_id == telegram_api::documentEmpty::ID) {
LOG(ERROR) << "Empty sticker document received";
@ -2162,6 +2220,11 @@ std::pair<int64, FileId> StickersManager::on_get_sticker_document(
tl_object_ptr<telegram_api::documentAttributeSticker> sticker;
for (auto &attribute : document->attributes_) {
switch (attribute->get_id()) {
case telegram_api::documentAttributeVideo::ID: {
auto video = move_tl_object_as<telegram_api::documentAttributeVideo>(attribute);
dimensions = get_dimensions(video->w_, video->h_, "sticker documentAttributeVideo");
break;
}
case telegram_api::documentAttributeImageSize::ID: {
auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
dimensions = get_dimensions(image_size->w_, image_size->h_, "sticker documentAttributeImageSize");
@ -2181,13 +2244,17 @@ std::pair<int64, FileId> StickersManager::on_get_sticker_document(
return {};
}
bool is_animated = document->mime_type_ == "application/x-tgsticker";
auto format = get_sticker_format(document->mime_type_);
if (format == StickerFormat::Unknown || (expected_format != StickerFormat::Unknown && format != expected_format)) {
LOG(ERROR) << "Expected sticker of the type " << expected_format << ", but received of the type " << format;
return {};
}
int64 document_id = document->id_;
FileId sticker_id =
td_->file_manager_->register_remote(FullRemoteFileLocation(FileType::Sticker, document_id, document->access_hash_,
dc_id, document->file_reference_.as_slice().str()),
FileLocationSource::FromServer, DialogId(), document->size_, 0,
PSTRING() << document_id << (is_animated ? ".tgs" : ".webp"));
PSTRING() << document_id << get_sticker_format_extension(format));
PhotoSize thumbnail;
string minithumbnail;
@ -2208,8 +2275,8 @@ std::pair<int64, FileId> StickersManager::on_get_sticker_document(
}
}
create_sticker(sticker_id, std::move(minithumbnail), std::move(thumbnail), dimensions, std::move(sticker),
is_animated, nullptr);
create_sticker(sticker_id, std::move(minithumbnail), std::move(thumbnail), dimensions, std::move(sticker), format,
nullptr);
return {document_id, sticker_id};
}
@ -2383,9 +2450,10 @@ void StickersManager::merge_stickers(FileId new_id, FileId old_id, bool can_dele
Sticker *new_ = new_it->second.get();
CHECK(new_ != nullptr);
if (old_->set_id == new_->set_id && (old_->alt != new_->alt || old_->set_id != new_->set_id ||
(!old_->is_animated && !new_->is_animated && old_->dimensions.width != 0 &&
old_->dimensions.height != 0 && old_->dimensions != new_->dimensions))) {
if (old_->set_id == new_->set_id &&
(old_->alt != new_->alt || old_->set_id != new_->set_id ||
(!is_sticker_format_vector(old_->format) && !is_sticker_format_vector(new_->format) &&
old_->dimensions.width != 0 && old_->dimensions.height != 0 && old_->dimensions != new_->dimensions))) {
LOG(ERROR) << "Sticker has changed: alt = (" << old_->alt << ", " << new_->alt << "), set_id = (" << old_->set_id
<< ", " << new_->set_id << "), dimensions = (" << old_->dimensions << ", " << new_->dimensions << ")";
}
@ -2523,9 +2591,9 @@ void StickersManager::add_sticker_thumbnail(Sticker *s, PhotoSize thumbnail) {
}
void StickersManager::create_sticker(FileId file_id, string minithumbnail, PhotoSize thumbnail, Dimensions dimensions,
tl_object_ptr<telegram_api::documentAttributeSticker> sticker, bool is_animated,
MultiPromiseActor *load_data_multipromise_ptr) {
if (is_animated && dimensions.width == 0) {
tl_object_ptr<telegram_api::documentAttributeSticker> sticker,
StickerFormat format, MultiPromiseActor *load_data_multipromise_ptr) {
if (is_sticker_format_vector(format) && dimensions.width == 0) {
dimensions.width = 512;
dimensions.height = 512;
}
@ -2553,7 +2621,7 @@ void StickersManager::create_sticker(FileId file_id, string minithumbnail, Photo
}
}
}
s->is_animated = is_animated;
s->format = format;
on_get_sticker(std::move(s), sticker != nullptr);
}
@ -2640,7 +2708,7 @@ SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id,
return SecretInputMedia{std::move(input_file),
make_tl_object<secret_api::decryptedMessageMediaDocument>(
std::move(thumbnail), sticker->s_thumbnail.dimensions.width,
sticker->s_thumbnail.dimensions.height, get_sticker_mime_type(sticker),
sticker->s_thumbnail.dimensions.height, get_sticker_format_mime_type(sticker->format),
narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
BufferSlice(encryption_key.iv_slice()), std::move(attributes), "")};
} else {
@ -2651,11 +2719,12 @@ SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id,
LOG(ERROR) << "Have a web sticker in " << sticker->set_id;
return {};
}
return SecretInputMedia{nullptr, make_tl_object<secret_api::decryptedMessageMediaExternalDocument>(
remote_location.get_id(), remote_location.get_access_hash(), 0 /*date*/,
get_sticker_mime_type(sticker), narrow_cast<int32>(file_view.size()),
make_tl_object<secret_api::photoSizeEmpty>("t"),
remote_location.get_dc_id().get_raw_id(), std::move(attributes))};
return SecretInputMedia{
nullptr, make_tl_object<secret_api::decryptedMessageMediaExternalDocument>(
remote_location.get_id(), remote_location.get_access_hash(), 0 /*date*/,
get_sticker_format_mime_type(sticker->format), narrow_cast<int32>(file_view.size()),
make_tl_object<secret_api::photoSizeEmpty>("t"), remote_location.get_dc_id().get_raw_id(),
std::move(attributes))};
}
}
@ -2694,12 +2763,14 @@ tl_object_ptr<telegram_api::InputMedia> StickersManager::get_input_media(
if (input_thumbnail != nullptr) {
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
auto mime_type = get_sticker_mime_type(s);
if (!s->is_animated && !s->set_id.is_valid()) {
auto mime_type = get_sticker_format_mime_type(s->format);
if (s->format == StickerFormat::Unknown && !s->set_id.is_valid()) {
auto suggested_path = file_view.suggested_path();
const PathView path_view(suggested_path);
if (path_view.extension() == "tgs") {
mime_type = "application/x-tgsticker";
} else if (path_view.extension() == "webm") {
mime_type = "video/webm";
}
}
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
@ -2721,7 +2792,8 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
bool is_installed = (set->flags_ & telegram_api::stickerSet::INSTALLED_DATE_MASK) != 0;
bool is_archived = set->archived_;
bool is_official = set->official_;
bool is_animated = set->animated_;
StickerFormat sticker_format =
set->videos_ ? StickerFormat::Webm : (set->animated_ ? StickerFormat::Tgs : StickerFormat::Webp);
bool is_masks = set->masks_;
PhotoSize thumbnail;
@ -2731,7 +2803,7 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
get_photo_size(td_->file_manager_.get(),
PhotoSizeSource::sticker_set_thumbnail(set_id.get(), s->access_hash, set->thumb_version_), 0, 0,
"", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb),
is_animated ? PhotoFormat::Tgs : PhotoFormat::Webp);
get_sticker_set_thumbnail_format(sticker_format));
if (photo_size.get_offset() == 0) {
if (!thumbnail.file_id.is_valid()) {
thumbnail = std::move(photo_size.get<0>());
@ -2754,7 +2826,7 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
s->sticker_count = set->count_;
s->hash = set->hash_;
s->is_official = is_official;
s->is_animated = is_animated;
s->sticker_format = sticker_format;
s->is_masks = is_masks;
s->is_changed = true;
} else {
@ -2819,10 +2891,10 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::sti
s->is_official = is_official;
s->is_changed = true;
}
if (s->is_animated != is_animated) {
LOG(ERROR) << "Animated type of " << set_id << "/" << s->short_name << " has changed from " << s->is_animated
<< " to " << is_animated << " from " << source;
s->is_animated = is_animated;
if (s->sticker_format != sticker_format) {
LOG(ERROR) << "Format of stickers in " << set_id << "/" << s->short_name << " has changed from "
<< s->sticker_format << " to " << sticker_format << " from " << source;
s->sticker_format = sticker_format;
s->is_changed = true;
}
LOG_IF(ERROR, s->is_masks != is_masks) << "Masks type of " << set_id << "/" << s->short_name << " has changed from "
@ -2858,7 +2930,7 @@ StickerSetId StickersManager::on_get_sticker_set_covered(tl_object_ptr<telegram_
auto &sticker_ids = sticker_set->sticker_ids;
auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_)).second;
auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_), sticker_set->sticker_format).second;
if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) {
sticker_ids.push_back(sticker_id);
sticker_set->is_changed = true;
@ -2882,7 +2954,7 @@ StickerSetId StickersManager::on_get_sticker_set_covered(tl_object_ptr<telegram_
auto &sticker_ids = sticker_set->sticker_ids;
for (auto &cover : multicovered_set->covers_) {
auto sticker_id = on_get_sticker_document(std::move(cover)).second;
auto sticker_id = on_get_sticker_document(std::move(cover), sticker_set->sticker_format).second;
if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) {
sticker_ids.push_back(sticker_id);
sticker_set->is_changed = true;
@ -2951,7 +3023,7 @@ StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_s
s->sticker_ids.clear();
bool is_bot = td_->auth_manager_->is_bot();
for (auto &document_ptr : documents) {
auto sticker_id = on_get_sticker_document(std::move(document_ptr));
auto sticker_id = on_get_sticker_document(std::move(document_ptr), s->sticker_format);
if (!sticker_id.second.is_valid()) {
continue;
}
@ -3082,6 +3154,106 @@ void StickersManager::on_get_special_sticker_set(const SpecialStickerSetType &ty
on_load_special_sticker_set(type, Status::OK());
}
td_api::object_ptr<td_api::updateReactions> StickersManager::get_update_reactions_object() const {
auto reactions = transform(reactions_.reactions_, [this](const Reaction &reaction) {
return td_api::make_object<td_api::reaction>(
reaction.reaction_, reaction.title_, reaction.is_active_, get_sticker_object(reaction.static_icon_),
get_sticker_object(reaction.appear_animation_), get_sticker_object(reaction.select_animation_),
get_sticker_object(reaction.activate_animation_), get_sticker_object(reaction.effect_animation_),
get_sticker_object(reaction.around_animation_), get_sticker_object(reaction.center_animation_));
});
return td_api::make_object<td_api::updateReactions>(std::move(reactions));
}
void StickersManager::save_reactions() {
LOG(INFO) << "Save available reactions";
G()->td_db()->get_binlog_pmc()->set("reactions", log_event_store(reactions_).as_slice().str());
}
void StickersManager::load_reactions() {
string reactions = G()->td_db()->get_binlog_pmc()->get("reactions");
if (reactions.empty()) {
return reload_reactions();
}
auto status = log_event_parse(reactions_, reactions);
if (status.is_error()) {
LOG(ERROR) << "Can't load available reactions: " << status;
return reload_reactions();
}
LOG(INFO) << "Successfully loaded " << reactions_.reactions_.size() << " available reactions";
send_closure(G()->td(), &Td::send_update, get_update_reactions_object());
update_active_reactions();
}
void StickersManager::update_active_reactions() {
vector<string> active_reactions;
for (auto &reaction : reactions_.reactions_) {
if (reaction.is_active_) {
active_reactions.push_back(reaction.reaction_);
}
}
td_->messages_manager_->set_active_reactions(std::move(active_reactions));
}
void StickersManager::on_get_available_reactions(
tl_object_ptr<telegram_api::messages_AvailableReactions> &&available_reactions_ptr) {
CHECK(reactions_.are_being_reloaded_);
reactions_.are_being_reloaded_ = false;
if (available_reactions_ptr == nullptr) {
// failed to get available reactions
return;
}
int32 constructor_id = available_reactions_ptr->get_id();
if (constructor_id == telegram_api::messages_availableReactionsNotModified::ID) {
LOG(INFO) << "Available reactions are not modified";
return;
}
CHECK(constructor_id == telegram_api::messages_availableReactions::ID);
auto available_reactions = move_tl_object_as<telegram_api::messages_availableReactions>(available_reactions_ptr);
vector<Reaction> new_reactions;
for (auto &available_reaction : available_reactions->reactions_) {
Reaction reaction;
reaction.is_active_ = !available_reaction->inactive_;
reaction.reaction_ = std::move(available_reaction->reaction_);
reaction.title_ = std::move(available_reaction->title_);
reaction.static_icon_ =
on_get_sticker_document(std::move(available_reaction->static_icon_), StickerFormat::Webp).second;
reaction.appear_animation_ =
on_get_sticker_document(std::move(available_reaction->appear_animation_), StickerFormat::Tgs).second;
reaction.select_animation_ =
on_get_sticker_document(std::move(available_reaction->select_animation_), StickerFormat::Tgs).second;
reaction.activate_animation_ =
on_get_sticker_document(std::move(available_reaction->activate_animation_), StickerFormat::Tgs).second;
reaction.effect_animation_ =
on_get_sticker_document(std::move(available_reaction->effect_animation_), StickerFormat::Tgs).second;
reaction.around_animation_ =
on_get_sticker_document(std::move(available_reaction->around_animation_), StickerFormat::Tgs).second;
reaction.center_animation_ =
on_get_sticker_document(std::move(available_reaction->center_icon_), StickerFormat::Tgs).second;
if (!reaction.static_icon_.is_valid() || !reaction.appear_animation_.is_valid() ||
!reaction.select_animation_.is_valid() || !reaction.activate_animation_.is_valid() ||
!reaction.effect_animation_.is_valid()) {
LOG(ERROR) << "Receive invalid reaction " << reaction.reaction_;
continue;
}
new_reactions.push_back(std::move(reaction));
}
reactions_.reactions_ = std::move(new_reactions);
reactions_.hash_ = available_reactions->hash_;
send_closure(G()->td(), &Td::send_update, get_update_reactions_object());
save_reactions();
update_active_reactions();
}
void StickersManager::on_get_installed_sticker_sets(bool is_masks,
tl_object_ptr<telegram_api::messages_AllStickers> &&stickers_ptr) {
next_installed_sticker_sets_load_time_[is_masks] = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
@ -3147,13 +3319,11 @@ void StickersManager::on_get_installed_sticker_sets(bool is_masks,
on_load_installed_sticker_sets_finished(is_masks, std::move(installed_sticker_set_ids));
if (installed_sticker_sets_hash_[is_masks] != stickers->hash_) {
LOG(ERROR) << "Sticker sets hash mismatch: server hash list = " << format::as_array(debug_hashes)
<< ", client hash list = "
<< format::as_array(
transform(installed_sticker_set_ids_[is_masks],
[this](StickerSetId sticker_set_id) { return get_sticker_set(sticker_set_id)->hash; }))
<< ", server sticker set list = " << format::as_array(debug_sticker_set_ids)
<< ", client sticker set list = " << format::as_array(installed_sticker_set_ids_[is_masks])
LOG(ERROR) << "Sticker sets hash mismatch: server hash list = " << debug_hashes << ", client hash list = "
<< transform(installed_sticker_set_ids_[is_masks],
[this](StickerSetId sticker_set_id) { return get_sticker_set(sticker_set_id)->hash; })
<< ", server sticker set list = " << debug_sticker_set_ids
<< ", client sticker set list = " << installed_sticker_set_ids_[is_masks]
<< ", server hash = " << stickers->hash_ << ", client hash = " << installed_sticker_sets_hash_[is_masks];
}
}
@ -3225,7 +3395,7 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
const Sticker *lhs_s = get_sticker(lhs);
const Sticker *rhs_s = get_sticker(rhs);
CHECK(lhs_s != nullptr && rhs_s != nullptr);
return lhs_s->is_animated && !rhs_s->is_animated;
return is_sticker_format_animated(lhs_s->format) && !is_sticker_format_animated(rhs_s->format);
};
// std::stable_sort(prepend_sticker_ids.begin(), prepend_sticker_ids.begin() + recent_sticker_ids_[0].size(),
// prefer_animated);
@ -3292,8 +3462,9 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
}
}
std::stable_sort(
examined_sticker_sets.begin(), examined_sticker_sets.end(),
[](const StickerSet *lhs, const StickerSet *rhs) { return lhs->is_animated && !rhs->is_animated; });
examined_sticker_sets.begin(), examined_sticker_sets.end(), [](const StickerSet *lhs, const StickerSet *rhs) {
return is_sticker_format_animated(lhs->sticker_format) && !is_sticker_format_animated(rhs->sticker_format);
});
for (auto sticker_set : examined_sticker_sets) {
auto it = sticker_set->emoji_stickers_map_.find(emoji);
if (it != sticker_set->emoji_stickers_map_.end()) {
@ -3430,7 +3601,7 @@ void StickersManager::on_find_stickers_success(const string &emoji,
found_stickers.sticker_ids_.clear();
for (auto &sticker : received_stickers->stickers_) {
FileId sticker_id = on_get_sticker_document(std::move(sticker)).second;
FileId sticker_id = on_get_sticker_document(std::move(sticker), StickerFormat::Unknown).second;
if (sticker_id.is_valid()) {
found_stickers.sticker_ids_.push_back(sticker_id);
}
@ -4723,6 +4894,15 @@ void StickersManager::send_update_animated_emoji_clicked(FullMessageId full_mess
dialog_id.get(), full_message_id.get_message_id().get(), get_sticker_object(sticker_id, false, true)));
}
bool StickersManager::is_active_reaction(const string &reaction) const {
for (auto &supported_reaction : reactions_.reactions_) {
if (supported_reaction.reaction_ == reaction) {
return supported_reaction.is_active_;
}
}
return false;
}
void StickersManager::view_featured_sticker_sets(const vector<StickerSetId> &sticker_set_ids) {
for (auto sticker_set_id : sticker_set_ids) {
auto set = get_sticker_set(sticker_set_id);
@ -5373,52 +5553,53 @@ void StickersManager::reorder_installed_sticker_sets(bool is_masks, const vector
promise.set_value(Unit());
}
string &StickersManager::get_input_sticker_emojis(td_api::InputSticker *sticker) {
CHECK(sticker != nullptr);
auto constructor_id = sticker->get_id();
if (constructor_id == td_api::inputStickerStatic::ID) {
return static_cast<td_api::inputStickerStatic *>(sticker)->emojis_;
}
CHECK(constructor_id == td_api::inputStickerAnimated::ID);
return static_cast<td_api::inputStickerAnimated *>(sticker)->emojis_;
}
Result<std::tuple<FileId, bool, bool, bool>> StickersManager::prepare_input_sticker(td_api::InputSticker *sticker) {
Result<std::tuple<FileId, bool, bool, StickerFormat>> StickersManager::prepare_input_sticker(
td_api::inputSticker *sticker) {
if (sticker == nullptr) {
return Status::Error(400, "Input sticker must be non-empty");
}
if (!clean_input_string(get_input_sticker_emojis(sticker))) {
if (!clean_input_string(sticker->emojis_)) {
return Status::Error(400, "Emojis must be encoded in UTF-8");
}
switch (sticker->get_id()) {
case td_api::inputStickerStatic::ID:
return prepare_input_file(static_cast<td_api::inputStickerStatic *>(sticker)->sticker_, false, false);
case td_api::inputStickerAnimated::ID:
return prepare_input_file(static_cast<td_api::inputStickerAnimated *>(sticker)->sticker_, true, false);
if (sticker->type_ == nullptr) {
return Status::Error(400, "Sticker type must be non-empty");
}
switch (sticker->type_->get_id()) {
case td_api::stickerTypeStatic::ID:
return prepare_input_file(sticker->sticker_, StickerFormat::Webp, false);
case td_api::stickerTypeAnimated::ID:
return prepare_input_file(sticker->sticker_, StickerFormat::Tgs, false);
case td_api::stickerTypeVideo::ID:
return prepare_input_file(sticker->sticker_, StickerFormat::Webm, false);
case td_api::stickerTypeMask::ID:
return prepare_input_file(sticker->sticker_, StickerFormat::Webp, false);
default:
UNREACHABLE();
return {};
}
}
Result<std::tuple<FileId, bool, bool, bool>> StickersManager::prepare_input_file(
const tl_object_ptr<td_api::InputFile> &input_file, bool is_animated, bool for_thumbnail) {
auto r_file_id = td_->file_manager_->get_input_file_id(is_animated ? FileType::Sticker : FileType::Document,
input_file, DialogId(), for_thumbnail, false);
Result<std::tuple<FileId, bool, bool, StickerFormat>> StickersManager::prepare_input_file(
const tl_object_ptr<td_api::InputFile> &input_file, StickerFormat format, bool for_thumbnail) {
auto file_type = format == StickerFormat::Tgs ? FileType::Sticker : FileType::Document;
auto r_file_id = td_->file_manager_->get_input_file_id(file_type, input_file, DialogId(), for_thumbnail, false);
if (r_file_id.is_error()) {
return Status::Error(400, r_file_id.error().message());
}
auto file_id = r_file_id.move_as_ok();
if (file_id.empty()) {
return std::make_tuple(FileId(), false, false, false);
return std::make_tuple(FileId(), false, false, StickerFormat::Unknown);
}
if (is_animated) {
if (format == StickerFormat::Tgs) {
int32 width = for_thumbnail ? 100 : 512;
create_sticker(file_id, string(), PhotoSize(), get_dimensions(width, width, "prepare_input_file"), nullptr, true,
create_sticker(file_id, string(), PhotoSize(), get_dimensions(width, width, "prepare_input_file"), nullptr, format,
nullptr);
} else if (format == StickerFormat::Webm) {
td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.webm", "video/webm", false);
} else {
td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.png", "image/png", false);
}
@ -5439,23 +5620,17 @@ Result<std::tuple<FileId, bool, bool, bool>> StickersManager::prepare_input_file
if (file_view.has_url()) {
is_url = true;
} else {
auto max_file_size = [&] {
if (for_thumbnail) {
return is_animated ? MAX_ANIMATED_THUMBNAIL_FILE_SIZE : MAX_THUMBNAIL_FILE_SIZE;
} else {
return is_animated ? MAX_ANIMATED_STICKER_FILE_SIZE : MAX_STICKER_FILE_SIZE;
}
}();
if (file_view.has_local_location() && file_view.expected_size() > max_file_size) {
if (file_view.has_local_location() &&
file_view.expected_size() > get_max_sticker_file_size(format, for_thumbnail)) {
return Status::Error(400, "File is too big");
}
is_local = true;
}
}
return std::make_tuple(file_id, is_url, is_local, is_animated);
return std::make_tuple(file_id, is_url, is_local, format);
}
FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr<td_api::InputSticker> &&sticker,
FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr<td_api::inputSticker> &&sticker,
Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
if (!is_bot) {
@ -5488,7 +5663,7 @@ FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr<td_api
return file_id;
}
tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_sticker(td_api::InputSticker *sticker,
tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_sticker(const td_api::inputSticker *sticker,
FileId file_id) const {
CHECK(sticker != nullptr);
FileView file_view = td_->file_manager_->get_file_view(file_id);
@ -5496,11 +5671,12 @@ tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_stic
auto input_document = file_view.main_remote_location().as_input_document();
tl_object_ptr<telegram_api::maskCoords> mask_coords;
if (sticker->get_id() == td_api::inputStickerStatic::ID) {
auto mask_position = static_cast<td_api::inputStickerStatic *>(sticker)->mask_position_.get();
if (sticker->type_->get_id() == td_api::stickerTypeMask::ID) {
auto sticker_format = static_cast<const td_api::stickerTypeMask *>(sticker->type_.get());
auto mask_position = sticker_format->mask_position_.get();
if (mask_position != nullptr && mask_position->point_ != nullptr) {
auto point = [mask_point = std::move(mask_position->point_)] {
switch (mask_point->get_id()) {
auto point = [mask_point_id = mask_position->point_->get_id()] {
switch (mask_point_id) {
case td_api::maskPointForehead::ID:
return 0;
case td_api::maskPointEyes::ID:
@ -5524,8 +5700,8 @@ tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_stic
flags |= telegram_api::inputStickerSetItem::MASK_COORDS_MASK;
}
return make_tl_object<telegram_api::inputStickerSetItem>(flags, std::move(input_document),
get_input_sticker_emojis(sticker), std::move(mask_coords));
return make_tl_object<telegram_api::inputStickerSetItem>(flags, std::move(input_document), sticker->emojis_,
std::move(mask_coords));
}
void StickersManager::get_suggested_sticker_set_name(string title, Promise<string> &&promise) {
@ -5575,8 +5751,8 @@ td_api::object_ptr<td_api::CheckStickerSetNameResult> StickersManager::get_check
}
}
void StickersManager::create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks,
vector<tl_object_ptr<td_api::InputSticker>> &&stickers, string software,
void StickersManager::create_new_sticker_set(UserId user_id, string &title, string &short_name,
vector<tl_object_ptr<td_api::inputSticker>> &&stickers, string software,
Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
if (!is_bot) {
@ -5603,7 +5779,8 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri
file_ids.reserve(stickers.size());
vector<FileId> local_file_ids;
vector<FileId> url_file_ids;
size_t animated_sticker_count = 0;
std::unordered_set<int32> sticker_formats;
StickerFormat sticker_format = StickerFormat::Unknown;
for (auto &sticker : stickers) {
auto r_file_id = prepare_input_sticker(sticker.get());
if (r_file_id.is_error()) {
@ -5612,13 +5789,11 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri
auto file_id = std::get<0>(r_file_id.ok());
auto is_url = std::get<1>(r_file_id.ok());
auto is_local = std::get<2>(r_file_id.ok());
auto is_animated = std::get<3>(r_file_id.ok());
if (is_animated) {
animated_sticker_count++;
if (is_url) {
return promise.set_error(Status::Error(400, "Animated stickers can't be uploaded by URL"));
}
sticker_format = std::get<3>(r_file_id.ok());
if (is_sticker_format_animated(sticker_format) && is_url) {
return promise.set_error(Status::Error(400, "Animated stickers can't be uploaded by URL"));
}
sticker_formats.insert(sticker->type_->get_id());
file_ids.push_back(file_id);
if (is_url) {
@ -5627,17 +5802,15 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri
local_file_ids.push_back(file_id);
}
}
if (animated_sticker_count != stickers.size() && animated_sticker_count != 0) {
return promise.set_error(Status::Error(400, "All stickers must be either animated or static"));
if (sticker_formats.size() != 1) {
return promise.set_error(Status::Error(400, "All stickers must be of the same format"));
}
bool is_animated = animated_sticker_count == stickers.size();
auto pending_new_sticker_set = make_unique<PendingNewStickerSet>();
pending_new_sticker_set->user_id = user_id;
pending_new_sticker_set->title = std::move(title);
pending_new_sticker_set->short_name = short_name;
pending_new_sticker_set->is_masks = is_masks;
pending_new_sticker_set->is_animated = is_animated;
pending_new_sticker_set->sticker_format = sticker_format;
pending_new_sticker_set->file_ids = std::move(file_ids);
pending_new_sticker_set->stickers = std::move(stickers);
pending_new_sticker_set->software = std::move(software);
@ -5728,11 +5901,12 @@ void StickersManager::do_upload_sticker_file(UserId user_id, FileId file_id,
}
FileView file_view = td_->file_manager_->get_file_view(file_id);
bool is_animated = file_view.get_type() == FileType::Sticker;
FileType file_type = file_view.get_type();
bool had_input_file = input_file != nullptr;
auto input_media = is_animated ? get_input_media(file_id, std::move(input_file), nullptr, string())
: td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr);
auto input_media = file_type == FileType::Sticker
? get_input_media(file_id, std::move(input_file), nullptr, string())
: td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr);
CHECK(input_media != nullptr);
if (had_input_file && !FileManager::extract_was_uploaded(input_media)) {
// if we had InputFile, but has failed to use it, then we need to immediately cancel file upload
@ -5761,8 +5935,8 @@ void StickersManager::on_uploaded_sticker_file(FileId file_id, tl_object_ptr<tel
CHECK(document_id == telegram_api::document::ID);
FileView file_view = td_->file_manager_->get_file_view(file_id);
bool is_animated = file_view.get_type() == FileType::Sticker;
auto expected_document_type = is_animated ? Document::Type::Sticker : Document::Type::General;
FileType file_type = file_view.get_type();
auto expected_document_type = file_type == FileType::Sticker ? Document::Type::Sticker : Document::Type::General;
auto parsed_document = td_->documents_manager_->on_get_document(
move_tl_object_as<telegram_api::document>(document_ptr), DialogId(), nullptr);
@ -5771,7 +5945,7 @@ void StickersManager::on_uploaded_sticker_file(FileId file_id, tl_object_ptr<tel
}
if (parsed_document.file_id != file_id) {
if (is_animated) {
if (file_type == FileType::Sticker) {
merge_stickers(parsed_document.file_id, file_id, false);
} else {
// must not delete the old document, because the file_id could be used for simultaneous URL uploads
@ -5803,8 +5977,8 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result<Unit> res
auto &promise = pending_new_sticker_set->promise;
TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(pending_new_sticker_set->user_id));
bool is_masks = pending_new_sticker_set->is_masks;
bool is_animated = pending_new_sticker_set->is_animated;
bool is_masks = pending_new_sticker_set->stickers[0]->type_->get_id() == td_api::stickerTypeMask::ID;
StickerFormat sticker_format = pending_new_sticker_set->sticker_format;
auto sticker_count = pending_new_sticker_set->stickers.size();
vector<tl_object_ptr<telegram_api::inputStickerSetItem>> input_stickers;
@ -5816,11 +5990,11 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result<Unit> res
td_->create_handler<CreateNewStickerSetQuery>(std::move(pending_new_sticker_set->promise))
->send(std::move(input_user), pending_new_sticker_set->title, pending_new_sticker_set->short_name, is_masks,
is_animated, std::move(input_stickers), pending_new_sticker_set->software);
sticker_format, std::move(input_stickers), pending_new_sticker_set->software);
}
void StickersManager::add_sticker_to_set(UserId user_id, string &short_name,
tl_object_ptr<td_api::InputSticker> &&sticker, Promise<Unit> &&promise) {
tl_object_ptr<td_api::inputSticker> &&sticker, Promise<Unit> &&promise) {
TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH);
@ -5919,7 +6093,7 @@ void StickersManager::do_set_sticker_set_thumbnail(UserId user_id, string short_
return promise.set_error(Status::Error(400, "Sticker set not found"));
}
auto r_file_id = prepare_input_file(thumbnail, sticker_set->is_animated, true);
auto r_file_id = prepare_input_file(thumbnail, sticker_set->sticker_format, true);
if (r_file_id.is_error()) {
return promise.set_error(r_file_id.move_as_error());
}
@ -6258,7 +6432,7 @@ void StickersManager::on_get_recent_stickers(bool is_repair, bool is_attached,
vector<FileId> recent_sticker_ids;
recent_sticker_ids.reserve(stickers->stickers_.size());
for (auto &document_ptr : stickers->stickers_) {
auto sticker_id = on_get_sticker_document(std::move(document_ptr)).second;
auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown).second;
if (!sticker_id.is_valid()) {
continue;
}
@ -6663,7 +6837,7 @@ void StickersManager::on_get_favorite_stickers(
vector<FileId> favorite_sticker_ids;
favorite_sticker_ids.reserve(favorite_stickers->stickers_.size());
for (auto &document_ptr : favorite_stickers->stickers_) {
auto sticker_id = on_get_sticker_document(std::move(document_ptr)).second;
auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown).second;
if (!sticker_id.is_valid()) {
continue;
}
@ -6924,10 +7098,6 @@ vector<string> StickersManager::get_sticker_emojis(const tl_object_ptr<td_api::I
return it->second;
}
string StickersManager::get_sticker_mime_type(const Sticker *s) {
return s->is_animated ? "application/x-tgsticker" : "image/webp";
}
string StickersManager::get_emoji_language_code_version_database_key(const string &language_code) {
return PSTRING() << "emojiv$" << language_code;
}
@ -7417,6 +7587,7 @@ void StickersManager::after_get_difference() {
if (td_->auth_manager_->is_bot()) {
return;
}
reload_reactions();
if (td_->is_online()) {
get_installed_sticker_sets(false, Auto());
get_installed_sticker_sets(true, Auto());
@ -7437,6 +7608,9 @@ void StickersManager::get_current_state(vector<td_api::object_ptr<td_api::Update
return;
}
if (!reactions_.reactions_.empty()) {
updates.push_back(get_update_reactions_object());
}
for (int is_masks = 0; is_masks < 2; is_masks++) {
if (are_installed_sticker_sets_loaded_[is_masks]) {
updates.push_back(get_update_installed_sticker_sets_object(is_masks));

View File

@ -13,6 +13,7 @@
#include "td/telegram/Photo.h"
#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/SpecialStickerSetType.h"
#include "td/telegram/StickerFormat.h"
#include "td/telegram/StickerSetId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
@ -90,8 +91,10 @@ class StickersManager final : public Actor {
Status on_animated_emoji_message_clicked(Slice emoji, FullMessageId full_message_id, string data);
bool is_active_reaction(const string &reaction) const;
void create_sticker(FileId file_id, string minithumbnail, PhotoSize thumbnail, Dimensions dimensions,
tl_object_ptr<telegram_api::documentAttributeSticker> sticker, bool is_animated,
tl_object_ptr<telegram_api::documentAttributeSticker> sticker, StickerFormat sticker_format,
MultiPromiseActor *load_data_multipromise_ptr);
bool has_input_media(FileId sticker_file_id, bool is_secret) const;
@ -130,6 +133,8 @@ class StickersManager final : public Actor {
void view_featured_sticker_sets(const vector<StickerSetId> &sticker_set_ids);
void on_get_available_reactions(tl_object_ptr<telegram_api::messages_AvailableReactions> &&available_reactions_ptr);
void on_get_installed_sticker_sets(bool is_masks, tl_object_ptr<telegram_api::messages_AllStickers> &&stickers_ptr);
void on_get_installed_sticker_sets_failed(bool is_masks, Status error);
@ -190,7 +195,7 @@ class StickersManager final : public Actor {
void reorder_installed_sticker_sets(bool is_masks, const vector<StickerSetId> &sticker_set_ids,
Promise<Unit> &&promise);
FileId upload_sticker_file(UserId user_id, tl_object_ptr<td_api::InputSticker> &&sticker, Promise<Unit> &&promise);
FileId upload_sticker_file(UserId user_id, tl_object_ptr<td_api::inputSticker> &&sticker, Promise<Unit> &&promise);
void get_suggested_sticker_set_name(string title, Promise<string> &&promise);
@ -200,11 +205,11 @@ class StickersManager final : public Actor {
static td_api::object_ptr<td_api::CheckStickerSetNameResult> get_check_sticker_set_name_result_object(
CheckStickerSetNameResult result);
void create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks,
vector<tl_object_ptr<td_api::InputSticker>> &&stickers, string software,
void create_new_sticker_set(UserId user_id, string &title, string &short_name,
vector<tl_object_ptr<td_api::inputSticker>> &&stickers, string software,
Promise<Unit> &&promise);
void add_sticker_to_set(UserId user_id, string &short_name, tl_object_ptr<td_api::InputSticker> &&sticker,
void add_sticker_to_set(UserId user_id, string &short_name, tl_object_ptr<td_api::inputSticker> &&sticker,
Promise<Unit> &&promise);
void set_sticker_set_thumbnail(UserId user_id, string &short_name, tl_object_ptr<td_api::InputFile> &&thumbnail,
@ -328,13 +333,9 @@ class StickersManager final : public Actor {
static constexpr int32 MAX_FEATURED_STICKER_SET_VIEW_DELAY = 5;
static constexpr int32 OLD_FEATURED_STICKER_SET_SLICE_SIZE = 20;
static constexpr int32 MAX_FOUND_STICKERS = 100; // server side limit
static constexpr int64 MAX_STICKER_FILE_SIZE = 1 << 19; // server side limit
static constexpr int64 MAX_THUMBNAIL_FILE_SIZE = 1 << 17; // server side limit
static constexpr int64 MAX_ANIMATED_STICKER_FILE_SIZE = 1 << 16; // server side limit
static constexpr int64 MAX_ANIMATED_THUMBNAIL_FILE_SIZE = 1 << 15; // server side limit
static constexpr size_t MAX_STICKER_SET_TITLE_LENGTH = 64; // server side limit
static constexpr size_t MAX_STICKER_SET_SHORT_NAME_LENGTH = 64; // server side limit
static constexpr int32 MAX_FOUND_STICKERS = 100; // server side limit
static constexpr size_t MAX_STICKER_SET_TITLE_LENGTH = 64; // server side limit
static constexpr size_t MAX_STICKER_SET_SHORT_NAME_LENGTH = 64; // server side limit
static constexpr int32 EMOJI_KEYWORDS_UPDATE_DELAY = 3600;
static constexpr double MIN_ANIMATED_EMOJI_CLICK_DELAY = 0.2;
@ -348,7 +349,7 @@ class StickersManager final : public Actor {
PhotoSize s_thumbnail;
PhotoSize m_thumbnail;
FileId file_id;
bool is_animated = false;
StickerFormat format = StickerFormat::Unknown;
bool is_mask = false;
int32 point = -1;
double x_shift = 0;
@ -366,6 +367,7 @@ class StickersManager final : public Actor {
int64 access_hash = 0;
string title;
string short_name;
StickerFormat sticker_format = StickerFormat::Unknown;
int32 sticker_count = 0;
int32 hash = 0;
int32 expires_at = 0;
@ -380,7 +382,6 @@ class StickersManager final : public Actor {
bool is_installed = false;
bool is_archived = false;
bool is_official = false;
bool is_animated = false;
bool is_masks = false;
bool is_viewed = true;
bool is_thumbnail_reloaded = false;
@ -398,10 +399,9 @@ class StickersManager final : public Actor {
UserId user_id;
string title;
string short_name;
bool is_masks = false;
bool is_animated = false;
StickerFormat sticker_format = StickerFormat::Unknown;
vector<FileId> file_ids;
vector<tl_object_ptr<td_api::InputSticker>> stickers;
vector<tl_object_ptr<td_api::inputSticker>> stickers;
string software;
Promise<> promise;
};
@ -409,7 +409,7 @@ class StickersManager final : public Actor {
struct PendingAddStickerToSet {
string short_name;
FileId file_id;
tl_object_ptr<td_api::InputSticker> sticker;
tl_object_ptr<td_api::inputSticker> sticker;
Promise<> promise;
};
@ -441,6 +441,37 @@ class StickersManager final : public Actor {
bool is_being_reloaded_ = false;
};
struct Reaction {
string reaction_;
string title_;
bool is_active_ = false;
FileId static_icon_;
FileId appear_animation_;
FileId select_animation_;
FileId activate_animation_;
FileId effect_animation_;
FileId around_animation_;
FileId center_animation_;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
struct Reactions {
int32 hash_ = 0;
bool are_being_reloaded_ = false;
vector<Reaction> reactions_;
template <class StorerT>
void store(StorerT &storer) const;
template <class ParserT>
void parse(ParserT &parser);
};
class StickerListLogEvent;
class StickerSetListLogEvent;
@ -465,7 +496,10 @@ class StickersManager final : public Actor {
StickerSet *add_sticker_set(StickerSetId sticker_set_id, int64 access_hash);
std::pair<int64, FileId> on_get_sticker_document(tl_object_ptr<telegram_api::Document> &&document_ptr);
static PhotoFormat get_sticker_set_thumbnail_format(StickerFormat sticker_format);
std::pair<int64, FileId> on_get_sticker_document(tl_object_ptr<telegram_api::Document> &&document_ptr,
StickerFormat expected_format);
static tl_object_ptr<telegram_api::InputStickerSet> get_input_sticker_set(const StickerSet *set);
@ -586,14 +620,12 @@ class StickersManager final : public Actor {
template <class ParserT>
void parse_sticker_set(StickerSet *sticker_set, ParserT &parser);
static string &get_input_sticker_emojis(td_api::InputSticker *sticker);
Result<std::tuple<FileId, bool, bool, StickerFormat>> prepare_input_file(
const tl_object_ptr<td_api::InputFile> &input_file, StickerFormat format, bool for_thumbnail);
Result<std::tuple<FileId, bool, bool, bool>> prepare_input_file(const tl_object_ptr<td_api::InputFile> &input_file,
bool is_animated, bool for_thumbnail);
Result<std::tuple<FileId, bool, bool, StickerFormat>> prepare_input_sticker(td_api::inputSticker *sticker);
Result<std::tuple<FileId, bool, bool, bool>> prepare_input_sticker(td_api::InputSticker *sticker);
tl_object_ptr<telegram_api::inputStickerSetItem> get_input_sticker(td_api::InputSticker *sticker,
tl_object_ptr<telegram_api::inputStickerSetItem> get_input_sticker(const td_api::inputSticker *sticker,
FileId file_id) const;
void upload_sticker_file(UserId user_id, FileId file_id, Promise<Unit> &&promise);
@ -657,6 +689,16 @@ class StickersManager final : public Actor {
void tear_down() final;
void save_reactions();
void load_reactions();
void reload_reactions();
void update_active_reactions();
td_api::object_ptr<td_api::updateReactions> get_update_reactions_object() const;
SpecialStickerSet &add_special_sticker_set(const SpecialStickerSetType &type);
static void init_special_sticker_set(SpecialStickerSet &sticker_set, int64 sticker_set_id, int64 access_hash,
@ -674,8 +716,6 @@ class StickersManager final : public Actor {
static void add_sticker_thumbnail(Sticker *s, PhotoSize thumbnail);
static string get_sticker_mime_type(const Sticker *s);
static string get_emoji_language_code_version_database_key(const string &language_code);
static string get_emoji_language_code_last_difference_time_database_key(const string &language_code);
@ -828,6 +868,8 @@ class StickersManager final : public Actor {
std::unordered_map<FileId, std::pair<UserId, Promise<Unit>>, FileIdHash> being_uploaded_files_;
Reactions reactions_;
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_;

View File

@ -11,6 +11,9 @@
#include "td/telegram/files/FileId.hpp"
#include "td/telegram/misc.h"
#include "td/telegram/Photo.hpp"
#include "td/telegram/StickerFormat.h"
#include "td/telegram/StickersManager.h"
#include "td/telegram/Td.h"
#include "td/utils/emoji.h"
#include "td/utils/logging.h"
@ -28,12 +31,15 @@ void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT
const Sticker *sticker = it->second.get();
bool has_sticker_set_access_hash = sticker->set_id.is_valid() && !in_sticker_set;
bool has_minithumbnail = !sticker->minithumbnail.empty();
bool is_tgs = sticker->format == StickerFormat::Tgs;
bool is_webm = sticker->format == StickerFormat::Webm;
BEGIN_STORE_FLAGS();
STORE_FLAG(sticker->is_mask);
STORE_FLAG(has_sticker_set_access_hash);
STORE_FLAG(in_sticker_set);
STORE_FLAG(sticker->is_animated);
STORE_FLAG(is_tgs);
STORE_FLAG(has_minithumbnail);
STORE_FLAG(is_webm);
END_STORE_FLAGS();
if (!in_sticker_set) {
store(sticker->set_id.get(), storer);
@ -69,13 +75,23 @@ FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) {
bool has_sticker_set_access_hash;
bool in_sticker_set_stored;
bool has_minithumbnail;
bool is_tgs;
bool is_webm;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(sticker->is_mask);
PARSE_FLAG(has_sticker_set_access_hash);
PARSE_FLAG(in_sticker_set_stored);
PARSE_FLAG(sticker->is_animated);
PARSE_FLAG(is_tgs);
PARSE_FLAG(has_minithumbnail);
PARSE_FLAG(is_webm);
END_PARSE_FLAGS();
if (is_webm) {
sticker->format = StickerFormat::Webm;
} else if (is_tgs) {
sticker->format = StickerFormat::Tgs;
} else {
sticker->format = StickerFormat::Webp;
}
if (in_sticker_set_stored != in_sticker_set) {
Slice data = parser.template fetch_string_raw<Slice>(parser.get_left_len());
for (auto c : data) {
@ -133,6 +149,8 @@ void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with
bool has_expires_at = !sticker_set->is_installed && sticker_set->expires_at != 0;
bool has_thumbnail = sticker_set->thumbnail.file_id.is_valid();
bool has_minithumbnail = !sticker_set->minithumbnail.empty();
bool is_tgs = sticker_set->sticker_format == StickerFormat::Tgs;
bool is_webm = sticker_set->sticker_format == StickerFormat::Webm;
BEGIN_STORE_FLAGS();
STORE_FLAG(sticker_set->is_inited);
STORE_FLAG(was_loaded);
@ -145,9 +163,10 @@ void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with
STORE_FLAG(has_expires_at);
STORE_FLAG(has_thumbnail);
STORE_FLAG(sticker_set->is_thumbnail_reloaded);
STORE_FLAG(sticker_set->is_animated);
STORE_FLAG(is_tgs);
STORE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded);
STORE_FLAG(has_minithumbnail);
STORE_FLAG(is_webm);
END_STORE_FLAGS();
store(sticker_set->id.get(), storer);
store(sticker_set->access_hash, storer);
@ -195,8 +214,9 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser
bool is_masks;
bool has_expires_at;
bool has_thumbnail;
bool is_animated;
bool is_tgs;
bool has_minithumbnail;
bool is_webm;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(sticker_set->is_inited);
PARSE_FLAG(sticker_set->was_loaded);
@ -209,9 +229,10 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser
PARSE_FLAG(has_expires_at);
PARSE_FLAG(has_thumbnail);
PARSE_FLAG(sticker_set->is_thumbnail_reloaded);
PARSE_FLAG(is_animated);
PARSE_FLAG(is_tgs);
PARSE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded);
PARSE_FLAG(has_minithumbnail);
PARSE_FLAG(is_webm);
END_PARSE_FLAGS();
int64 sticker_set_id;
int64 access_hash;
@ -223,6 +244,15 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser
<< sticker_set->access_hash;
}
StickerFormat sticker_format = StickerFormat::Unknown;
if (is_webm) {
sticker_format = StickerFormat::Webm;
} else if (is_tgs) {
sticker_format = StickerFormat::Tgs;
} else {
sticker_format = StickerFormat::Webp;
}
if (sticker_set->is_inited) {
string title;
string short_name;
@ -255,7 +285,7 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser
sticker_set->expires_at = expires_at;
sticker_set->is_official = is_official;
sticker_set->is_masks = is_masks;
sticker_set->is_animated = is_animated;
sticker_set->sticker_format = sticker_format;
short_name_to_sticker_set_id_.emplace(clean_username(sticker_set->short_name), sticker_set->id);
on_update_sticker_set(sticker_set, is_installed, is_archived, false, true);
@ -270,9 +300,9 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser
if (sticker_set->sticker_count != sticker_count || sticker_set->hash != hash) {
sticker_set->is_loaded = false;
}
if (sticker_set->is_animated != is_animated) {
LOG(ERROR) << "Is animated of " << sticker_set->id << " has changed from \"" << is_animated << "\" to \""
<< sticker_set->is_animated << "\"";
if (sticker_set->sticker_format != sticker_format) {
LOG(ERROR) << "Sticker format of " << sticker_set->id << " has changed from \"" << sticker_format << "\" to \""
<< sticker_set->sticker_format << "\"";
}
if (sticker_set->is_masks != is_masks) {
LOG(ERROR) << "Is masks of " << sticker_set->id << " has changed from \"" << is_masks << "\" to \""
@ -348,4 +378,78 @@ void StickersManager::parse_sticker_set_id(StickerSetId &sticker_set_id, ParserT
add_sticker_set(sticker_set_id, sticker_set_access_hash);
}
template <class StorerT>
void StickersManager::Reaction::store(StorerT &storer) const {
StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get();
bool has_around_animation = !around_animation_.empty();
bool has_center_animation = !center_animation_.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(is_active_);
STORE_FLAG(has_around_animation);
STORE_FLAG(has_center_animation);
END_STORE_FLAGS();
td::store(reaction_, storer);
td::store(title_, storer);
stickers_manager->store_sticker(static_icon_, false, storer, "Reaction");
stickers_manager->store_sticker(appear_animation_, false, storer, "Reaction");
stickers_manager->store_sticker(select_animation_, false, storer, "Reaction");
stickers_manager->store_sticker(activate_animation_, false, storer, "Reaction");
stickers_manager->store_sticker(effect_animation_, false, storer, "Reaction");
if (has_around_animation) {
stickers_manager->store_sticker(around_animation_, false, storer, "Reaction");
}
if (has_center_animation) {
stickers_manager->store_sticker(center_animation_, false, storer, "Reaction");
}
}
template <class ParserT>
void StickersManager::Reaction::parse(ParserT &parser) {
StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get();
bool has_around_animation;
bool has_center_animation;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_active_);
PARSE_FLAG(has_around_animation);
PARSE_FLAG(has_center_animation);
END_PARSE_FLAGS();
td::parse(reaction_, parser);
td::parse(title_, parser);
static_icon_ = stickers_manager->parse_sticker(false, parser);
appear_animation_ = stickers_manager->parse_sticker(false, parser);
select_animation_ = stickers_manager->parse_sticker(false, parser);
activate_animation_ = stickers_manager->parse_sticker(false, parser);
effect_animation_ = stickers_manager->parse_sticker(false, parser);
if (has_around_animation) {
around_animation_ = stickers_manager->parse_sticker(false, parser);
}
if (has_center_animation) {
center_animation_ = stickers_manager->parse_sticker(false, parser);
}
}
template <class StorerT>
void StickersManager::Reactions::store(StorerT &storer) const {
bool has_reactions = !reactions_.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(has_reactions);
END_STORE_FLAGS();
if (has_reactions) {
td::store(reactions_, storer);
td::store(hash_, storer);
}
}
template <class ParserT>
void StickersManager::Reactions::parse(ParserT &parser) {
bool has_reactions;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_reactions);
END_PARSE_FLAGS();
if (has_reactions) {
td::parse(reactions_, parser);
td::parse(hash_, parser);
}
}
} // namespace td

View File

@ -59,6 +59,7 @@
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessageLinkInfo.h"
#include "td/telegram/MessageReaction.h"
#include "td/telegram/MessageSearchFilter.h"
#include "td/telegram/MessageSender.h"
#include "td/telegram/MessagesManager.h"
@ -2240,7 +2241,7 @@ class ChangeStickerSetRequest final : public RequestOnceActor {
class UploadStickerFileRequest final : public RequestOnceActor {
UserId user_id_;
tl_object_ptr<td_api::InputSticker> sticker_;
tl_object_ptr<td_api::inputSticker> sticker_;
FileId file_id;
@ -2254,7 +2255,7 @@ class UploadStickerFileRequest final : public RequestOnceActor {
public:
UploadStickerFileRequest(ActorShared<Td> td, uint64 request_id, int64 user_id,
tl_object_ptr<td_api::InputSticker> &&sticker)
tl_object_ptr<td_api::inputSticker> &&sticker)
: RequestOnceActor(std::move(td), request_id), user_id_(user_id), sticker_(std::move(sticker)) {
}
};
@ -2263,13 +2264,12 @@ class CreateNewStickerSetRequest final : public RequestOnceActor {
UserId user_id_;
string title_;
string name_;
bool is_masks_;
vector<tl_object_ptr<td_api::InputSticker>> stickers_;
vector<tl_object_ptr<td_api::inputSticker>> stickers_;
string software_;
void do_run(Promise<Unit> &&promise) final {
td_->stickers_manager_->create_new_sticker_set(user_id_, title_, name_, is_masks_, std::move(stickers_),
std::move(software_), std::move(promise));
td_->stickers_manager_->create_new_sticker_set(user_id_, title_, name_, std::move(stickers_), std::move(software_),
std::move(promise));
}
void do_send_result() final {
@ -2282,12 +2282,11 @@ class CreateNewStickerSetRequest final : public RequestOnceActor {
public:
CreateNewStickerSetRequest(ActorShared<Td> td, uint64 request_id, int64 user_id, string &&title, string &&name,
bool is_masks, vector<tl_object_ptr<td_api::InputSticker>> &&stickers, string &&software)
vector<tl_object_ptr<td_api::inputSticker>> &&stickers, string &&software)
: RequestOnceActor(std::move(td), request_id)
, user_id_(user_id)
, title_(std::move(title))
, name_(std::move(name))
, is_masks_(is_masks)
, stickers_(std::move(stickers))
, software_(std::move(software)) {
}
@ -2296,7 +2295,7 @@ class CreateNewStickerSetRequest final : public RequestOnceActor {
class AddStickerToSetRequest final : public RequestOnceActor {
UserId user_id_;
string name_;
tl_object_ptr<td_api::InputSticker> sticker_;
tl_object_ptr<td_api::inputSticker> sticker_;
void do_run(Promise<Unit> &&promise) final {
td_->stickers_manager_->add_sticker_to_set(user_id_, name_, std::move(sticker_), std::move(promise));
@ -2312,7 +2311,7 @@ class AddStickerToSetRequest final : public RequestOnceActor {
public:
AddStickerToSetRequest(ActorShared<Td> td, uint64 request_id, int64 user_id, string &&name,
tl_object_ptr<td_api::InputSticker> &&sticker)
tl_object_ptr<td_api::inputSticker> &&sticker)
: RequestOnceActor(std::move(td), request_id)
, user_id_(user_id)
, name_(std::move(name))
@ -4775,6 +4774,16 @@ void Td::on_request(uint64 id, td_api::getMessageLinkInfo &request) {
CREATE_REQUEST(GetMessageLinkInfoRequest, std::move(request.url_));
}
void Td::on_request(uint64 id, td_api::translateText &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.text_);
CLEAN_INPUT_STRING(request.from_language_code_);
CLEAN_INPUT_STRING(request.to_language_code_);
CREATE_REQUEST_PROMISE();
messages_manager_->translate_text(request.text_, request.from_language_code_, request.to_language_code_,
std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getFile &request) {
send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(FileId(request.file_id_, 0)));
}
@ -5279,6 +5288,36 @@ void Td::on_request(uint64 id, const td_api::getChatScheduledMessages &request)
CREATE_REQUEST(GetChatScheduledMessagesRequest, request.chat_id_);
}
void Td::on_request(uint64 id, const td_api::getMessageAvailableReactions &request) {
CHECK_IS_USER();
auto r_reactions =
messages_manager_->get_message_available_reactions({DialogId(request.chat_id_), MessageId(request.message_id_)});
if (r_reactions.is_error()) {
send_closure(actor_id(this), &Td::send_error, id, r_reactions.move_as_error());
} else {
send_closure(actor_id(this), &Td::send_result, id,
td_api::make_object<td_api::availableReactions>(r_reactions.move_as_ok()));
}
}
void Td::on_request(uint64 id, td_api::setMessageReaction &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.reaction_);
CREATE_OK_REQUEST_PROMISE();
messages_manager_->set_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)},
std::move(request.reaction_), request.is_big_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::getMessageAddedReactions &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.reaction_);
CLEAN_INPUT_STRING(request.offset_);
CREATE_REQUEST_PROMISE();
get_message_added_reactions(this, {DialogId(request.chat_id_), MessageId(request.message_id_)},
std::move(request.reaction_), std::move(request.offset_), request.limit_,
std::move(promise));
}
void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.offset_);
@ -5329,6 +5368,12 @@ void Td::on_request(uint64 id, const td_api::readAllChatMentions &request) {
messages_manager_->read_all_dialog_mentions(DialogId(request.chat_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::readAllChatReactions &request) {
CHECK_IS_USER();
CREATE_OK_REQUEST_PROMISE();
messages_manager_->read_all_dialog_reactions(DialogId(request.chat_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getChatAvailableMessageSenders &request) {
CHECK_IS_USER();
CREATE_REQUEST_PROMISE();
@ -6056,6 +6101,15 @@ void Td::on_request(uint64 id, const td_api::setPinnedChats &request) {
transform(request.chat_ids_, [](int64 chat_id) { return DialogId(chat_id); })));
}
void Td::on_request(uint64 id, td_api::setChatAvailableReactions &request) {
for (auto &reaction : request.available_reactions_) {
CLEAN_INPUT_STRING(reaction);
}
CREATE_OK_REQUEST_PROMISE();
messages_manager_->set_dialog_available_reactions(DialogId(request.chat_id_), std::move(request.available_reactions_),
std::move(promise));
}
void Td::on_request(uint64 id, td_api::setChatClientData &request) {
answer_ok_query(
id, messages_manager_->set_dialog_client_data(DialogId(request.chat_id_), std::move(request.client_data_)));
@ -6841,7 +6895,7 @@ void Td::on_request(uint64 id, td_api::createNewStickerSet &request) {
CLEAN_INPUT_STRING(request.name_);
CLEAN_INPUT_STRING(request.source_);
CREATE_REQUEST(CreateNewStickerSetRequest, request.user_id_, std::move(request.title_), std::move(request.name_),
request.is_masks_, std::move(request.stickers_), std::move(request.source_));
std::move(request.stickers_), std::move(request.source_));
}
void Td::on_request(uint64 id, td_api::addStickerToSet &request) {

View File

@ -105,7 +105,7 @@ class Td final : public Actor {
Td &operator=(Td &&) = delete;
~Td() final;
static constexpr const char *TDLIB_VERSION = "1.8.0";
static constexpr const char *TDLIB_VERSION = "1.8.1";
struct Options {
std::shared_ptr<NetQueryStats> net_query_stats;
@ -542,6 +542,8 @@ class Td final : public Actor {
void on_request(uint64 id, td_api::getMessageLinkInfo &request);
void on_request(uint64 id, td_api::translateText &request);
void on_request(uint64 id, const td_api::getFile &request);
void on_request(uint64 id, td_api::getRemoteFile &request);
@ -654,6 +656,12 @@ class Td final : public Actor {
void on_request(uint64 id, const td_api::getChatScheduledMessages &request);
void on_request(uint64 id, const td_api::getMessageAvailableReactions &request);
void on_request(uint64 id, td_api::setMessageReaction &request);
void on_request(uint64 id, td_api::getMessageAddedReactions &request);
void on_request(uint64 id, td_api::getMessagePublicForwards &request);
void on_request(uint64 id, const td_api::removeNotification &request);
@ -668,6 +676,8 @@ class Td final : public Actor {
void on_request(uint64 id, const td_api::readAllChatMentions &request);
void on_request(uint64 id, const td_api::readAllChatReactions &request);
void on_request(uint64 id, const td_api::getChatAvailableMessageSenders &request);
void on_request(uint64 id, const td_api::setChatMessageSender &request);
@ -848,6 +858,8 @@ class Td final : public Actor {
void on_request(uint64 id, const td_api::setPinnedChats &request);
void on_request(uint64 id, td_api::setChatAvailableReactions &request);
void on_request(uint64 id, td_api::setChatClientData &request);
void on_request(uint64 id, td_api::setChatDescription &request);

View File

@ -120,6 +120,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_p
case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer:
case LogEvent::HandlerType::DeleteAllCallMessagesOnServer:
case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer:
case LogEvent::HandlerType::ReadAllDialogReactionsOnServer:
events.to_messages_manager.push_back(event.clone());
break;
case LogEvent::HandlerType::AddMessagePushNotification:

View File

@ -2701,6 +2701,12 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelWebPage>
promise.set_value(Unit());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessageReactions> update, Promise<Unit> &&promise) {
td_->messages_manager_->on_update_message_reactions(
{DialogId(update->peer_), MessageId(ServerMessageId(update->msg_id_))}, std::move(update->reactions_));
promise.set_value(Unit());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateFolderPeers> update, Promise<Unit> &&promise) {
for (auto &folder_peer : update->folder_peers_) {
DialogId dialog_id(folder_peer->peer_);
@ -3280,7 +3286,4 @@ void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePendingJoinRequ
// unsupported updates
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessageReactions> update, Promise<Unit> &&promise) {
}
} // namespace td

View File

@ -384,6 +384,8 @@ class UpdatesManager final : public Actor {
void on_update(tl_object_ptr<telegram_api::updateWebPage> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateChannelWebPage> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateMessageReactions> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateFolderPeers> update, Promise<Unit> &&promise);
void on_update(tl_object_ptr<telegram_api::updateUserTyping> update, Promise<Unit> &&promise);
@ -504,8 +506,6 @@ class UpdatesManager final : public Actor {
void on_update(tl_object_ptr<telegram_api::updatePendingJoinRequests> update, Promise<Unit> &&promise);
// unsupported updates
void on_update(tl_object_ptr<telegram_api::updateMessageReactions> update, Promise<Unit> &&promise);
};
} // namespace td

View File

@ -10,7 +10,7 @@
namespace td {
constexpr int32 MTPROTO_LAYER = 136;
constexpr int32 MTPROTO_LAYER = 138;
enum class Version : int32 {
Initial, // 0

View File

@ -428,7 +428,7 @@ class CliClient final : public Actor {
static char get_delimiter(Slice str) {
std::unordered_set<char> chars;
for (auto c : trim(str)) {
if (!is_alnum(c) && c != '-' && c != '.' && c != '/') {
if (!is_alnum(c) && c != '-' && c != '.' && c != '/' && static_cast<uint8>(c) <= 127) {
chars.insert(c);
}
}
@ -442,6 +442,14 @@ class CliClient final : public Actor {
return ' ';
}
static vector<Slice> autosplit(Slice str) {
return full_split(trim(str), get_delimiter(str));
}
static vector<string> autosplit_str(Slice str) {
return transform(autosplit(str), [](Slice slice) { return slice.str(); });
}
int64 as_chat_id(Slice str) const {
str = trim(str);
if (str == "me") {
@ -474,8 +482,7 @@ class CliClient final : public Actor {
}
static vector<int32> as_chat_filter_ids(Slice chat_filter_ids) {
return transform(full_split(trim(chat_filter_ids), get_delimiter(chat_filter_ids)),
[](Slice str) { return as_chat_filter_id(str); });
return transform(autosplit(chat_filter_ids), as_chat_filter_id);
}
static td_api::object_ptr<td_api::ChatList> as_chat_list(string chat_list) {
@ -489,8 +496,7 @@ class CliClient final : public Actor {
}
vector<int64> as_chat_ids(Slice chat_ids) const {
return transform(full_split(trim(chat_ids), get_delimiter(chat_ids)),
[this](Slice str) { return as_chat_id(str); });
return transform(autosplit(chat_ids), [this](Slice str) { return as_chat_id(str); });
}
static int64 as_message_id(Slice str) {
@ -502,7 +508,7 @@ class CliClient final : public Actor {
}
static vector<int64> as_message_ids(Slice message_ids) {
return transform(full_split(trim(message_ids), get_delimiter(message_ids)), as_message_id);
return transform(autosplit(message_ids), as_message_id);
}
static int64 as_message_thread_id(Slice str) {
@ -522,6 +528,21 @@ class CliClient final : public Actor {
return to_integer<int32>(trim(str));
}
static td_api::object_ptr<td_api::StickerType> as_sticker_type(string sticker_type) {
if (!sticker_type.empty() && sticker_type.back() == 'a') {
return td_api::make_object<td_api::stickerTypeAnimated>();
}
if (!sticker_type.empty() && sticker_type.back() == 'v') {
return td_api::make_object<td_api::stickerTypeVideo>();
}
if (!sticker_type.empty() && sticker_type.back() == 'm') {
auto position = td_api::make_object<td_api::maskPosition>(td_api::make_object<td_api::maskPointEyes>(),
Random::fast(-5, 5), Random::fast(-5, 5), 1.0);
return td_api::make_object<td_api::stickerTypeMask>(Random::fast_bool() ? nullptr : std::move(position));
}
return td_api::make_object<td_api::stickerTypeStatic>();
}
static int32 as_limit(Slice str, int32 default_limit = 10) {
if (str.empty()) {
return default_limit;
@ -549,7 +570,7 @@ class CliClient final : public Actor {
}
vector<int64> as_user_ids(Slice user_ids) const {
return transform(full_split(user_ids, get_delimiter(user_ids)), [this](Slice str) { return as_user_id(str); });
return transform(autosplit(user_ids), [this](Slice str) { return as_user_id(str); });
}
static int64 as_basic_group_id(Slice str) {
@ -613,10 +634,10 @@ class CliClient final : public Actor {
expected_size);
}
static td_api::object_ptr<td_api::InputFile> as_input_file(string str) {
static td_api::object_ptr<td_api::InputFile> as_input_file(Slice str) {
str = trim(str);
if ((str.size() >= 20 && is_base64url(str)) || begins_with(str, "http")) {
return as_remote_file(str);
return as_remote_file(str.str());
}
auto r_file_id = to_integer_safe<int32>(str);
if (r_file_id.is_ok()) {
@ -624,9 +645,9 @@ class CliClient final : public Actor {
}
if (str.find(';') < str.size()) {
auto res = split(str, ';');
return as_generated_file(res.first, res.second);
return as_generated_file(res.first.str(), res.second.str());
}
return as_local_file(str);
return as_local_file(str.str());
}
static td_api::object_ptr<td_api::inputThumbnail> as_input_thumbnail(td_api::object_ptr<td_api::InputFile> input_file,
@ -671,8 +692,8 @@ class CliClient final : public Actor {
}
template <class T>
static vector<T> to_integers(Slice ids_string) {
return transform(transform(full_split(ids_string, get_delimiter(ids_string)), trim<Slice>), to_integer<T>);
static vector<T> to_integers(Slice integers) {
return transform(transform(autosplit(integers), trim<Slice>), to_integer<T>);
}
static void get_args(string &args, string &arg) {
@ -1198,6 +1219,9 @@ class CliClient final : public Actor {
if (filter == "um" || filter == "umention") {
return td_api::make_object<td_api::searchMessagesFilterUnreadMention>();
}
if (filter == "ur" || filter == "ureaction") {
return td_api::make_object<td_api::searchMessagesFilterUnreadReaction>();
}
if (filter == "f" || filter == "failed") {
return td_api::make_object<td_api::searchMessagesFilterFailedToSend>();
}
@ -1477,7 +1501,7 @@ class CliClient final : public Actor {
}
static auto as_passport_element_types(Slice types) {
return transform(full_split(types, get_delimiter(types)), [](Slice str) { return as_passport_element_type(str); });
return transform(autosplit(types), as_passport_element_type);
}
static td_api::object_ptr<td_api::InputPassportElement> as_input_passport_element(const string &passport_element_type,
@ -1486,7 +1510,7 @@ class CliClient final : public Actor {
vector<td_api::object_ptr<td_api::InputFile>> input_files;
td_api::object_ptr<td_api::InputFile> selfie;
if (!arg.empty()) {
auto files = full_split(arg);
auto files = autosplit(arg);
CHECK(!files.empty());
if (with_selfie) {
selfie = as_input_file(files.back());
@ -2059,6 +2083,27 @@ class CliClient final : public Actor {
ChatId chat_id;
get_args(args, chat_id);
send_request(td_api::make_object<td_api::getChatScheduledMessages>(chat_id));
} else if (op == "gmar") {
ChatId chat_id;
MessageId message_id;
get_args(args, chat_id, message_id);
send_request(td_api::make_object<td_api::getMessageAvailableReactions>(chat_id, message_id));
} else if (op == "react") {
ChatId chat_id;
MessageId message_id;
string reaction;
bool is_big;
get_args(args, chat_id, message_id, reaction, is_big);
send_request(td_api::make_object<td_api::setMessageReaction>(chat_id, message_id, reaction, is_big));
} else if (op == "gmars") {
ChatId chat_id;
MessageId message_id;
string reaction;
string offset;
string limit;
get_args(args, chat_id, message_id, reaction, offset, limit);
send_request(td_api::make_object<td_api::getMessageAddedReactions>(chat_id, message_id, reaction, offset,
as_limit(limit)));
} else if (op == "gmpf") {
ChatId chat_id;
MessageId message_id;
@ -2192,7 +2237,7 @@ class CliClient final : public Actor {
string language_code;
string keys;
get_args(args, language_code, keys);
send_request(td_api::make_object<td_api::getLanguagePackStrings>(language_code, full_split(keys)));
send_request(td_api::make_object<td_api::getLanguagePackStrings>(language_code, autosplit_str(keys)));
} else if (op == "glpss") {
string language_database_path;
string language_pack;
@ -2540,30 +2585,20 @@ class CliClient final : public Actor {
} else if (op == "cssn") {
const string &name = args;
send_request(td_api::make_object<td_api::checkStickerSetName>(name));
} else if (op == "usf" || op == "usfa") {
td_api::object_ptr<td_api::InputSticker> input_sticker;
if (op == "usfa") {
input_sticker = td_api::make_object<td_api::inputStickerAnimated>(as_input_file(args), "😀");
} else {
input_sticker = td_api::make_object<td_api::inputStickerStatic>(as_input_file(args), "😀", nullptr);
}
send_request(td_api::make_object<td_api::uploadStickerFile>(-1, std::move(input_sticker)));
} else if (op == "cnss" || op == "cnssa") {
} else if (op == "usf" || op == "usfa" || op == "usfv" || op == "usfm") {
send_request(td_api::make_object<td_api::uploadStickerFile>(
-1, td_api::make_object<td_api::inputSticker>(as_input_file(args), "😀", as_sticker_type(op))));
} else if (op == "cnss" || op == "cnssa" || op == "cnssv" || op == "cnssm") {
string title;
string name;
string stickers;
get_args(args, title, name, stickers);
auto input_stickers =
transform(full_split(stickers, get_delimiter(stickers)),
[op](const string &sticker) -> td_api::object_ptr<td_api::InputSticker> {
if (op == "cnssa") {
return td_api::make_object<td_api::inputStickerAnimated>(as_input_file(sticker), "😀");
} else {
return td_api::make_object<td_api::inputStickerStatic>(as_input_file(sticker), "😀", nullptr);
}
});
send_request(td_api::make_object<td_api::createNewStickerSet>(my_id_, title, name, false,
std::move(input_stickers), "tg_cli"));
transform(autosplit(stickers), [op](Slice sticker) -> td_api::object_ptr<td_api::inputSticker> {
return td_api::make_object<td_api::inputSticker>(as_input_file(sticker), "😀", as_sticker_type(op));
});
send_request(
td_api::make_object<td_api::createNewStickerSet>(my_id_, title, name, std::move(input_stickers), "tg_cli"));
} else if (op == "sss") {
send_request(td_api::make_object<td_api::searchStickerSet>(args));
} else if (op == "siss") {
@ -2745,6 +2780,12 @@ class CliClient final : public Actor {
send_request(td_api::make_object<td_api::getMessageEmbeddingCode>(chat_id, message_id, for_album));
} else if (op == "gmli") {
send_request(td_api::make_object<td_api::getMessageLinkInfo>(args));
} else if (op == "tt") {
string text;
string from_language_code;
string to_language_code;
get_args(args, text, from_language_code, to_language_code);
send_request(td_api::make_object<td_api::translateText>(text, from_language_code, to_language_code));
} else if (op == "gf" || op == "GetFile") {
send_request(td_api::make_object<td_api::getFile>(as_file_id(args)));
} else if (op == "gfdps") {
@ -3272,12 +3313,7 @@ class CliClient final : public Actor {
send_request(
td_api::make_object<td_api::toggleChatDefaultDisableNotification>(chat_id, default_disable_notification));
} else if (op == "spchats" || op == "spchatsa" || begins_with(op, "spchats-")) {
vector<string> chat_ids_str = full_split(args, ' ');
vector<int64> chat_ids;
for (auto &chat_id_str : chat_ids_str) {
chat_ids.push_back(as_chat_id(chat_id_str));
}
send_request(td_api::make_object<td_api::setPinnedChats>(as_chat_list(op), std::move(chat_ids)));
send_request(td_api::make_object<td_api::setPinnedChats>(as_chat_list(op), as_chat_ids(args)));
} else if (op == "sca") {
ChatId chat_id;
string message_thread_id;
@ -3690,7 +3726,7 @@ class CliClient final : public Actor {
ChatId chat_id;
string question;
get_args(args, chat_id, question, args);
auto options = full_split(args);
auto options = autosplit_str(args);
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),
@ -4167,6 +4203,11 @@ class CliClient final : public Actor {
get_args(args, supergroup_id, sign_messages);
send_request(
td_api::make_object<td_api::toggleSupergroupSignMessages>(as_supergroup_id(supergroup_id), sign_messages));
} else if (op == "scar") {
ChatId chat_id;
string available_reactions;
get_args(args, chat_id, available_reactions);
send_request(td_api::make_object<td_api::setChatAvailableReactions>(chat_id, autosplit_str(available_reactions)));
} else if (op == "scd") {
ChatId chat_id;
string description;
@ -4303,6 +4344,10 @@ class CliClient final : public Actor {
ChatId chat_id;
get_args(args, chat_id);
send_request(td_api::make_object<td_api::readAllChatMentions>(chat_id));
} else if (op == "racr") {
ChatId chat_id;
get_args(args, chat_id);
send_request(td_api::make_object<td_api::readAllChatReactions>(chat_id));
} else if (op == "tre") {
send_request(td_api::make_object<td_api::testReturnError>(
args.empty() ? nullptr : td_api::make_object<td_api::error>(-1, args)));

View File

@ -884,7 +884,7 @@ string FileManager::get_file_name(FileType file_type, Slice path) {
}
break;
case FileType::Sticker:
if (extension != "webp" && extension != "tgs") {
if (extension != "webp" && extension != "tgs" && extension != "webm") {
return fix_file_extension(file_name, "sticker", "webp");
}
break;
@ -3222,7 +3222,7 @@ FileType FileManager::guess_file_type(const tl_object_ptr<td_api::InputFile> &fi
if (extension == "mp3" || extension == "mpeg3" || extension == "m4a") {
return FileType::Audio;
}
if (extension == "webp" || extension == "tgs") {
if (extension == "webp" || extension == "tgs" || extension == "webm") {
return FileType::Sticker;
}
if (extension == "gif") {

View File

@ -100,6 +100,7 @@ class LogEvent {
UnpinAllDialogMessagesOnServer = 0x121,
DeleteAllCallMessagesOnServer = 0x122,
DeleteDialogMessagesByDateOnServer = 0x123,
ReadAllDialogReactionsOnServer = 0x124,
GetChannelDifference = 0x140,
AddMessagePushNotification = 0x200,
EditMessagePushNotification = 0x201,

View File

@ -20,6 +20,7 @@
#include "td/utils/logging.h"
#include "td/utils/ObjectPool.h"
#include "td/utils/Slice.h"
#include "td/utils/Span.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
@ -196,11 +197,11 @@ class NetQuery final : public TsListNode<NetQueryDebug> {
message_id_ = message_id;
}
NetQueryRef invoke_after() const {
Span<NetQueryRef> invoke_after() const {
return invoke_after_;
}
void set_invoke_after(NetQueryRef ref) {
invoke_after_ = ref;
void set_invoke_after(std::vector<NetQueryRef> refs) {
invoke_after_ = std::move(refs);
}
void set_session_rand(uint32 session_rand) {
session_rand_ = session_rand;
@ -289,7 +290,7 @@ class NetQuery final : public TsListNode<NetQueryDebug> {
BufferSlice answer_;
int32 tl_constructor_ = 0;
NetQueryRef invoke_after_;
std::vector<NetQueryRef> invoke_after_;
uint32 session_rand_ = 0;
bool may_be_lost_ = false;
@ -383,6 +384,9 @@ inline StringBuilder &operator<<(StringBuilder &stream, const NetQuery &net_quer
}
inline StringBuilder &operator<<(StringBuilder &stream, const NetQueryPtr &net_query_ptr) {
if (net_query_ptr.empty()) {
return stream << "[Query: null]";
}
return stream << *net_query_ptr;
}

View File

@ -35,6 +35,7 @@
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Span.h"
#include "td/utils/Time.h"
#include "td/utils/Timer.h"
#include "td/utils/tl_parsers.h"
@ -753,7 +754,7 @@ Status Session::on_message_result_ok(uint64 id, BufferSlice packet, size_t origi
auto dropped_size = dropped_size_;
dropped_size_ = 0;
return Status::Error(
2, PSLICE() << "Too much dropped packets " << tag("total_size", format::as_size(dropped_size)));
2, PSLICE() << "Too many dropped packets " << tag("total_size", format::as_size(dropped_size)));
}
}
return Status::OK();
@ -799,24 +800,29 @@ void Session::on_message_result_error(uint64 id, int error_code, string message)
// UNAUTHORIZED
if (error_code == 401 && message != "SESSION_PASSWORD_NEEDED") {
if (auth_data_.use_pfs() && (message == CSlice("AUTH_KEY_PERM_EMPTY") || !is_main_)) {
LOG(INFO) << "Receive 401, " << message << " in session " << auth_data_.get_session_id() << " for auth key "
if (auth_data_.use_pfs() && message == CSlice("AUTH_KEY_PERM_EMPTY")) {
LOG(INFO) << "Receive AUTH_KEY_PERM_EMPTY in session " << auth_data_.get_session_id() << " for auth key "
<< auth_data_.get_tmp_auth_key().id();
// temporary key can be dropped any time
auth_data_.drop_tmp_auth_key();
on_tmp_auth_key_updated();
error_code = 500;
} else {
bool can_drop_main_auth_key_without_logging_out = is_cdn_;
if (!is_main_) {
CHECK(!auth_data_.use_pfs());
if (G()->net_query_dispatcher().get_main_dc_id().get_raw_id() != raw_dc_id_) {
can_drop_main_auth_key_without_logging_out = true;
}
if (auth_data_.use_pfs() && !is_main_) {
// temporary key can be dropped any time
auth_data_.drop_tmp_auth_key();
on_tmp_auth_key_updated();
error_code = 500;
}
bool can_drop_main_auth_key_without_logging_out = is_cdn_;
if (!is_main_ && G()->net_query_dispatcher().get_main_dc_id().get_raw_id() != raw_dc_id_) {
can_drop_main_auth_key_without_logging_out = true;
}
LOG(INFO) << "Receive 401, " << message << " in session " << auth_data_.get_session_id() << " for auth key "
<< auth_data_.get_auth_key().id() << ", PFS = " << auth_data_.use_pfs() << ", is_main = " << is_main_
<< ", can_drop_main_auth_key_without_logging_out = " << can_drop_main_auth_key_without_logging_out;
if (can_drop_main_auth_key_without_logging_out) {
LOG(INFO) << "Receive 401, " << message << " in session " << auth_data_.get_session_id() << " for auth key "
<< auth_data_.get_auth_key().id();
auth_data_.drop_main_auth_key();
on_auth_key_updated();
error_code = 500;
@ -987,14 +993,17 @@ void Session::connection_send_query(ConnectionInfo *info, NetQueryPtr &&net_quer
return return_query(std::move(net_query));
}
uint64 invoke_after_id = 0;
NetQueryRef invoke_after = net_query->invoke_after();
if (!invoke_after.empty()) {
invoke_after_id = invoke_after->message_id();
if (invoke_after->session_id() != auth_data_.get_session_id() || invoke_after_id == 0) {
Span<NetQueryRef> invoke_after = net_query->invoke_after();
std::vector<uint64> invoke_after_ids;
for (auto &ref : invoke_after) {
auto invoke_after_id = ref->message_id();
if (ref->session_id() != auth_data_.get_session_id() || invoke_after_id == 0) {
net_query->set_error_resend_invoke_after();
return return_query(std::move(net_query));
}
invoke_after_ids.push_back(invoke_after_id);
}
if (!invoke_after.empty()) {
if (!unknown_queries_.empty()) {
pending_invoke_after_queries_.push_back(std::move(net_query));
return;
@ -1005,7 +1014,7 @@ void Session::connection_send_query(ConnectionInfo *info, NetQueryPtr &&net_quer
if (!immediately_fail_query) {
auto r_message_id =
info->connection_->send_query(net_query->query().clone(), net_query->gzip_flag() == NetQuery::GzipFlag::On,
message_id, invoke_after_id, static_cast<bool>(net_query->quick_ack_promise_));
message_id, invoke_after_ids, static_cast<bool>(net_query->quick_ack_promise_));
net_query->on_net_write(net_query->query().size());
@ -1019,7 +1028,7 @@ void Session::connection_send_query(ConnectionInfo *info, NetQueryPtr &&net_quer
}
}
VLOG(net_query) << "Send query to connection " << net_query << " [msg_id:" << format::as_hex(message_id) << "]"
<< tag("invoke_after", format::as_hex(invoke_after_id));
<< tag("invoke_after", transform(invoke_after_ids, [](auto id) { return format::as_hex(id); }));
net_query->set_message_id(message_id);
net_query->cancel_slot_.clear_event();
LOG_CHECK(sent_queries_.find(message_id) == sent_queries_.end()) << message_id;
@ -1158,8 +1167,8 @@ void Session::connection_open_finish(ConnectionInfo *info,
info->created_at_ = Time::now_cached();
info->wakeup_at_ = Time::now_cached() + 10;
if (unknown_queries_.size() > MAX_INFLIGHT_QUERIES) {
LOG(ERROR) << "With current limits `Too much queries with unknown state` error must be impossible";
on_session_failed(Status::Error("Too much queries with unknown state"));
LOG(ERROR) << "With current limits `Too many queries with unknown state` error must be impossible";
on_session_failed(Status::Error("Too many queries with unknown state"));
return;
}
if (info->ask_info_) {

View File

@ -57,7 +57,7 @@ class HttpHeaderCreator {
sb_ << content;
}
if (sb_.is_error()) {
return Status::Error("Too much headers");
return Status::Error("Too many headers");
}
return sb_.as_cslice();
}

View File

@ -200,7 +200,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query, bool can_be_slow) {
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");
return Status::Error(413, "Request Entity Too Large: too many parameters");
}
if (flow_sink_.is_ready()) {
@ -406,7 +406,7 @@ Result<bool> HttpReader::parse_multipart_form_data(bool can_be_slow) {
if (has_file_name_) {
// file
if (query_->files_.size() == max_files_) {
return Status::Error(413, "Request Entity Too Large: too much files attached");
return Status::Error(413, "Request Entity Too Large: too many files attached");
}
auto file = open_temp_file(file_name_);
if (file.is_error()) {
@ -432,7 +432,7 @@ Result<bool> HttpReader::parse_multipart_form_data(bool can_be_slow) {
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");
return Status::Error(413, "Request Entity Too Large: too many parameters in form data");
}
query_->container_.emplace_back(content_->cut_head(form_data_read_length_).move_as_buffer_slice());
@ -460,7 +460,7 @@ Result<bool> HttpReader::parse_multipart_form_data(bool can_be_slow) {
CHECK(content_->size() < form_data_read_length_ + boundary_.size());
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");
return Status::Error(413, "Request Entity Too Large: too many parameters in form data");
}
return false;
case FormDataParseState::ReadFile: {
@ -594,7 +594,7 @@ Status HttpReader::parse_url(MutableSlice url) {
Status HttpReader::parse_parameters(MutableSlice parameters) {
total_parameters_length_ += parameters.size();
if (total_parameters_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
return Status::Error(413, "Request Entity Too Large: too much parameters");
return Status::Error(413, "Request Entity Too Large: too many parameters");
}
LOG(DEBUG) << "Parse parameters: \"" << parameters << "\"";
@ -620,7 +620,7 @@ Status HttpReader::parse_json_parameters(MutableSlice parameters) {
total_parameters_length_ += parameters.size();
if (total_parameters_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
return Status::Error(413, "Request Entity Too Large: too much parameters");
return Status::Error(413, "Request Entity Too Large: too many parameters");
}
LOG(DEBUG) << "Parse JSON parameters: \"" << parameters << "\"";

View File

@ -184,6 +184,7 @@ set(TDUTILS_SOURCE
td/utils/BufferedUdp.h
td/utils/ByteFlow.h
td/utils/CancellationToken.h
td/utils/ChainScheduler.h
td/utils/ChangesProcessor.h
td/utils/check.h
td/utils/Closure.h
@ -289,6 +290,7 @@ endif()
set(TDUTILS_TEST_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/test/bitmask.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/buffer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/ChainScheduler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/ConcurrentHashMap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/crypto.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/Enumerator.cpp

View File

@ -0,0 +1,308 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/utils/algorithm.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
#include "td/utils/List.h"
#include "td/utils/optional.h"
#include "td/utils/Span.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/VectorQueue.h"
#include <functional>
#include <map>
namespace td {
struct ChainSchedulerTaskWithParents {
uint64 task_id{};
vector<uint64> parents;
};
template <class ExtraT = Unit>
class ChainScheduler {
public:
using TaskId = uint64;
using ChainId = uint64;
TaskId create_task(Span<ChainId> chains, ExtraT extra = {});
ExtraT *get_task_extra(TaskId task_id);
optional<ChainSchedulerTaskWithParents> start_next_task();
void finish_task(TaskId task_id);
void reset_task(TaskId task_id);
template <class ExtraTT>
friend StringBuilder &operator<<(StringBuilder &sb, ChainScheduler<ExtraTT> &scheduler);
template <class F>
void for_each(F &&f) {
tasks_.for_each([&f](auto, Task &task) { f(task.extra); });
}
private:
struct ChainNode : ListNode {
TaskId task_id{};
};
class Chain {
public:
void add_task(ChainNode *node) {
head_.put_back(node);
}
optional<TaskId> get_first() {
if (head_.empty()) {
return {};
}
return static_cast<ChainNode &>(*head_.get_next()).task_id;
}
optional<TaskId> get_child(ChainNode *chain_node) {
if (chain_node->get_next() == head_.end()) {
return {};
}
return static_cast<ChainNode &>(*chain_node->get_next()).task_id;
}
optional<TaskId> get_parent(ChainNode *chain_node) {
if (chain_node->get_prev() == head_.end()) {
return {};
}
return static_cast<ChainNode &>(*chain_node->get_prev()).task_id;
}
void finish_task(ChainNode *node) {
node->remove();
}
bool empty() const {
return head_.empty();
}
void foreach(std::function<void(TaskId)> f) const {
for (auto it = head_.begin(); it != head_.end(); it = it->get_next()) {
f(static_cast<const ChainNode &>(*it).task_id);
}
}
private:
ListNode head_;
};
struct ChainInfo {
Chain chain;
uint32 active_tasks{};
};
struct TaskChainInfo {
ChainNode chain_node;
ChainId chain_id{};
ChainInfo *chain_info{};
bool waiting_for_parent{};
};
struct Task {
enum class State { Pending, Active } state{State::Pending};
vector<TaskChainInfo> chains;
ExtraT extra;
};
std::map<ChainId, ChainInfo> chains_;
std::map<ChainId, TaskId> limited_tasks_;
Container<Task> tasks_;
VectorQueue<TaskId> pending_tasks_;
void on_parent_is_ready(TaskId task_id, ChainId chain_id) {
auto *task = tasks_.get(task_id);
CHECK(task);
for (TaskChainInfo &task_chain_info : task->chains) {
if (task_chain_info.chain_id == chain_id) {
task_chain_info.waiting_for_parent = false;
}
}
try_start_task(task_id, task);
}
void try_start_task(TaskId task_id, Task *task) {
if (task->state != Task::State::Pending) {
return;
}
for (TaskChainInfo &task_chain_info : task->chains) {
if (task_chain_info.waiting_for_parent) {
return;
}
ChainInfo &chain_info = chains_[task_chain_info.chain_id];
if (chain_info.active_tasks >= 10) {
limited_tasks_[task_chain_info.chain_id] = task_id;
return;
}
}
do_start_task(task_id, task);
}
void do_start_task(TaskId task_id, Task *task) {
for (TaskChainInfo &task_chain_info : task->chains) {
ChainInfo &chain_info = chains_[task_chain_info.chain_id];
chain_info.active_tasks++;
}
task->state = Task::State::Active;
pending_tasks_.push(task_id);
notify_children(task);
}
void notify_children(Task *task) {
for (TaskChainInfo &task_chain_info : task->chains) {
ChainInfo &chain_info = chains_[task_chain_info.chain_id];
auto o_child = chain_info.chain.get_child(&task_chain_info.chain_node);
if (o_child) {
on_parent_is_ready(o_child.value(), task_chain_info.chain_id);
}
}
}
void inactivate_task(TaskId task_id, Task *task) {
CHECK(task->state == Task::State::Active);
task->state = Task::State::Pending;
for (TaskChainInfo &task_chain_info : task->chains) {
ChainInfo &chain_info = chains_[task_chain_info.chain_id];
chain_info.active_tasks--;
auto it = limited_tasks_.find(task_chain_info.chain_id);
if (it != limited_tasks_.end()) {
auto limited_task_id = it->second;
limited_tasks_.erase(it);
if (limited_task_id != task_id) {
try_start_task(limited_task_id, tasks_.get(limited_task_id));
}
}
auto o_first = chain_info.chain.get_first();
if (o_first) {
auto first_task_id = o_first.unwrap();
if (first_task_id != task_id) {
try_start_task(first_task_id, tasks_.get(first_task_id));
}
}
}
}
void finish_chain_task(TaskChainInfo &task_chain_info) {
auto &chain = task_chain_info.chain_info->chain;
chain.finish_task(&task_chain_info.chain_node);
if (chain.empty()) {
chains_.erase(task_chain_info.chain_id);
}
}
};
template <class ExtraT>
typename ChainScheduler<ExtraT>::TaskId ChainScheduler<ExtraT>::create_task(Span<ChainScheduler::ChainId> chains,
ExtraT extra) {
auto task_id = tasks_.create();
Task &task = *tasks_.get(task_id);
task.extra = std::move(extra);
task.chains = transform(chains, [&](auto chain_id) {
TaskChainInfo task_chain_info;
ChainInfo &chain_info = chains_[chain_id];
task_chain_info.chain_id = chain_id;
task_chain_info.chain_info = &chain_info;
task_chain_info.chain_node.task_id = task_id;
return task_chain_info;
});
for (TaskChainInfo &task_chain_info : task.chains) {
auto &chain = task_chain_info.chain_info->chain;
chain.add_task(&task_chain_info.chain_node);
task_chain_info.waiting_for_parent = static_cast<bool>(chain.get_parent(&task_chain_info.chain_node));
}
try_start_task(task_id, &task);
return task_id;
}
template <class ExtraT>
ExtraT *ChainScheduler<ExtraT>::get_task_extra(ChainScheduler::TaskId task_id) { // may return nullptr
auto *task = tasks_.get(task_id);
if (!task) {
return nullptr;
}
return &task->extra;
}
template <class ExtraT>
optional<ChainSchedulerTaskWithParents> ChainScheduler<ExtraT>::start_next_task() {
if (pending_tasks_.empty()) {
return {};
}
auto task_id = pending_tasks_.pop();
ChainSchedulerTaskWithParents res;
res.task_id = task_id;
auto *task = tasks_.get(task_id);
CHECK(task);
for (TaskChainInfo &task_chain_info : task->chains) {
Chain &chain = task_chain_info.chain_info->chain;
auto o_parent = chain.get_parent(&task_chain_info.chain_node);
if (o_parent) {
res.parents.push_back(o_parent.value());
}
}
return res;
}
template <class ExtraT>
void ChainScheduler<ExtraT>::finish_task(ChainScheduler::TaskId task_id) {
auto *task = tasks_.get(task_id);
CHECK(task);
inactivate_task(task_id, task);
notify_children(task);
for (TaskChainInfo &task_chain_info : task->chains) {
finish_chain_task(task_chain_info);
}
tasks_.erase(task_id);
}
template <class ExtraT>
void ChainScheduler<ExtraT>::reset_task(ChainScheduler::TaskId task_id) {
auto *task = tasks_.get(task_id);
CHECK(task);
inactivate_task(task_id, task);
for (TaskChainInfo &task_chain_info : task->chains) {
ChainInfo &chain_info = chains_[task_chain_info.chain_id];
task_chain_info.waiting_for_parent = static_cast<bool>(chain_info.chain.get_parent(&task_chain_info.chain_node));
}
try_start_task(task_id, task);
}
template <class ExtraT>
StringBuilder &operator<<(StringBuilder &sb, ChainScheduler<ExtraT> &scheduler) {
// 1 print chains
sb << "\n";
for (auto &it : scheduler.chains_) {
sb << "ChainId{" << it.first << "} ";
sb << " active_cnt=" << it.second.active_tasks;
sb << " : ";
it.second.chain.foreach([&](auto task_id) { sb << *scheduler.get_task_extra(task_id); });
sb << "\n";
}
scheduler.tasks_.for_each([&](auto id, auto &task) {
sb << "Task: " << task.extra;
sb << " state =" << static_cast<int>(task.state);
for (auto &task_chain_info : task.chains) {
if (task_chain_info.waiting_for_parent) {
sb << " wait "
<< *scheduler.get_task_extra(
task_chain_info.chain_info->chain.get_parent(&task_chain_info.chain_node).value());
}
}
sb << "\n";
});
return sb;
}
} // namespace td

View File

@ -89,12 +89,24 @@ struct ListNode {
ListNode *end() {
return this;
}
const ListNode *begin() const {
return next;
}
const ListNode *end() const {
return this;
}
ListNode *get_next() {
return next;
}
ListNode *get_prev() {
return prev;
}
const ListNode *get_next() const {
return next;
}
const ListNode *get_prev() const {
return prev;
}
protected:
void clear() {

View File

@ -196,7 +196,7 @@ Result<vector<char *>> OptionParser::run_impl(int argc, char *argv[], int expect
return Status::Error("Unexpected non-option parameters specified");
}
if (non_options.size() > static_cast<size_t>(expected_non_option_count)) {
return Status::Error("Too much non-option parameters specified");
return Status::Error("Too many non-option parameters specified");
} else {
return Status::Error("Too few non-option parameters specified");
}

View File

@ -145,46 +145,11 @@ using Span = detail::SpanImpl<T, const T>;
template <class T>
using MutableSpan = detail::SpanImpl<T, T>;
template <class T>
Span<T> span(const T *ptr, size_t size) {
return Span<T>(ptr, size);
}
template <class T>
Span<T> span(const vector<T> &vec) {
return Span<T>(vec);
}
template <class T>
MutableSpan<T> mutable_span(T *ptr, size_t size) {
return MutableSpan<T>(ptr, size);
}
template <class T>
MutableSpan<T> mutable_span(vector<T> &vec) {
return MutableSpan<T>(vec);
}
template <class T>
Span<T> span_one(const T &value) {
return Span<T>(&value, 1);
}
template <class T>
MutableSpan<T> mutable_span_one(T &value) {
return MutableSpan<T>(&value, 1);
}
template <class T>
Span<T> as_span(Span<T> span) {
return span;
}
template <class T>
Span<T> as_span(const std::vector<T> &vec) {
return Span<T>(vec);
}
template <class T>
MutableSpan<T> as_mutable_span(MutableSpan<T> span) {
return span;
}
template <class T>
MutableSpan<T> as_mutable_span(std::vector<T> &vec) {
return MutableSpan<T>(vec);

View File

@ -118,6 +118,16 @@ bool contains(const V &v, const T &value) {
return false;
}
template <class V, class F>
bool all_of(const V &v, F &&f) {
for (const auto &x : v) {
if (!f(x)) {
return false;
}
}
return true;
}
template <class T>
void reset_to_empty(T &value) {
using std::swap;

View File

@ -6,6 +6,8 @@
//
#include "td/utils/emoji.h"
#include "td/utils/base64.h"
#include "td/utils/Gzip.h"
#include "td/utils/misc.h"
#include <unordered_set>
@ -13,656 +15,211 @@
namespace td {
bool is_emoji(Slice str) {
static const std::unordered_set<Slice, SliceHash> emojis = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "🀄", "🃏", "🆎", "🆑", "🆒", "🆓", "🆔", "🆕", "🆖",
"🆗", "🆘", "🆙", "🆚", "🈁", "🈚", "🈯", "🈲", "🈳", "🈴", "🈵", "🈶", "🈸", "🈹", "🈺", "🉐", "🉑", "🌀", "🌁", "🌂", "🌃", "🌄", "🌅",
"🌆", "🌇", "🌈", "🌉", "🌊", "🌋", "🌌", "🌍", "🌎", "🌏", "🌐", "🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌙", "🌚", "🌛", "🌜",
"🌝", "🌞", "🌟", "🌠", "🌭", "🌮", "🌯", "🌰", "🌱", "🌲", "🌳", "🌴", "🌵", "🌷", "🌸", "🌹", "🌺", "🌻", "🌼", "🌽", "🌾",
"🌿", "🍀", "🍁", "🍂", "🍃", "🍄", "🍅", "🍆", "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓", "🍔", "🍕",
"🍖", "🍗", "🍘", "🍙", "🍚", "🍛", "🍜", "🍝", "🍞", "🍟", "🍠", "🍡", "🍢", "🍣", "🍤", "🍥", "🍦", "🍧", "🍨", "🍩", "🍪", "🍫", "🍬",
"🍭", "🍮", "🍯", "🍰", "🍱", "🍲", "🍳", "🍴", "🍵", "🍶", "🍷", "🍸", "🍹", "🍺", "🍻", "🍼", "🍾", "🍿", "🎀", "🎁", "🎂",
"🎃", "🎄", "🎅", "🎆", "🎇", "🎈", "🎉", "🎊", "🎋", "🎌", "🎍", "🎎", "🎏", "🎐", "🎑", "🎒", "🎓", "🎠", "🎡", "🎢", "🎣", "🎤", "🎥",
"🎦", "🎧", "🎨", "🎩", "🎪", "🎫", "🎬", "🎭", "🎮", "🎯", "🎰", "🎱", "🎲", "🎳", "🎴", "🎵", "🎶", "🎷", "🎸", "🎹", "🎺", "🎻", "🎼",
"🎽", "🎾", "🎿", "🏀", "🏁", "🏂", "🏃", "🏄", "🏅", "🏆", "🏇", "🏈", "🏉", "🏊", "🏏", "🏐", "🏑", "🏒", "🏓",
"🏠", "🏡", "🏢", "🏣", "🏤", "🏥", "🏦", "🏧", "🏨", "🏩", "🏪", "🏫", "🏬", "🏭", "🏮", "🏯", "🏰", "🏴", "🏸", "🏹",
"🏺", "🏻", "🏼", "🏽", "🏾", "🏿", "🐀", "🐁", "🐂", "🐃", "🐄", "🐅", "🐆", "🐇", "🐈", "🐉", "🐊", "🐋", "🐌",
"🐍", "🐎", "🐏", "🐐", "🐑", "🐒", "🐓", "🐔", "🐕", "🐖", "🐗", "🐘", "🐙", "🐚", "🐛", "🐜", "🐝", "🐞", "🐟", "🐠", "🐡", "🐢", "🐣",
"🐤", "🐥", "🐦", "🐧", "🐨", "🐩", "🐪", "🐫", "🐬", "🐭", "🐮", "🐯", "🐰", "🐱", "🐲", "🐳", "🐴", "🐵", "🐶", "🐷", "🐸", "🐹", "🐺",
"🐻", "🐼", "🐽", "🐾", "👀", "👂", "👃", "👄", "👅", "👆", "👇", "👈", "👉", "👊", "👋", "👌", "👍", "👎", "👏", "👐", "👑", "👒", "👓",
"👔", "👕", "👖", "👗", "👘", "👙", "👚", "👛", "👜", "👝", "👞", "👟", "👠", "👡", "👢", "👣", "👤", "👥", "👦", "👧", "👨", "👩", "👪",
"👫", "👬", "👭", "👮", "👯", "👰", "👱", "👲", "👳", "👴", "👵", "👶", "👷", "👸", "👹", "👺", "👻", "👼", "👽", "👾", "👿", "💀", "💁",
"💂", "💃", "💄", "💅", "💆", "💇", "💈", "💉", "💊", "💋", "💌", "💍", "💎", "💏", "💐", "💑", "💒", "💓", "💔", "💕", "💖", "💗", "💘",
"💙", "💚", "💛", "💜", "💝", "💞", "💟", "💠", "💡", "💢", "💣", "💤", "💥", "💦", "💧", "💨", "💩", "💪", "💫", "💬", "💭", "💮", "💯",
"💰", "💱", "💲", "💳", "💴", "💵", "💶", "💷", "💸", "💹", "💺", "💻", "💼", "💽", "💾", "💿", "📀", "📁", "📂", "📃", "📄", "📅", "📆",
"📇", "📈", "📉", "📊", "📋", "📌", "📍", "📎", "📏", "📐", "📑", "📒", "📓", "📔", "📕", "📖", "📗", "📘", "📙", "📚", "📛", "📜", "📝",
"📞", "📟", "📠", "📡", "📢", "📣", "📤", "📥", "📦", "📧", "📨", "📩", "📪", "📫", "📬", "📭", "📮", "📯", "📰", "📱", "📲", "📳", "📴",
"📵", "📶", "📷", "📸", "📹", "📺", "📻", "📼", "📿", "🔀", "🔁", "🔂", "🔃", "🔄", "🔅", "🔆", "🔇", "🔈", "🔉", "🔊", "🔋",
"🔌", "🔍", "🔎", "🔏", "🔐", "🔑", "🔒", "🔓", "🔔", "🔕", "🔖", "🔗", "🔘", "🔙", "🔚", "🔛", "🔜", "🔝", "🔞", "🔟", "🔠", "🔡", "🔢",
"🔣", "🔤", "🔥", "🔦", "🔧", "🔨", "🔩", "🔪", "🔫", "🔬", "🔭", "🔮", "🔯", "🔰", "🔱", "🔲", "🔳", "🔴", "🔵", "🔶", "🔷", "🔸", "🔹",
"🔺", "🔻", "🔼", "🔽", "🕋", "🕌", "🕍", "🕎", "🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚", "🕛",
"🕜", "🕝", "🕞", "🕟", "🕠", "🕡", "🕢", "🕣", "🕤", "🕥", "🕦", "🕧", "🕺", "🖕", "🖖", "🖤", "🗻", "🗼", "🗽", "🗾",
"🗿", "😀", "😁", "😂", "😃", "😄", "😅", "😆", "😇", "😈", "😉", "😊", "😋", "😌", "😍", "😎", "😏", "😐", "😑", "😒", "😓", "😔", "😕",
"😖", "😗", "😘", "😙", "😚", "😛", "😜", "😝", "😞", "😟", "😠", "😡", "😢", "😣", "😤", "😥", "😦", "😧", "😨", "😩", "😪", "😫", "😬",
"😭", "😮", "😯", "😰", "😱", "😲", "😳", "😴", "😵", "😶", "😷", "😸", "😹", "😺", "😻", "😼", "😽", "😾", "😿", "🙀", "🙁",
"🙂", "🙃", "🙄", "🙅", "🙆", "🙇", "🙈", "🙉", "🙊", "🙋", "🙌", "🙍", "🙎", "🙏", "🚀", "🚁", "🚂", "🚃", "🚄", "🚅", "🚆",
"🚇", "🚈", "🚉", "🚊", "🚋", "🚌", "🚍", "🚎", "🚏", "🚐", "🚑", "🚒", "🚓", "🚔", "🚕", "🚖", "🚗", "🚘", "🚙", "🚚", "🚛", "🚜", "🚝",
"🚞", "🚟", "🚠", "🚡", "🚢", "🚣", "🚤", "🚥", "🚦", "🚧", "🚨", "🚩", "🚪", "🚫", "🚬", "🚭", "🚮", "🚯", "🚰", "🚱", "🚲", "🚳", "🚴",
"🚵", "🚶", "🚷", "🚸", "🚹", "🚺", "🚻", "🚼", "🚽", "🚾", "🚿", "🛀", "🛁", "🛂", "🛃", "🛄", "🛅", "🛌", "🛐", "🛑",
"🛒", "🛕", "🛖", "🛗", "🛝", "🛞", "🛟", "🛫", "🛬", "🛴", "🛵", "🛶", "🛷", "🛸",
"🛹", "🛺", "🛻", "🛼", "🟠", "🟡", "🟢", "🟣", "🟤", "🟥", "🟦", "🟧", "🟨", "🟩",
"🟪", "🟫", "🟰", "🤌", "🤍", "🤎", "🤏", "🤐", "🤑", "🤒", "🤓", "🤔", "🤕", "🤖",
"🤗", "🤘", "🤙", "🤚", "🤛", "🤜", "🤝", "🤞", "🤟", "🤠", "🤡", "🤢", "🤣", "🤤",
"🤥", "🤦", "🤧", "🤨", "🤩", "🤪", "🤫", "🤬", "🤭", "🤮", "🤯", "🤰", "🤱", "🤲",
"🤳", "🤴", "🤵", "🤶", "🤷", "🤸", "🤹", "🤺", "🤼", "🤽", "🤾", "🤿", "🥀", "🥁",
"🥂", "🥃", "🥄", "🥅", "🥇", "🥈", "🥉", "🥊", "🥋", "🥌", "🥍", "🥎", "🥏", "🥐",
"🥑", "🥒", "🥓", "🥔", "🥕", "🥖", "🥗", "🥘", "🥙", "🥚", "🥛", "🥜", "🥝", "🥞",
"🥟", "🥠", "🥡", "🥢", "🥣", "🥤", "🥥", "🥦", "🥧", "🥨", "🥩", "🥪", "🥫", "🥬",
"🥭", "🥮", "🥯", "🥰", "🥱", "🥲", "🥳", "🥴", "🥵", "🥶", "🥷", "🥸", "🥹", "🥺",
"🥻", "🥼", "🥽", "🥾", "🥿", "🦀", "🦁", "🦂", "🦃", "🦄", "🦅", "🦆", "🦇", "🦈",
"🦉", "🦊", "🦋", "🦌", "🦍", "🦎", "🦏", "🦐", "🦑", "🦒", "🦓", "🦔", "🦕", "🦖",
"🦗", "🦘", "🦙", "🦚", "🦛", "🦜", "🦝", "🦞", "🦟", "🦠", "🦡", "🦢", "🦣", "🦤",
"🦥", "🦦", "🦧", "🦨", "🦩", "🦪", "🦫", "🦬", "🦭", "🦮", "🦯", "🦰", "🦱", "🦲",
"🦳", "🦴", "🦵", "🦶", "🦷", "🦸", "🦹", "🦺", "🦻", "🦼", "🦽", "🦾", "🦿", "🧀",
"🧁", "🧂", "🧃", "🧄", "🧅", "🧆", "🧇", "🧈", "🧉", "🧊", "🧋", "🧌", "🧍", "🧎",
"🧏", "🧐", "🧑", "🧒", "🧓", "🧔", "🧕", "🧖", "🧗", "🧘", "🧙", "🧚", "🧛", "🧜",
"🧝", "🧞", "🧟", "🧠", "🧡", "🧢", "🧣", "🧤", "🧥", "🧦", "🧧", "🧨", "🧩", "🧪",
"🧫", "🧬", "🧭", "🧮", "🧯", "🧰", "🧱", "🧲", "🧳", "🧴", "🧵", "🧶", "🧷", "🧸",
"🧹", "🧺", "🧻", "🧼", "🧽", "🧾", "🧿", "🩰", "🩱", "🩲", "🩳", "🩴", "🩸", "🩹",
"🩺", "🩻", "🩼", "🪀", "🪁", "🪂", "🪃", "🪄", "🪅", "🪆", "🪐", "🪑", "🪒", "🪓",
"🪔", "🪕", "🪖", "🪗", "🪘", "🪙", "🪚", "🪛", "🪜", "🪝", "🪞", "🪟", "🪠", "🪡",
"🪢", "🪣", "🪤", "🪥", "🪦", "🪧", "🪨", "🪩", "🪪", "🪫", "🪬", "🪰", "🪱", "🪲",
"🪳", "🪴", "🪵", "🪶", "🪷", "🪸", "🪹", "🪺", "🫀", "🫁", "🫂", "🫃", "🫄", "🫅",
"🫐", "🫑", "🫒", "🫓", "🫔", "🫕", "🫖", "🫗", "🫘", "🫙", "🫠", "🫡", "🫢", "🫣",
"🫤", "🫥", "🫦", "🫧", "🫰", "🫱", "🫲", "🫳", "🫴", "🫵", "🫶", "©", "©️", "®",
"®️", "", "‼️", "", "⁉️", "", "™️", "", "", "", "↔️", "", "↕️", "", "↖️",
"", "↗️", "", "↘️", "", "↙️", "", "↩️", "", "↪️", "", "⌨️", "", "⏏️", "",
"⏭️", "", "⏮️", "", "⏯️", "", "⏱️", "", "⏲️", "", "⏸️", "", "⏹️", "",
"⏺️", "", "Ⓜ️", "", "▪️", "", "▫️", "", "▶️", "", "◀️", "", "◻️", "", "◼️",
"", "☀️", "", "☁️", "", "☂️", "", "☃️", "", "☄️", "", "☎️", "", "☑️", "",
"☘️", "", "☝️", "", "☠️", "", "☢️", "", "☣️", "", "☦️", "", "☪️", "", "☮️",
"", "☯️", "", "☸️", "", "☹️", "", "☺️", "", "♀️", "", "♂️", "", "♟️", "",
"♠️", "", "♣️", "", "♥️", "", "♦️", "", "♨️", "", "♻️", "", "♾️", "", "⚒️",
"", "⚔️", "", "⚕️", "", "⚖️", "", "⚗️", "", "⚙️", "", "⚛️", "", "⚜️", "",
"⚠️", "", "⚧️", "", "⚰️", "", "⚱️", "", "⛈️", "", "⛏️", "", "⛑️", "", "⛓️",
"", "⛩️", "", "⛰️", "", "⛱️", "", "⛴️", "", "⛷️", "", "⛸️", "", "⛹️", "",
"✂️", "", "✈️", "", "✉️", "", "✌️", "", "✍️", "", "✏️", "", "✒️", "", "✔️",
"", "✖️", "", "✝️", "", "✡️", "", "✳️", "", "✴️", "", "❄️", "", "❇️", "",
"❣️", "", "❤️", "", "➡️", "", "⤴️", "", "⤵️", "", "⬅️", "", "⬆️", "", "⬇️",
"", "〰️", "", "〽️", "", "㊗️", "", "㊙️", "🅰", "🅰️", "🅱", "🅱️", "🅾", "🅾️",
"🅿", "🅿️", "🈂", "🈂️", "🈷", "🈷️", "🌡", "🌡️", "🌤", "🌤️", "🌥", "🌥️", "🌦",
"🌦️", "🌧", "🌧️", "🌨", "🌨️", "🌩", "🌩️", "🌪", "🌪️", "🌫", "🌫️", "🌬",
"🌬️", "🌶", "🌶️", "🍽", "🍽️", "🎖", "🎖️", "🎗", "🎗️", "🎙", "🎙️", "🎚",
"🎚️", "🎛", "🎛️", "🎞", "🎞️", "🎟", "🎟️", "🏋", "🏋️", "🏌", "🏌️", "🏍",
"🏍️", "🏎", "🏎️", "🏔", "🏔️", "🏕", "🏕️", "🏖", "🏖️", "🏗", "🏗️", "🏘",
"🏘️", "🏙", "🏙️", "🏚", "🏚️", "🏛", "🏛️", "🏜", "🏜️", "🏝", "🏝️", "🏞",
"🏞️", "🏟", "🏟️", "🏳", "🏳️", "🏵", "🏵️", "🏷", "🏷️", "🐿", "🐿️", "👁",
"👁️", "📽", "📽️", "🕉", "🕉️", "🕊", "🕊️", "🕯", "🕯️", "🕰", "🕰️", "🕳",
"🕳️", "🕴", "🕴️", "🕵", "🕵️", "🕶", "🕶️", "🕷", "🕷️", "🕸", "🕸️", "🕹",
"🕹️", "🖇", "🖇️", "🖊", "🖊️", "🖋", "🖋️", "🖌", "🖌️", "🖍", "🖍️", "🖐",
"🖐️", "🖥", "🖥️", "🖨", "🖨️", "🖱", "🖱️", "🖲", "🖲️", "🖼", "🖼️", "🗂",
"🗂️", "🗃", "🗃️", "🗄", "🗄️", "🗑", "🗑️", "🗒", "🗒️", "🗓", "🗓️", "🗜",
"🗜️", "🗝", "🗝️", "🗞", "🗞️", "🗡", "🗡️", "🗣", "🗣️", "🗨", "🗨️", "🗯",
"🗯️", "🗳", "🗳️", "🗺", "🗺️", "🛋", "🛋️", "🛍", "🛍️", "🛎", "🛎️", "🛏",
"🛏️", "🛠", "🛠️", "🛡", "🛡️", "🛢", "🛢️", "🛣", "🛣️", "🛤", "🛤️", "🛥",
"🛥️", "🛩", "🛩️", "🛰", "🛰️", "🛳", "🛳️", "#⃣", "#️⃣", "*⃣", "*️⃣", "0⃣",
"0", "1⃣", "1", "2⃣", "2", "3⃣", "3", "4⃣", "4", "5⃣", "5", "6⃣", "6", "7⃣",
"7", "8⃣", "8", "9⃣", "9", "🇦🇨", "🇦🇩", "🇦🇪", "🇦🇫", "🇦🇬", "🇦🇮", "🇦🇱", "🇦🇲", "🇦🇴", "🇦🇶", "🇦🇷", "🇦🇸",
"🇦🇹", "🇦🇺", "🇦🇼", "🇦🇽", "🇦🇿", "🇧🇦", "🇧🇧", "🇧🇩", "🇧🇪", "🇧🇫", "🇧🇬", "🇧🇭", "🇧🇮", "🇧🇯", "🇧🇱", "🇧🇲", "🇧🇳", "🇧🇴", "🇧🇶",
"🇧🇷", "🇧🇸", "🇧🇹", "🇧🇻", "🇧🇼", "🇧🇾", "🇧🇿", "🇨🇦", "🇨🇨", "🇨🇩", "🇨🇫", "🇨🇬", "🇨🇭", "🇨🇮", "🇨🇰", "🇨🇱", "🇨🇲", "🇨🇳", "🇨🇴",
"🇨🇵", "🇨🇷", "🇨🇺", "🇨🇻", "🇨🇼", "🇨🇽", "🇨🇾", "🇨🇿", "🇩🇪", "🇩🇬", "🇩🇯", "🇩🇰", "🇩🇲", "🇩🇴", "🇩🇿", "🇪🇦", "🇪🇨", "🇪🇪", "🇪🇬",
"🇪🇭", "🇪🇷", "🇪🇸", "🇪🇹", "🇪🇺", "🇫🇮", "🇫🇯", "🇫🇰", "🇫🇲", "🇫🇴", "🇫🇷", "🇬🇦", "🇬🇧", "🇬🇩", "🇬🇪", "🇬🇫", "🇬🇬", "🇬🇭", "🇬🇮",
"🇬🇱", "🇬🇲", "🇬🇳", "🇬🇵", "🇬🇶", "🇬🇷", "🇬🇸", "🇬🇹", "🇬🇺", "🇬🇼", "🇬🇾", "🇭🇰", "🇭🇲", "🇭🇳", "🇭🇷", "🇭🇹", "🇭🇺", "🇮🇨", "🇮🇩",
"🇮🇪", "🇮🇱", "🇮🇲", "🇮🇳", "🇮🇴", "🇮🇶", "🇮🇷", "🇮🇸", "🇮🇹", "🇯🇪", "🇯🇲", "🇯🇴", "🇯🇵", "🇰🇪", "🇰🇬", "🇰🇭", "🇰🇮", "🇰🇲", "🇰🇳",
"🇰🇵", "🇰🇷", "🇰🇼", "🇰🇾", "🇰🇿", "🇱🇦", "🇱🇧", "🇱🇨", "🇱🇮", "🇱🇰", "🇱🇷", "🇱🇸", "🇱🇹", "🇱🇺", "🇱🇻", "🇱🇾", "🇲🇦", "🇲🇨", "🇲🇩",
"🇲🇪", "🇲🇫", "🇲🇬", "🇲🇭", "🇲🇰", "🇲🇱", "🇲🇲", "🇲🇳", "🇲🇴", "🇲🇵", "🇲🇶", "🇲🇷", "🇲🇸", "🇲🇹", "🇲🇺", "🇲🇻", "🇲🇼", "🇲🇽", "🇲🇾",
"🇲🇿", "🇳🇦", "🇳🇨", "🇳🇪", "🇳🇫", "🇳🇬", "🇳🇮", "🇳🇱", "🇳🇴", "🇳🇵", "🇳🇷", "🇳🇺", "🇳🇿", "🇴🇲", "🇵🇦", "🇵🇪", "🇵🇫", "🇵🇬", "🇵🇭",
"🇵🇰", "🇵🇱", "🇵🇲", "🇵🇳", "🇵🇷", "🇵🇸", "🇵🇹", "🇵🇼", "🇵🇾", "🇶🇦", "🇷🇪", "🇷🇴", "🇷🇸", "🇷🇺", "🇷🇼", "🇸🇦", "🇸🇧", "🇸🇨", "🇸🇩",
"🇸🇪", "🇸🇬", "🇸🇭", "🇸🇮", "🇸🇯", "🇸🇰", "🇸🇱", "🇸🇲", "🇸🇳", "🇸🇴", "🇸🇷", "🇸🇸", "🇸🇹", "🇸🇻", "🇸🇽", "🇸🇾", "🇸🇿", "🇹🇦", "🇹🇨",
"🇹🇩", "🇹🇫", "🇹🇬", "🇹🇭", "🇹🇯", "🇹🇰", "🇹🇱", "🇹🇲", "🇹🇳", "🇹🇴", "🇹🇷", "🇹🇹", "🇹🇻", "🇹🇼", "🇹🇿", "🇺🇦", "🇺🇬", "🇺🇲", "🇺🇳",
"🇺🇸", "🇺🇾", "🇺🇿", "🇻🇦", "🇻🇨", "🇻🇪", "🇻🇬", "🇻🇮", "🇻🇳", "🇻🇺", "🇼🇫", "🇼🇸", "🇽🇰", "🇾🇪", "🇾🇹", "🇿🇦", "🇿🇲", "🇿🇼",
"🏴󠁧󠁢󠁥󠁮󠁧󠁿", "🏴󠁧󠁢󠁳󠁣󠁴󠁿", "🏴󠁧󠁢󠁷󠁬󠁳󠁿", "☝🏻",
"☝🏼", "☝🏽", "☝🏾", "☝🏿", "⛹🏻", "⛹🏼", "⛹🏽", "⛹🏾", "⛹🏿", "✊🏻",
"✊🏼", "✊🏽", "✊🏾", "✊🏿", "✋🏻", "✋🏼", "✋🏽", "✋🏾", "✋🏿", "✌🏻",
"✌🏼", "✌🏽", "✌🏾", "✌🏿", "✍🏻", "✍🏼", "✍🏽", "✍🏾", "✍🏿", "🎅🏻",
"🎅🏼", "🎅🏽", "🎅🏾", "🎅🏿", "🏂🏻", "🏂🏼", "🏂🏽", "🏂🏾", "🏂🏿",
"🏃🏻", "🏃🏼", "🏃🏽", "🏃🏾", "🏃🏿", "🏄🏻", "🏄🏼", "🏄🏽", "🏄🏾",
"🏄🏿", "🏇🏻", "🏇🏼", "🏇🏽", "🏇🏾", "🏇🏿", "🏊🏻", "🏊🏼", "🏊🏽",
"🏊🏾", "🏊🏿", "🏋🏻", "🏋🏼", "🏋🏽", "🏋🏾", "🏋🏿", "🏌🏻", "🏌🏼",
"🏌🏽", "🏌🏾", "🏌🏿", "👂🏻", "👂🏼", "👂🏽", "👂🏾", "👂🏿", "👃🏻",
"👃🏼", "👃🏽", "👃🏾", "👃🏿", "👆🏻", "👆🏼", "👆🏽", "👆🏾", "👆🏿",
"👇🏻", "👇🏼", "👇🏽", "👇🏾", "👇🏿", "👈🏻", "👈🏼", "👈🏽", "👈🏾",
"👈🏿", "👉🏻", "👉🏼", "👉🏽", "👉🏾", "👉🏿", "👊🏻", "👊🏼", "👊🏽",
"👊🏾", "👊🏿", "👋🏻", "👋🏼", "👋🏽", "👋🏾", "👋🏿", "👌🏻", "👌🏼",
"👌🏽", "👌🏾", "👌🏿", "👍🏻", "👍🏼", "👍🏽", "👍🏾", "👍🏿", "👎🏻",
"👎🏼", "👎🏽", "👎🏾", "👎🏿", "👏🏻", "👏🏼", "👏🏽", "👏🏾", "👏🏿",
"👐🏻", "👐🏼", "👐🏽", "👐🏾", "👐🏿", "👦🏻", "👦🏼", "👦🏽", "👦🏾",
"👦🏿", "👧🏻", "👧🏼", "👧🏽", "👧🏾", "👧🏿", "👨🏻", "👨🏼", "👨🏽",
"👨🏾", "👨🏿", "👩🏻", "👩🏼", "👩🏽", "👩🏾", "👩🏿", "👫🏻", "👫🏼",
"👫🏽", "👫🏾", "👫🏿", "👬🏻", "👬🏼", "👬🏽", "👬🏾", "👬🏿", "👭🏻",
"👭🏼", "👭🏽", "👭🏾", "👭🏿", "👮🏻", "👮🏼", "👮🏽", "👮🏾", "👮🏿",
"👰🏻", "👰🏼", "👰🏽", "👰🏾", "👰🏿", "👱🏻", "👱🏼", "👱🏽", "👱🏾",
"👱🏿", "👲🏻", "👲🏼", "👲🏽", "👲🏾", "👲🏿", "👳🏻", "👳🏼", "👳🏽",
"👳🏾", "👳🏿", "👴🏻", "👴🏼", "👴🏽", "👴🏾", "👴🏿", "👵🏻", "👵🏼",
"👵🏽", "👵🏾", "👵🏿", "👶🏻", "👶🏼", "👶🏽", "👶🏾", "👶🏿", "👷🏻",
"👷🏼", "👷🏽", "👷🏾", "👷🏿", "👸🏻", "👸🏼", "👸🏽", "👸🏾", "👸🏿",
"👼🏻", "👼🏼", "👼🏽", "👼🏾", "👼🏿", "💁🏻", "💁🏼", "💁🏽", "💁🏾",
"💁🏿", "💂🏻", "💂🏼", "💂🏽", "💂🏾", "💂🏿", "💃🏻", "💃🏼", "💃🏽",
"💃🏾", "💃🏿", "💅🏻", "💅🏼", "💅🏽", "💅🏾", "💅🏿", "💆🏻", "💆🏼",
"💆🏽", "💆🏾", "💆🏿", "💇🏻", "💇🏼", "💇🏽", "💇🏾", "💇🏿", "💏🏻",
"💏🏼", "💏🏽", "💏🏾", "💏🏿", "💑🏻", "💑🏼", "💑🏽", "💑🏾", "💑🏿",
"💪🏻", "💪🏼", "💪🏽", "💪🏾", "💪🏿", "🕴🏻", "🕴🏼", "🕴🏽", "🕴🏾",
"🕴🏿", "🕵🏻", "🕵🏼", "🕵🏽", "🕵🏾", "🕵🏿", "🕺🏻", "🕺🏼", "🕺🏽",
"🕺🏾", "🕺🏿", "🖐🏻", "🖐🏼", "🖐🏽", "🖐🏾", "🖐🏿", "🖕🏻", "🖕🏼",
"🖕🏽", "🖕🏾", "🖕🏿", "🖖🏻", "🖖🏼", "🖖🏽", "🖖🏾", "🖖🏿", "🙅🏻",
"🙅🏼", "🙅🏽", "🙅🏾", "🙅🏿", "🙆🏻", "🙆🏼", "🙆🏽", "🙆🏾", "🙆🏿",
"🙇🏻", "🙇🏼", "🙇🏽", "🙇🏾", "🙇🏿", "🙋🏻", "🙋🏼", "🙋🏽", "🙋🏾",
"🙋🏿", "🙌🏻", "🙌🏼", "🙌🏽", "🙌🏾", "🙌🏿", "🙍🏻", "🙍🏼", "🙍🏽",
"🙍🏾", "🙍🏿", "🙎🏻", "🙎🏼", "🙎🏽", "🙎🏾", "🙎🏿", "🙏🏻", "🙏🏼",
"🙏🏽", "🙏🏾", "🙏🏿", "🚣🏻", "🚣🏼", "🚣🏽", "🚣🏾", "🚣🏿", "🚴🏻",
"🚴🏼", "🚴🏽", "🚴🏾", "🚴🏿", "🚵🏻", "🚵🏼", "🚵🏽", "🚵🏾", "🚵🏿",
"🚶🏻", "🚶🏼", "🚶🏽", "🚶🏾", "🚶🏿", "🛀🏻", "🛀🏼", "🛀🏽", "🛀🏾",
"🛀🏿", "🛌🏻", "🛌🏼", "🛌🏽", "🛌🏾", "🛌🏿", "🤌🏻", "🤌🏼", "🤌🏽",
"🤌🏾", "🤌🏿", "🤏🏻", "🤏🏼", "🤏🏽", "🤏🏾", "🤏🏿", "🤘🏻", "🤘🏼",
"🤘🏽", "🤘🏾", "🤘🏿", "🤙🏻", "🤙🏼", "🤙🏽", "🤙🏾", "🤙🏿", "🤚🏻",
"🤚🏼", "🤚🏽", "🤚🏾", "🤚🏿", "🤛🏻", "🤛🏼", "🤛🏽", "🤛🏾", "🤛🏿",
"🤜🏻", "🤜🏼", "🤜🏽", "🤜🏾", "🤜🏿", "🤝🏻", "🤝🏼", "🤝🏽", "🤝🏾",
"🤝🏿", "🤞🏻", "🤞🏼", "🤞🏽", "🤞🏾", "🤞🏿", "🤟🏻", "🤟🏼", "🤟🏽",
"🤟🏾", "🤟🏿", "🤦🏻", "🤦🏼", "🤦🏽", "🤦🏾", "🤦🏿", "🤰🏻", "🤰🏼",
"🤰🏽", "🤰🏾", "🤰🏿", "🤱🏻", "🤱🏼", "🤱🏽", "🤱🏾", "🤱🏿", "🤲🏻",
"🤲🏼", "🤲🏽", "🤲🏾", "🤲🏿", "🤳🏻", "🤳🏼", "🤳🏽", "🤳🏾", "🤳🏿",
"🤴🏻", "🤴🏼", "🤴🏽", "🤴🏾", "🤴🏿", "🤵🏻", "🤵🏼", "🤵🏽", "🤵🏾",
"🤵🏿", "🤶🏻", "🤶🏼", "🤶🏽", "🤶🏾", "🤶🏿", "🤷🏻", "🤷🏼", "🤷🏽",
"🤷🏾", "🤷🏿", "🤸🏻", "🤸🏼", "🤸🏽", "🤸🏾", "🤸🏿", "🤹🏻", "🤹🏼",
"🤹🏽", "🤹🏾", "🤹🏿", "🤽🏻", "🤽🏼", "🤽🏽", "🤽🏾", "🤽🏿", "🤾🏻",
"🤾🏼", "🤾🏽", "🤾🏾", "🤾🏿", "🥷🏻", "🥷🏼", "🥷🏽", "🥷🏾", "🥷🏿",
"🦵🏻", "🦵🏼", "🦵🏽", "🦵🏾", "🦵🏿", "🦶🏻", "🦶🏼", "🦶🏽", "🦶🏾",
"🦶🏿", "🦸🏻", "🦸🏼", "🦸🏽", "🦸🏾", "🦸🏿", "🦹🏻", "🦹🏼", "🦹🏽",
"🦹🏾", "🦹🏿", "🦻🏻", "🦻🏼", "🦻🏽", "🦻🏾", "🦻🏿", "🧍🏻", "🧍🏼",
"🧍🏽", "🧍🏾", "🧍🏿", "🧎🏻", "🧎🏼", "🧎🏽", "🧎🏾", "🧎🏿", "🧏🏻",
"🧏🏼", "🧏🏽", "🧏🏾", "🧏🏿", "🧑🏻", "🧑🏼", "🧑🏽", "🧑🏾", "🧑🏿",
"🧒🏻", "🧒🏼", "🧒🏽", "🧒🏾", "🧒🏿", "🧓🏻", "🧓🏼", "🧓🏽", "🧓🏾",
"🧓🏿", "🧔🏻", "🧔🏼", "🧔🏽", "🧔🏾", "🧔🏿", "🧕🏻", "🧕🏼", "🧕🏽",
"🧕🏾", "🧕🏿", "🧖🏻", "🧖🏼", "🧖🏽", "🧖🏾", "🧖🏿", "🧗🏻", "🧗🏼",
"🧗🏽", "🧗🏾", "🧗🏿", "🧘🏻", "🧘🏼", "🧘🏽", "🧘🏾", "🧘🏿", "🧙🏻",
"🧙🏼", "🧙🏽", "🧙🏾", "🧙🏿", "🧚🏻", "🧚🏼", "🧚🏽", "🧚🏾", "🧚🏿",
"🧛🏻", "🧛🏼", "🧛🏽", "🧛🏾", "🧛🏿", "🧜🏻", "🧜🏼", "🧜🏽", "🧜🏾",
"🧜🏿", "🧝🏻", "🧝🏼", "🧝🏽", "🧝🏾", "🧝🏿", "🫃🏻", "🫃🏼", "🫃🏽",
"🫃🏾", "🫃🏿", "🫄🏻", "🫄🏼", "🫄🏽", "🫄🏾", "🫄🏿", "🫅🏻", "🫅🏼",
"🫅🏽", "🫅🏾", "🫅🏿", "🫰🏻", "🫰🏼", "🫰🏽", "🫰🏾", "🫰🏿", "🫱🏻",
"🫱🏼", "🫱🏽", "🫱🏾", "🫱🏿", "🫲🏻", "🫲🏼", "🫲🏽", "🫲🏾", "🫲🏿",
"🫳🏻", "🫳🏼", "🫳🏽", "🫳🏾", "🫳🏿", "🫴🏻", "🫴🏼", "🫴🏽", "🫴🏾",
"🫴🏿", "🫵🏻", "🫵🏼", "🫵🏽", "🫵🏾", "🫵🏿", "🫶🏻", "🫶🏼", "🫶🏽",
"🫶🏾", "🫶🏿", "👨‍❤‍👨", "👨‍❤️‍👨", "👨‍❤‍💋‍👨",
"👨‍❤️‍💋‍👨", "👨‍👦", "👨‍👦‍👦", "👨‍👧", "👨‍👧‍👦",
"👨‍👧‍👧", "👨‍👨‍👦", "👨‍👨‍👦‍👦", "👨‍👨‍👧",
"👨‍👨‍👧‍👦", "👨‍👨‍👧‍👧", "👨‍👩‍👦", "👨‍👩‍👦‍👦",
"👨‍👩‍👧", "👨‍👩‍👧‍👦", "👨‍👩‍👧‍👧", "👨🏻‍❤‍👨🏻",
"👨🏻‍❤️‍👨🏻", "👨🏻‍❤‍👨🏼", "👨🏻‍❤️‍👨🏼",
"👨🏻‍❤‍👨🏽", "👨🏻‍❤️‍👨🏽", "👨🏻‍❤‍👨🏾",
"👨🏻‍❤️‍👨🏾", "👨🏻‍❤‍👨🏿", "👨🏻‍❤️‍👨🏿",
"👨🏻‍❤‍💋‍👨🏻", "👨🏻‍❤️‍💋‍👨🏻", "👨🏻‍❤‍💋‍👨🏼",
"👨🏻‍❤️‍💋‍👨🏼", "👨🏻‍❤‍💋‍👨🏽", "👨🏻‍❤️‍💋‍👨🏽",
"👨🏻‍❤‍💋‍👨🏾", "👨🏻‍❤️‍💋‍👨🏾", "👨🏻‍❤‍💋‍👨🏿",
"👨🏻‍❤️‍💋‍👨🏿", "👨🏻‍🤝‍👨🏼", "👨🏻‍🤝‍👨🏽",
"👨🏻‍🤝‍👨🏾", "👨🏻‍🤝‍👨🏿", "👨🏼‍❤‍👨🏻",
"👨🏼‍❤️‍👨🏻", "👨🏼‍❤‍👨🏼", "👨🏼‍❤️‍👨🏼",
"👨🏼‍❤‍👨🏽", "👨🏼‍❤️‍👨🏽", "👨🏼‍❤‍👨🏾",
"👨🏼‍❤️‍👨🏾", "👨🏼‍❤‍👨🏿", "👨🏼‍❤️‍👨🏿",
"👨🏼‍❤‍💋‍👨🏻", "👨🏼‍❤️‍💋‍👨🏻", "👨🏼‍❤‍💋‍👨🏼",
"👨🏼‍❤️‍💋‍👨🏼", "👨🏼‍❤‍💋‍👨🏽", "👨🏼‍❤️‍💋‍👨🏽",
"👨🏼‍❤‍💋‍👨🏾", "👨🏼‍❤️‍💋‍👨🏾", "👨🏼‍❤‍💋‍👨🏿",
"👨🏼‍❤️‍💋‍👨🏿", "👨🏼‍🤝‍👨🏻", "👨🏼‍🤝‍👨🏽",
"👨🏼‍🤝‍👨🏾", "👨🏼‍🤝‍👨🏿", "👨🏽‍❤‍👨🏻",
"👨🏽‍❤️‍👨🏻", "👨🏽‍❤‍👨🏼", "👨🏽‍❤️‍👨🏼",
"👨🏽‍❤‍👨🏽", "👨🏽‍❤️‍👨🏽", "👨🏽‍❤‍👨🏾",
"👨🏽‍❤️‍👨🏾", "👨🏽‍❤‍👨🏿", "👨🏽‍❤️‍👨🏿",
"👨🏽‍❤‍💋‍👨🏻", "👨🏽‍❤️‍💋‍👨🏻", "👨🏽‍❤‍💋‍👨🏼",
"👨🏽‍❤️‍💋‍👨🏼", "👨🏽‍❤‍💋‍👨🏽", "👨🏽‍❤️‍💋‍👨🏽",
"👨🏽‍❤‍💋‍👨🏾", "👨🏽‍❤️‍💋‍👨🏾", "👨🏽‍❤‍💋‍👨🏿",
"👨🏽‍❤️‍💋‍👨🏿", "👨🏽‍🤝‍👨🏻", "👨🏽‍🤝‍👨🏼",
"👨🏽‍🤝‍👨🏾", "👨🏽‍🤝‍👨🏿", "👨🏾‍❤‍👨🏻",
"👨🏾‍❤️‍👨🏻", "👨🏾‍❤‍👨🏼", "👨🏾‍❤️‍👨🏼",
"👨🏾‍❤‍👨🏽", "👨🏾‍❤️‍👨🏽", "👨🏾‍❤‍👨🏾",
"👨🏾‍❤️‍👨🏾", "👨🏾‍❤‍👨🏿", "👨🏾‍❤️‍👨🏿",
"👨🏾‍❤‍💋‍👨🏻", "👨🏾‍❤️‍💋‍👨🏻", "👨🏾‍❤‍💋‍👨🏼",
"👨🏾‍❤️‍💋‍👨🏼", "👨🏾‍❤‍💋‍👨🏽", "👨🏾‍❤️‍💋‍👨🏽",
"👨🏾‍❤‍💋‍👨🏾", "👨🏾‍❤️‍💋‍👨🏾", "👨🏾‍❤‍💋‍👨🏿",
"👨🏾‍❤️‍💋‍👨🏿", "👨🏾‍🤝‍👨🏻", "👨🏾‍🤝‍👨🏼",
"👨🏾‍🤝‍👨🏽", "👨🏾‍🤝‍👨🏿", "👨🏿‍❤‍👨🏻",
"👨🏿‍❤️‍👨🏻", "👨🏿‍❤‍👨🏼", "👨🏿‍❤️‍👨🏼",
"👨🏿‍❤‍👨🏽", "👨🏿‍❤️‍👨🏽", "👨🏿‍❤‍👨🏾",
"👨🏿‍❤️‍👨🏾", "👨🏿‍❤‍👨🏿", "👨🏿‍❤️‍👨🏿",
"👨🏿‍❤‍💋‍👨🏻", "👨🏿‍❤️‍💋‍👨🏻", "👨🏿‍❤‍💋‍👨🏼",
"👨🏿‍❤️‍💋‍👨🏼", "👨🏿‍❤‍💋‍👨🏽", "👨🏿‍❤️‍💋‍👨🏽",
"👨🏿‍❤‍💋‍👨🏾", "👨🏿‍❤️‍💋‍👨🏾", "👨🏿‍❤‍💋‍👨🏿",
"👨🏿‍❤️‍💋‍👨🏿", "👨🏿‍🤝‍👨🏻", "👨🏿‍🤝‍👨🏼",
"👨🏿‍🤝‍👨🏽", "👨🏿‍🤝‍👨🏾", "👩‍❤‍👨", "👩‍❤️‍👨",
"👩‍❤‍👩", "👩‍❤️‍👩", "👩‍❤‍💋‍👨", "👩‍❤️‍💋‍👨",
"👩‍❤‍💋‍👩", "👩‍❤️‍💋‍👩", "👩‍👦", "👩‍👦‍👦", "👩‍👧",
"👩‍👧‍👦", "👩‍👧‍👧", "👩‍👩‍👦", "👩‍👩‍👦‍👦",
"👩‍👩‍👧", "👩‍👩‍👧‍👦", "👩‍👩‍👧‍👧", "👩🏻‍❤‍👨🏻",
"👩🏻‍❤️‍👨🏻", "👩🏻‍❤‍👨🏼", "👩🏻‍❤️‍👨🏼",
"👩🏻‍❤‍👨🏽", "👩🏻‍❤️‍👨🏽", "👩🏻‍❤‍👨🏾",
"👩🏻‍❤️‍👨🏾", "👩🏻‍❤‍👨🏿", "👩🏻‍❤️‍👨🏿",
"👩🏻‍❤‍👩🏻", "👩🏻‍❤️‍👩🏻", "👩🏻‍❤‍👩🏼",
"👩🏻‍❤️‍👩🏼", "👩🏻‍❤‍👩🏽", "👩🏻‍❤️‍👩🏽",
"👩🏻‍❤‍👩🏾", "👩🏻‍❤️‍👩🏾", "👩🏻‍❤‍👩🏿",
"👩🏻‍❤️‍👩🏿", "👩🏻‍❤‍💋‍👨🏻", "👩🏻‍❤️‍💋‍👨🏻",
"👩🏻‍❤‍💋‍👨🏼", "👩🏻‍❤️‍💋‍👨🏼", "👩🏻‍❤‍💋‍👨🏽",
"👩🏻‍❤️‍💋‍👨🏽", "👩🏻‍❤‍💋‍👨🏾", "👩🏻‍❤️‍💋‍👨🏾",
"👩🏻‍❤‍💋‍👨🏿", "👩🏻‍❤️‍💋‍👨🏿", "👩🏻‍❤‍💋‍👩🏻",
"👩🏻‍❤️‍💋‍👩🏻", "👩🏻‍❤‍💋‍👩🏼", "👩🏻‍❤️‍💋‍👩🏼",
"👩🏻‍❤‍💋‍👩🏽", "👩🏻‍❤️‍💋‍👩🏽", "👩🏻‍❤‍💋‍👩🏾",
"👩🏻‍❤️‍💋‍👩🏾", "👩🏻‍❤‍💋‍👩🏿", "👩🏻‍❤️‍💋‍👩🏿",
"👩🏻‍🤝‍👨🏼", "👩🏻‍🤝‍👨🏽", "👩🏻‍🤝‍👨🏾",
"👩🏻‍🤝‍👨🏿", "👩🏻‍🤝‍👩🏼", "👩🏻‍🤝‍👩🏽",
"👩🏻‍🤝‍👩🏾", "👩🏻‍🤝‍👩🏿", "👩🏼‍❤‍👨🏻",
"👩🏼‍❤️‍👨🏻", "👩🏼‍❤‍👨🏼", "👩🏼‍❤️‍👨🏼",
"👩🏼‍❤‍👨🏽", "👩🏼‍❤️‍👨🏽", "👩🏼‍❤‍👨🏾",
"👩🏼‍❤️‍👨🏾", "👩🏼‍❤‍👨🏿", "👩🏼‍❤️‍👨🏿",
"👩🏼‍❤‍👩🏻", "👩🏼‍❤️‍👩🏻", "👩🏼‍❤‍👩🏼",
"👩🏼‍❤️‍👩🏼", "👩🏼‍❤‍👩🏽", "👩🏼‍❤️‍👩🏽",
"👩🏼‍❤‍👩🏾", "👩🏼‍❤️‍👩🏾", "👩🏼‍❤‍👩🏿",
"👩🏼‍❤️‍👩🏿", "👩🏼‍❤‍💋‍👨🏻", "👩🏼‍❤️‍💋‍👨🏻",
"👩🏼‍❤‍💋‍👨🏼", "👩🏼‍❤️‍💋‍👨🏼", "👩🏼‍❤‍💋‍👨🏽",
"👩🏼‍❤️‍💋‍👨🏽", "👩🏼‍❤‍💋‍👨🏾", "👩🏼‍❤️‍💋‍👨🏾",
"👩🏼‍❤‍💋‍👨🏿", "👩🏼‍❤️‍💋‍👨🏿", "👩🏼‍❤‍💋‍👩🏻",
"👩🏼‍❤️‍💋‍👩🏻", "👩🏼‍❤‍💋‍👩🏼", "👩🏼‍❤️‍💋‍👩🏼",
"👩🏼‍❤‍💋‍👩🏽", "👩🏼‍❤️‍💋‍👩🏽", "👩🏼‍❤‍💋‍👩🏾",
"👩🏼‍❤️‍💋‍👩🏾", "👩🏼‍❤‍💋‍👩🏿", "👩🏼‍❤️‍💋‍👩🏿",
"👩🏼‍🤝‍👨🏻", "👩🏼‍🤝‍👨🏽", "👩🏼‍🤝‍👨🏾",
"👩🏼‍🤝‍👨🏿", "👩🏼‍🤝‍👩🏻", "👩🏼‍🤝‍👩🏽",
"👩🏼‍🤝‍👩🏾", "👩🏼‍🤝‍👩🏿", "👩🏽‍❤‍👨🏻",
"👩🏽‍❤️‍👨🏻", "👩🏽‍❤‍👨🏼", "👩🏽‍❤️‍👨🏼",
"👩🏽‍❤‍👨🏽", "👩🏽‍❤️‍👨🏽", "👩🏽‍❤‍👨🏾",
"👩🏽‍❤️‍👨🏾", "👩🏽‍❤‍👨🏿", "👩🏽‍❤️‍👨🏿",
"👩🏽‍❤‍👩🏻", "👩🏽‍❤️‍👩🏻", "👩🏽‍❤‍👩🏼",
"👩🏽‍❤️‍👩🏼", "👩🏽‍❤‍👩🏽", "👩🏽‍❤️‍👩🏽",
"👩🏽‍❤‍👩🏾", "👩🏽‍❤️‍👩🏾", "👩🏽‍❤‍👩🏿",
"👩🏽‍❤️‍👩🏿", "👩🏽‍❤‍💋‍👨🏻", "👩🏽‍❤️‍💋‍👨🏻",
"👩🏽‍❤‍💋‍👨🏼", "👩🏽‍❤️‍💋‍👨🏼", "👩🏽‍❤‍💋‍👨🏽",
"👩🏽‍❤️‍💋‍👨🏽", "👩🏽‍❤‍💋‍👨🏾", "👩🏽‍❤️‍💋‍👨🏾",
"👩🏽‍❤‍💋‍👨🏿", "👩🏽‍❤️‍💋‍👨🏿", "👩🏽‍❤‍💋‍👩🏻",
"👩🏽‍❤️‍💋‍👩🏻", "👩🏽‍❤‍💋‍👩🏼", "👩🏽‍❤️‍💋‍👩🏼",
"👩🏽‍❤‍💋‍👩🏽", "👩🏽‍❤️‍💋‍👩🏽", "👩🏽‍❤‍💋‍👩🏾",
"👩🏽‍❤️‍💋‍👩🏾", "👩🏽‍❤‍💋‍👩🏿", "👩🏽‍❤️‍💋‍👩🏿",
"👩🏽‍🤝‍👨🏻", "👩🏽‍🤝‍👨🏼", "👩🏽‍🤝‍👨🏾",
"👩🏽‍🤝‍👨🏿", "👩🏽‍🤝‍👩🏻", "👩🏽‍🤝‍👩🏼",
"👩🏽‍🤝‍👩🏾", "👩🏽‍🤝‍👩🏿", "👩🏾‍❤‍👨🏻",
"👩🏾‍❤️‍👨🏻", "👩🏾‍❤‍👨🏼", "👩🏾‍❤️‍👨🏼",
"👩🏾‍❤‍👨🏽", "👩🏾‍❤️‍👨🏽", "👩🏾‍❤‍👨🏾",
"👩🏾‍❤️‍👨🏾", "👩🏾‍❤‍👨🏿", "👩🏾‍❤️‍👨🏿",
"👩🏾‍❤‍👩🏻", "👩🏾‍❤️‍👩🏻", "👩🏾‍❤‍👩🏼",
"👩🏾‍❤️‍👩🏼", "👩🏾‍❤‍👩🏽", "👩🏾‍❤️‍👩🏽",
"👩🏾‍❤‍👩🏾", "👩🏾‍❤️‍👩🏾", "👩🏾‍❤‍👩🏿",
"👩🏾‍❤️‍👩🏿", "👩🏾‍❤‍💋‍👨🏻", "👩🏾‍❤️‍💋‍👨🏻",
"👩🏾‍❤‍💋‍👨🏼", "👩🏾‍❤️‍💋‍👨🏼", "👩🏾‍❤‍💋‍👨🏽",
"👩🏾‍❤️‍💋‍👨🏽", "👩🏾‍❤‍💋‍👨🏾", "👩🏾‍❤️‍💋‍👨🏾",
"👩🏾‍❤‍💋‍👨🏿", "👩🏾‍❤️‍💋‍👨🏿", "👩🏾‍❤‍💋‍👩🏻",
"👩🏾‍❤️‍💋‍👩🏻", "👩🏾‍❤‍💋‍👩🏼", "👩🏾‍❤️‍💋‍👩🏼",
"👩🏾‍❤‍💋‍👩🏽", "👩🏾‍❤️‍💋‍👩🏽", "👩🏾‍❤‍💋‍👩🏾",
"👩🏾‍❤️‍💋‍👩🏾", "👩🏾‍❤‍💋‍👩🏿", "👩🏾‍❤️‍💋‍👩🏿",
"👩🏾‍🤝‍👨🏻", "👩🏾‍🤝‍👨🏼", "👩🏾‍🤝‍👨🏽",
"👩🏾‍🤝‍👨🏿", "👩🏾‍🤝‍👩🏻", "👩🏾‍🤝‍👩🏼",
"👩🏾‍🤝‍👩🏽", "👩🏾‍🤝‍👩🏿", "👩🏿‍❤‍👨🏻",
"👩🏿‍❤️‍👨🏻", "👩🏿‍❤‍👨🏼", "👩🏿‍❤️‍👨🏼",
"👩🏿‍❤‍👨🏽", "👩🏿‍❤️‍👨🏽", "👩🏿‍❤‍👨🏾",
"👩🏿‍❤️‍👨🏾", "👩🏿‍❤‍👨🏿", "👩🏿‍❤️‍👨🏿",
"👩🏿‍❤‍👩🏻", "👩🏿‍❤️‍👩🏻", "👩🏿‍❤‍👩🏼",
"👩🏿‍❤️‍👩🏼", "👩🏿‍❤‍👩🏽", "👩🏿‍❤️‍👩🏽",
"👩🏿‍❤‍👩🏾", "👩🏿‍❤️‍👩🏾", "👩🏿‍❤‍👩🏿",
"👩🏿‍❤️‍👩🏿", "👩🏿‍❤‍💋‍👨🏻", "👩🏿‍❤️‍💋‍👨🏻",
"👩🏿‍❤‍💋‍👨🏼", "👩🏿‍❤️‍💋‍👨🏼", "👩🏿‍❤‍💋‍👨🏽",
"👩🏿‍❤️‍💋‍👨🏽", "👩🏿‍❤‍💋‍👨🏾", "👩🏿‍❤️‍💋‍👨🏾",
"👩🏿‍❤‍💋‍👨🏿", "👩🏿‍❤️‍💋‍👨🏿", "👩🏿‍❤‍💋‍👩🏻",
"👩🏿‍❤️‍💋‍👩🏻", "👩🏿‍❤‍💋‍👩🏼", "👩🏿‍❤️‍💋‍👩🏼",
"👩🏿‍❤‍💋‍👩🏽", "👩🏿‍❤️‍💋‍👩🏽", "👩🏿‍❤‍💋‍👩🏾",
"👩🏿‍❤️‍💋‍👩🏾", "👩🏿‍❤‍💋‍👩🏿", "👩🏿‍❤️‍💋‍👩🏿",
"👩🏿‍🤝‍👨🏻", "👩🏿‍🤝‍👨🏼", "👩🏿‍🤝‍👨🏽",
"👩🏿‍🤝‍👨🏾", "👩🏿‍🤝‍👩🏻", "👩🏿‍🤝‍👩🏼",
"👩🏿‍🤝‍👩🏽", "👩🏿‍🤝‍👩🏾", "🧑‍🤝‍🧑",
"🧑🏻‍❤‍💋‍🧑🏼", "🧑🏻‍❤️‍💋‍🧑🏼", "🧑🏻‍❤‍💋‍🧑🏽",
"🧑🏻‍❤️‍💋‍🧑🏽", "🧑🏻‍❤‍💋‍🧑🏾", "🧑🏻‍❤️‍💋‍🧑🏾",
"🧑🏻‍❤‍💋‍🧑🏿", "🧑🏻‍❤️‍💋‍🧑🏿", "🧑🏻‍❤‍🧑🏼",
"🧑🏻‍❤️‍🧑🏼", "🧑🏻‍❤‍🧑🏽", "🧑🏻‍❤️‍🧑🏽",
"🧑🏻‍❤‍🧑🏾", "🧑🏻‍❤️‍🧑🏾", "🧑🏻‍❤‍🧑🏿",
"🧑🏻‍❤️‍🧑🏿", "🧑🏻‍🎄", "🧑🏻‍🤝‍🧑🏻", "🧑🏻‍🤝‍🧑🏼",
"🧑🏻‍🤝‍🧑🏽", "🧑🏻‍🤝‍🧑🏾", "🧑🏻‍🤝‍🧑🏿",
"🧑🏼‍❤‍💋‍🧑🏻", "🧑🏼‍❤️‍💋‍🧑🏻", "🧑🏼‍❤‍💋‍🧑🏽",
"🧑🏼‍❤️‍💋‍🧑🏽", "🧑🏼‍❤‍💋‍🧑🏾", "🧑🏼‍❤️‍💋‍🧑🏾",
"🧑🏼‍❤‍💋‍🧑🏿", "🧑🏼‍❤️‍💋‍🧑🏿", "🧑🏼‍❤‍🧑🏻",
"🧑🏼‍❤️‍🧑🏻", "🧑🏼‍❤‍🧑🏽", "🧑🏼‍❤️‍🧑🏽",
"🧑🏼‍❤‍🧑🏾", "🧑🏼‍❤️‍🧑🏾", "🧑🏼‍❤‍🧑🏿",
"🧑🏼‍❤️‍🧑🏿", "🧑🏼‍🎄", "🧑🏼‍🤝‍🧑🏻", "🧑🏼‍🤝‍🧑🏼",
"🧑🏼‍🤝‍🧑🏽", "🧑🏼‍🤝‍🧑🏾", "🧑🏼‍🤝‍🧑🏿",
"🧑🏽‍❤‍💋‍🧑🏻", "🧑🏽‍❤️‍💋‍🧑🏻", "🧑🏽‍❤‍💋‍🧑🏼",
"🧑🏽‍❤️‍💋‍🧑🏼", "🧑🏽‍❤‍💋‍🧑🏾", "🧑🏽‍❤️‍💋‍🧑🏾",
"🧑🏽‍❤‍💋‍🧑🏿", "🧑🏽‍❤️‍💋‍🧑🏿", "🧑🏽‍❤‍🧑🏻",
"🧑🏽‍❤️‍🧑🏻", "🧑🏽‍❤‍🧑🏼", "🧑🏽‍❤️‍🧑🏼",
"🧑🏽‍❤‍🧑🏾", "🧑🏽‍❤️‍🧑🏾", "🧑🏽‍❤‍🧑🏿",
"🧑🏽‍❤️‍🧑🏿", "🧑🏽‍🎄", "🧑🏽‍🤝‍🧑🏻", "🧑🏽‍🤝‍🧑🏼",
"🧑🏽‍🤝‍🧑🏽", "🧑🏽‍🤝‍🧑🏾", "🧑🏽‍🤝‍🧑🏿",
"🧑🏾‍❤‍💋‍🧑🏻", "🧑🏾‍❤️‍💋‍🧑🏻", "🧑🏾‍❤‍💋‍🧑🏼",
"🧑🏾‍❤️‍💋‍🧑🏼", "🧑🏾‍❤‍💋‍🧑🏽", "🧑🏾‍❤️‍💋‍🧑🏽",
"🧑🏾‍❤‍💋‍🧑🏿", "🧑🏾‍❤️‍💋‍🧑🏿", "🧑🏾‍❤‍🧑🏻",
"🧑🏾‍❤️‍🧑🏻", "🧑🏾‍❤‍🧑🏼", "🧑🏾‍❤️‍🧑🏼",
"🧑🏾‍❤‍🧑🏽", "🧑🏾‍❤️‍🧑🏽", "🧑🏾‍❤‍🧑🏿",
"🧑🏾‍❤️‍🧑🏿", "🧑🏾‍🎄", "🧑🏾‍🤝‍🧑🏻", "🧑🏾‍🤝‍🧑🏼",
"🧑🏾‍🤝‍🧑🏽", "🧑🏾‍🤝‍🧑🏾", "🧑🏾‍🤝‍🧑🏿",
"🧑🏿‍❤‍💋‍🧑🏻", "🧑🏿‍❤️‍💋‍🧑🏻", "🧑🏿‍❤‍💋‍🧑🏼",
"🧑🏿‍❤️‍💋‍🧑🏼", "🧑🏿‍❤‍💋‍🧑🏽", "🧑🏿‍❤️‍💋‍🧑🏽",
"🧑🏿‍❤‍💋‍🧑🏾", "🧑🏿‍❤️‍💋‍🧑🏾", "🧑🏿‍❤‍🧑🏻",
"🧑🏿‍❤️‍🧑🏻", "🧑🏿‍❤‍🧑🏼", "🧑🏿‍❤️‍🧑🏼",
"🧑🏿‍❤‍🧑🏽", "🧑🏿‍❤️‍🧑🏽", "🧑🏿‍❤‍🧑🏾",
"🧑🏿‍❤️‍🧑🏾", "🧑🏿‍🎄", "🧑🏿‍🤝‍🧑🏻", "🧑🏿‍🤝‍🧑🏼",
"🧑🏿‍🤝‍🧑🏽", "🧑🏿‍🤝‍🧑🏾", "🧑🏿‍🤝‍🧑🏿", "🫱🏻‍🫲🏼",
"🫱🏻‍🫲🏽", "🫱🏻‍🫲🏾", "🫱🏻‍🫲🏿", "🫱🏼‍🫲🏻", "🫱🏼‍🫲🏽",
"🫱🏼‍🫲🏾", "🫱🏼‍🫲🏿", "🫱🏽‍🫲🏻", "🫱🏽‍🫲🏼", "🫱🏽‍🫲🏾",
"🫱🏽‍🫲🏿", "🫱🏾‍🫲🏻", "🫱🏾‍🫲🏼", "🫱🏾‍🫲🏽", "🫱🏾‍🫲🏿",
"🫱🏿‍🫲🏻", "🫱🏿‍🫲🏼", "🫱🏿‍🫲🏽", "🫱🏿‍🫲🏾", "👨‍⚕",
"👨‍⚕️", "👨‍⚖", "👨‍⚖️", "👨‍✈", "👨‍✈️", "👨‍🌾", "👨‍🍳",
"👨‍🍼", "👨‍🎓", "👨‍🎤", "👨‍🎨", "👨‍🏫", "👨‍🏭", "👨‍💻",
"👨‍💼", "👨‍🔧", "👨‍🔬", "👨‍🚀", "👨‍🚒", "👨‍🦯", "👨‍🦼",
"👨‍🦽", "👨🏻‍⚕", "👨🏻‍⚕️", "👨🏻‍⚖", "👨🏻‍⚖️", "👨🏻‍✈",
"👨🏻‍✈️", "👨🏻‍🌾", "👨🏻‍🍳", "👨🏻‍🍼", "👨🏻‍🎓",
"👨🏻‍🎤", "👨🏻‍🎨", "👨🏻‍🏫", "👨🏻‍🏭", "👨🏻‍💻", "👨🏻‍💼",
"👨🏻‍🔧", "👨🏻‍🔬", "👨🏻‍🚀", "👨🏻‍🚒", "👨🏻‍🦯", "👨🏻‍🦼",
"👨🏻‍🦽", "👨🏼‍⚕", "👨🏼‍⚕️", "👨🏼‍⚖", "👨🏼‍⚖️", "👨🏼‍✈",
"👨🏼‍✈️", "👨🏼‍🌾", "👨🏼‍🍳", "👨🏼‍🍼", "👨🏼‍🎓",
"👨🏼‍🎤", "👨🏼‍🎨", "👨🏼‍🏫", "👨🏼‍🏭", "👨🏼‍💻", "👨🏼‍💼",
"👨🏼‍🔧", "👨🏼‍🔬", "👨🏼‍🚀", "👨🏼‍🚒", "👨🏼‍🦯", "👨🏼‍🦼",
"👨🏼‍🦽", "👨🏽‍⚕", "👨🏽‍⚕️", "👨🏽‍⚖", "👨🏽‍⚖️", "👨🏽‍✈",
"👨🏽‍✈️", "👨🏽‍🌾", "👨🏽‍🍳", "👨🏽‍🍼", "👨🏽‍🎓",
"👨🏽‍🎤", "👨🏽‍🎨", "👨🏽‍🏫", "👨🏽‍🏭", "👨🏽‍💻", "👨🏽‍💼",
"👨🏽‍🔧", "👨🏽‍🔬", "👨🏽‍🚀", "👨🏽‍🚒", "👨🏽‍🦯", "👨🏽‍🦼",
"👨🏽‍🦽", "👨🏾‍⚕", "👨🏾‍⚕️", "👨🏾‍⚖", "👨🏾‍⚖️", "👨🏾‍✈",
"👨🏾‍✈️", "👨🏾‍🌾", "👨🏾‍🍳", "👨🏾‍🍼", "👨🏾‍🎓",
"👨🏾‍🎤", "👨🏾‍🎨", "👨🏾‍🏫", "👨🏾‍🏭", "👨🏾‍💻", "👨🏾‍💼",
"👨🏾‍🔧", "👨🏾‍🔬", "👨🏾‍🚀", "👨🏾‍🚒", "👨🏾‍🦯", "👨🏾‍🦼",
"👨🏾‍🦽", "👨🏿‍⚕", "👨🏿‍⚕️", "👨🏿‍⚖", "👨🏿‍⚖️", "👨🏿‍✈",
"👨🏿‍✈️", "👨🏿‍🌾", "👨🏿‍🍳", "👨🏿‍🍼", "👨🏿‍🎓",
"👨🏿‍🎤", "👨🏿‍🎨", "👨🏿‍🏫", "👨🏿‍🏭", "👨🏿‍💻", "👨🏿‍💼",
"👨🏿‍🔧", "👨🏿‍🔬", "👨🏿‍🚀", "👨🏿‍🚒", "👨🏿‍🦯", "👨🏿‍🦼",
"👨🏿‍🦽", "👩‍⚕", "👩‍⚕️", "👩‍⚖", "👩‍⚖️", "👩‍✈", "👩‍✈️",
"👩‍🌾", "👩‍🍳", "👩‍🍼", "👩‍🎓", "👩‍🎤", "👩‍🎨", "👩‍🏫",
"👩‍🏭", "👩‍💻", "👩‍💼", "👩‍🔧", "👩‍🔬", "👩‍🚀", "👩‍🚒",
"👩‍🦯", "👩‍🦼", "👩‍🦽", "👩🏻‍⚕", "👩🏻‍⚕️", "👩🏻‍⚖",
"👩🏻‍⚖️", "👩🏻‍✈", "👩🏻‍✈️", "👩🏻‍🌾", "👩🏻‍🍳",
"👩🏻‍🍼", "👩🏻‍🎓", "👩🏻‍🎤", "👩🏻‍🎨", "👩🏻‍🏫", "👩🏻‍🏭",
"👩🏻‍💻", "👩🏻‍💼", "👩🏻‍🔧", "👩🏻‍🔬", "👩🏻‍🚀", "👩🏻‍🚒",
"👩🏻‍🦯", "👩🏻‍🦼", "👩🏻‍🦽", "👩🏼‍⚕", "👩🏼‍⚕️", "👩🏼‍⚖",
"👩🏼‍⚖️", "👩🏼‍✈", "👩🏼‍✈️", "👩🏼‍🌾", "👩🏼‍🍳",
"👩🏼‍🍼", "👩🏼‍🎓", "👩🏼‍🎤", "👩🏼‍🎨", "👩🏼‍🏫", "👩🏼‍🏭",
"👩🏼‍💻", "👩🏼‍💼", "👩🏼‍🔧", "👩🏼‍🔬", "👩🏼‍🚀", "👩🏼‍🚒",
"👩🏼‍🦯", "👩🏼‍🦼", "👩🏼‍🦽", "👩🏽‍⚕", "👩🏽‍⚕️", "👩🏽‍⚖",
"👩🏽‍⚖️", "👩🏽‍✈", "👩🏽‍✈️", "👩🏽‍🌾", "👩🏽‍🍳",
"👩🏽‍🍼", "👩🏽‍🎓", "👩🏽‍🎤", "👩🏽‍🎨", "👩🏽‍🏫", "👩🏽‍🏭",
"👩🏽‍💻", "👩🏽‍💼", "👩🏽‍🔧", "👩🏽‍🔬", "👩🏽‍🚀", "👩🏽‍🚒",
"👩🏽‍🦯", "👩🏽‍🦼", "👩🏽‍🦽", "👩🏾‍⚕", "👩🏾‍⚕️", "👩🏾‍⚖",
"👩🏾‍⚖️", "👩🏾‍✈", "👩🏾‍✈️", "👩🏾‍🌾", "👩🏾‍🍳",
"👩🏾‍🍼", "👩🏾‍🎓", "👩🏾‍🎤", "👩🏾‍🎨", "👩🏾‍🏫", "👩🏾‍🏭",
"👩🏾‍💻", "👩🏾‍💼", "👩🏾‍🔧", "👩🏾‍🔬", "👩🏾‍🚀", "👩🏾‍🚒",
"👩🏾‍🦯", "👩🏾‍🦼", "👩🏾‍🦽", "👩🏿‍⚕", "👩🏿‍⚕️", "👩🏿‍⚖",
"👩🏿‍⚖️", "👩🏿‍✈", "👩🏿‍✈️", "👩🏿‍🌾", "👩🏿‍🍳",
"👩🏿‍🍼", "👩🏿‍🎓", "👩🏿‍🎤", "👩🏿‍🎨", "👩🏿‍🏫", "👩🏿‍🏭",
"👩🏿‍💻", "👩🏿‍💼", "👩🏿‍🔧", "👩🏿‍🔬", "👩🏿‍🚀", "👩🏿‍🚒",
"👩🏿‍🦯", "👩🏿‍🦼", "👩🏿‍🦽", "🧑‍⚕", "🧑‍⚕️", "🧑‍⚖",
"🧑‍⚖️", "🧑‍✈", "🧑‍✈️", "🧑‍🌾", "🧑‍🍳", "🧑‍🍼", "🧑‍🎓",
"🧑‍🎤", "🧑‍🎨", "🧑‍🏫", "🧑‍🏭", "🧑‍💻", "🧑‍💼", "🧑‍🔧",
"🧑‍🔬", "🧑‍🚀", "🧑‍🚒", "🧑‍🦯", "🧑‍🦼", "🧑‍🦽", "🧑🏻‍⚕",
"🧑🏻‍⚕️", "🧑🏻‍⚖", "🧑🏻‍⚖️", "🧑🏻‍✈", "🧑🏻‍✈️",
"🧑🏻‍🌾", "🧑🏻‍🍳", "🧑🏻‍🍼", "🧑🏻‍🎓", "🧑🏻‍🎤", "🧑🏻‍🎨",
"🧑🏻‍🏫", "🧑🏻‍🏭", "🧑🏻‍💻", "🧑🏻‍💼", "🧑🏻‍🔧", "🧑🏻‍🔬",
"🧑🏻‍🚀", "🧑🏻‍🚒", "🧑🏻‍🦯", "🧑🏻‍🦼", "🧑🏻‍🦽", "🧑🏼‍⚕",
"🧑🏼‍⚕️", "🧑🏼‍⚖", "🧑🏼‍⚖️", "🧑🏼‍✈", "🧑🏼‍✈️",
"🧑🏼‍🌾", "🧑🏼‍🍳", "🧑🏼‍🍼", "🧑🏼‍🎓", "🧑🏼‍🎤", "🧑🏼‍🎨",
"🧑🏼‍🏫", "🧑🏼‍🏭", "🧑🏼‍💻", "🧑🏼‍💼", "🧑🏼‍🔧", "🧑🏼‍🔬",
"🧑🏼‍🚀", "🧑🏼‍🚒", "🧑🏼‍🦯", "🧑🏼‍🦼", "🧑🏼‍🦽", "🧑🏽‍⚕",
"🧑🏽‍⚕️", "🧑🏽‍⚖", "🧑🏽‍⚖️", "🧑🏽‍✈", "🧑🏽‍✈️",
"🧑🏽‍🌾", "🧑🏽‍🍳", "🧑🏽‍🍼", "🧑🏽‍🎓", "🧑🏽‍🎤", "🧑🏽‍🎨",
"🧑🏽‍🏫", "🧑🏽‍🏭", "🧑🏽‍💻", "🧑🏽‍💼", "🧑🏽‍🔧", "🧑🏽‍🔬",
"🧑🏽‍🚀", "🧑🏽‍🚒", "🧑🏽‍🦯", "🧑🏽‍🦼", "🧑🏽‍🦽", "🧑🏾‍⚕",
"🧑🏾‍⚕️", "🧑🏾‍⚖", "🧑🏾‍⚖️", "🧑🏾‍✈", "🧑🏾‍✈️",
"🧑🏾‍🌾", "🧑🏾‍🍳", "🧑🏾‍🍼", "🧑🏾‍🎓", "🧑🏾‍🎤", "🧑🏾‍🎨",
"🧑🏾‍🏫", "🧑🏾‍🏭", "🧑🏾‍💻", "🧑🏾‍💼", "🧑🏾‍🔧", "🧑🏾‍🔬",
"🧑🏾‍🚀", "🧑🏾‍🚒", "🧑🏾‍🦯", "🧑🏾‍🦼", "🧑🏾‍🦽", "🧑🏿‍⚕",
"🧑🏿‍⚕️", "🧑🏿‍⚖", "🧑🏿‍⚖️", "🧑🏿‍✈", "🧑🏿‍✈️",
"🧑🏿‍🌾", "🧑🏿‍🍳", "🧑🏿‍🍼", "🧑🏿‍🎓", "🧑🏿‍🎤", "🧑🏿‍🎨",
"🧑🏿‍🏫", "🧑🏿‍🏭", "🧑🏿‍💻", "🧑🏿‍💼", "🧑🏿‍🔧", "🧑🏿‍🔬",
"🧑🏿‍🚀", "🧑🏿‍🚒", "🧑🏿‍🦯", "🧑🏿‍🦼", "🧑🏿‍🦽", "⛹🏻‍♀",
"⛹🏻‍♀️", "⛹🏻‍♂", "⛹🏻‍♂️", "⛹🏼‍♀", "⛹🏼‍♀️", "⛹🏼‍♂",
"⛹🏼‍♂️", "⛹🏽‍♀", "⛹🏽‍♀️", "⛹🏽‍♂", "⛹🏽‍♂️", "⛹🏾‍♀",
"⛹🏾‍♀️", "⛹🏾‍♂", "⛹🏾‍♂️", "⛹🏿‍♀", "⛹🏿‍♀️", "⛹🏿‍♂",
"⛹🏿‍♂️", "⛹‍♀", "⛹️‍♀️", "⛹‍♂", "⛹️‍♂️", "🏃‍♀", "🏃‍♀️",
"🏃‍♂", "🏃‍♂️", "🏃🏻‍♀", "🏃🏻‍♀️", "🏃🏻‍♂", "🏃🏻‍♂️",
"🏃🏼‍♀", "🏃🏼‍♀️", "🏃🏼‍♂", "🏃🏼‍♂️", "🏃🏽‍♀",
"🏃🏽‍♀️", "🏃🏽‍♂", "🏃🏽‍♂️", "🏃🏾‍♀", "🏃🏾‍♀️",
"🏃🏾‍♂", "🏃🏾‍♂️", "🏃🏿‍♀", "🏃🏿‍♀️", "🏃🏿‍♂",
"🏃🏿‍♂️", "🏄‍♀", "🏄‍♀️", "🏄‍♂", "🏄‍♂️", "🏄🏻‍♀",
"🏄🏻‍♀️", "🏄🏻‍♂", "🏄🏻‍♂️", "🏄🏼‍♀", "🏄🏼‍♀️",
"🏄🏼‍♂", "🏄🏼‍♂️", "🏄🏽‍♀", "🏄🏽‍♀️", "🏄🏽‍♂",
"🏄🏽‍♂️", "🏄🏾‍♀", "🏄🏾‍♀️", "🏄🏾‍♂", "🏄🏾‍♂️",
"🏄🏿‍♀", "🏄🏿‍♀️", "🏄🏿‍♂", "🏄🏿‍♂️", "🏊‍♀", "🏊‍♀️",
"🏊‍♂", "🏊‍♂️", "🏊🏻‍♀", "🏊🏻‍♀️", "🏊🏻‍♂", "🏊🏻‍♂️",
"🏊🏼‍♀", "🏊🏼‍♀️", "🏊🏼‍♂", "🏊🏼‍♂️", "🏊🏽‍♀",
"🏊🏽‍♀️", "🏊🏽‍♂", "🏊🏽‍♂️", "🏊🏾‍♀", "🏊🏾‍♀️",
"🏊🏾‍♂", "🏊🏾‍♂️", "🏊🏿‍♀", "🏊🏿‍♀️", "🏊🏿‍♂",
"🏊🏿‍♂️", "🏋🏻‍♀", "🏋🏻‍♀️", "🏋🏻‍♂", "🏋🏻‍♂️",
"🏋🏼‍♀", "🏋🏼‍♀️", "🏋🏼‍♂", "🏋🏼‍♂️", "🏋🏽‍♀",
"🏋🏽‍♀️", "🏋🏽‍♂", "🏋🏽‍♂️", "🏋🏾‍♀", "🏋🏾‍♀️",
"🏋🏾‍♂", "🏋🏾‍♂️", "🏋🏿‍♀", "🏋🏿‍♀️", "🏋🏿‍♂",
"🏋🏿‍♂️", "🏋‍♀", "🏋️‍♀️", "🏋‍♂", "🏋️‍♂️", "🏌🏻‍♀",
"🏌🏻‍♀️", "🏌🏻‍♂", "🏌🏻‍♂️", "🏌🏼‍♀", "🏌🏼‍♀️",
"🏌🏼‍♂", "🏌🏼‍♂️", "🏌🏽‍♀", "🏌🏽‍♀️", "🏌🏽‍♂",
"🏌🏽‍♂️", "🏌🏾‍♀", "🏌🏾‍♀️", "🏌🏾‍♂", "🏌🏾‍♂️",
"🏌🏿‍♀", "🏌🏿‍♀️", "🏌🏿‍♂", "🏌🏿‍♂️", "🏌‍♀", "🏌️‍♀️",
"🏌‍♂", "🏌️‍♂️", "👮‍♀", "👮‍♀️", "👮‍♂", "👮‍♂️", "👮🏻‍♀",
"👮🏻‍♀️", "👮🏻‍♂", "👮🏻‍♂️", "👮🏼‍♀", "👮🏼‍♀️",
"👮🏼‍♂", "👮🏼‍♂️", "👮🏽‍♀", "👮🏽‍♀️", "👮🏽‍♂",
"👮🏽‍♂️", "👮🏾‍♀", "👮🏾‍♀️", "👮🏾‍♂", "👮🏾‍♂️",
"👮🏿‍♀", "👮🏿‍♀️", "👮🏿‍♂", "👮🏿‍♂️", "👯‍♀", "👯‍♀️",
"👯‍♂", "👯‍♂️", "👰‍♀", "👰‍♀️", "👰‍♂", "👰‍♂️", "👰🏻‍♀",
"👰🏻‍♀️", "👰🏻‍♂", "👰🏻‍♂️", "👰🏼‍♀", "👰🏼‍♀️",
"👰🏼‍♂", "👰🏼‍♂️", "👰🏽‍♀", "👰🏽‍♀️", "👰🏽‍♂",
"👰🏽‍♂️", "👰🏾‍♀", "👰🏾‍♀️", "👰🏾‍♂", "👰🏾‍♂️",
"👰🏿‍♀", "👰🏿‍♀️", "👰🏿‍♂", "👰🏿‍♂️", "👱‍♀", "👱‍♀️",
"👱‍♂", "👱‍♂️", "👱🏻‍♀", "👱🏻‍♀️", "👱🏻‍♂", "👱🏻‍♂️",
"👱🏼‍♀", "👱🏼‍♀️", "👱🏼‍♂", "👱🏼‍♂️", "👱🏽‍♀",
"👱🏽‍♀️", "👱🏽‍♂", "👱🏽‍♂️", "👱🏾‍♀", "👱🏾‍♀️",
"👱🏾‍♂", "👱🏾‍♂️", "👱🏿‍♀", "👱🏿‍♀️", "👱🏿‍♂",
"👱🏿‍♂️", "👳‍♀", "👳‍♀️", "👳‍♂", "👳‍♂️", "👳🏻‍♀",
"👳🏻‍♀️", "👳🏻‍♂", "👳🏻‍♂️", "👳🏼‍♀", "👳🏼‍♀️",
"👳🏼‍♂", "👳🏼‍♂️", "👳🏽‍♀", "👳🏽‍♀️", "👳🏽‍♂",
"👳🏽‍♂️", "👳🏾‍♀", "👳🏾‍♀️", "👳🏾‍♂", "👳🏾‍♂️",
"👳🏿‍♀", "👳🏿‍♀️", "👳🏿‍♂", "👳🏿‍♂️", "👷‍♀", "👷‍♀️",
"👷‍♂", "👷‍♂️", "👷🏻‍♀", "👷🏻‍♀️", "👷🏻‍♂", "👷🏻‍♂️",
"👷🏼‍♀", "👷🏼‍♀️", "👷🏼‍♂", "👷🏼‍♂️", "👷🏽‍♀",
"👷🏽‍♀️", "👷🏽‍♂", "👷🏽‍♂️", "👷🏾‍♀", "👷🏾‍♀️",
"👷🏾‍♂", "👷🏾‍♂️", "👷🏿‍♀", "👷🏿‍♀️", "👷🏿‍♂",
"👷🏿‍♂️", "💁‍♀", "💁‍♀️", "💁‍♂", "💁‍♂️", "💁🏻‍♀",
"💁🏻‍♀️", "💁🏻‍♂", "💁🏻‍♂️", "💁🏼‍♀", "💁🏼‍♀️",
"💁🏼‍♂", "💁🏼‍♂️", "💁🏽‍♀", "💁🏽‍♀️", "💁🏽‍♂",
"💁🏽‍♂️", "💁🏾‍♀", "💁🏾‍♀️", "💁🏾‍♂", "💁🏾‍♂️",
"💁🏿‍♀", "💁🏿‍♀️", "💁🏿‍♂", "💁🏿‍♂️", "💂‍♀", "💂‍♀️",
"💂‍♂", "💂‍♂️", "💂🏻‍♀", "💂🏻‍♀️", "💂🏻‍♂", "💂🏻‍♂️",
"💂🏼‍♀", "💂🏼‍♀️", "💂🏼‍♂", "💂🏼‍♂️", "💂🏽‍♀",
"💂🏽‍♀️", "💂🏽‍♂", "💂🏽‍♂️", "💂🏾‍♀", "💂🏾‍♀️",
"💂🏾‍♂", "💂🏾‍♂️", "💂🏿‍♀", "💂🏿‍♀️", "💂🏿‍♂",
"💂🏿‍♂️", "💆‍♀", "💆‍♀️", "💆‍♂", "💆‍♂️", "💆🏻‍♀",
"💆🏻‍♀️", "💆🏻‍♂", "💆🏻‍♂️", "💆🏼‍♀", "💆🏼‍♀️",
"💆🏼‍♂", "💆🏼‍♂️", "💆🏽‍♀", "💆🏽‍♀️", "💆🏽‍♂",
"💆🏽‍♂️", "💆🏾‍♀", "💆🏾‍♀️", "💆🏾‍♂", "💆🏾‍♂️",
"💆🏿‍♀", "💆🏿‍♀️", "💆🏿‍♂", "💆🏿‍♂️", "💇‍♀", "💇‍♀️",
"💇‍♂", "💇‍♂️", "💇🏻‍♀", "💇🏻‍♀️", "💇🏻‍♂", "💇🏻‍♂️",
"💇🏼‍♀", "💇🏼‍♀️", "💇🏼‍♂", "💇🏼‍♂️", "💇🏽‍♀",
"💇🏽‍♀️", "💇🏽‍♂", "💇🏽‍♂️", "💇🏾‍♀", "💇🏾‍♀️",
"💇🏾‍♂", "💇🏾‍♂️", "💇🏿‍♀", "💇🏿‍♀️", "💇🏿‍♂",
"💇🏿‍♂️", "🕵🏻‍♀", "🕵🏻‍♀️", "🕵🏻‍♂", "🕵🏻‍♂️",
"🕵🏼‍♀", "🕵🏼‍♀️", "🕵🏼‍♂", "🕵🏼‍♂️", "🕵🏽‍♀",
"🕵🏽‍♀️", "🕵🏽‍♂", "🕵🏽‍♂️", "🕵🏾‍♀", "🕵🏾‍♀️",
"🕵🏾‍♂", "🕵🏾‍♂️", "🕵🏿‍♀", "🕵🏿‍♀️", "🕵🏿‍♂",
"🕵🏿‍♂️", "🕵‍♀", "🕵️‍♀️", "🕵‍♂", "🕵️‍♂️", "🙅‍♀",
"🙅‍♀️", "🙅‍♂", "🙅‍♂️", "🙅🏻‍♀", "🙅🏻‍♀️", "🙅🏻‍♂",
"🙅🏻‍♂️", "🙅🏼‍♀", "🙅🏼‍♀️", "🙅🏼‍♂", "🙅🏼‍♂️",
"🙅🏽‍♀", "🙅🏽‍♀️", "🙅🏽‍♂", "🙅🏽‍♂️", "🙅🏾‍♀",
"🙅🏾‍♀️", "🙅🏾‍♂", "🙅🏾‍♂️", "🙅🏿‍♀", "🙅🏿‍♀️",
"🙅🏿‍♂", "🙅🏿‍♂️", "🙆‍♀", "🙆‍♀️", "🙆‍♂", "🙆‍♂️",
"🙆🏻‍♀", "🙆🏻‍♀️", "🙆🏻‍♂", "🙆🏻‍♂️", "🙆🏼‍♀",
"🙆🏼‍♀️", "🙆🏼‍♂", "🙆🏼‍♂️", "🙆🏽‍♀", "🙆🏽‍♀️",
"🙆🏽‍♂", "🙆🏽‍♂️", "🙆🏾‍♀", "🙆🏾‍♀️", "🙆🏾‍♂",
"🙆🏾‍♂️", "🙆🏿‍♀", "🙆🏿‍♀️", "🙆🏿‍♂", "🙆🏿‍♂️", "🙇‍♀",
"🙇‍♀️", "🙇‍♂", "🙇‍♂️", "🙇🏻‍♀", "🙇🏻‍♀️", "🙇🏻‍♂",
"🙇🏻‍♂️", "🙇🏼‍♀", "🙇🏼‍♀️", "🙇🏼‍♂", "🙇🏼‍♂️",
"🙇🏽‍♀", "🙇🏽‍♀️", "🙇🏽‍♂", "🙇🏽‍♂️", "🙇🏾‍♀",
"🙇🏾‍♀️", "🙇🏾‍♂", "🙇🏾‍♂️", "🙇🏿‍♀", "🙇🏿‍♀️",
"🙇🏿‍♂", "🙇🏿‍♂️", "🙋‍♀", "🙋‍♀️", "🙋‍♂", "🙋‍♂️",
"🙋🏻‍♀", "🙋🏻‍♀️", "🙋🏻‍♂", "🙋🏻‍♂️", "🙋🏼‍♀",
"🙋🏼‍♀️", "🙋🏼‍♂", "🙋🏼‍♂️", "🙋🏽‍♀", "🙋🏽‍♀️",
"🙋🏽‍♂", "🙋🏽‍♂️", "🙋🏾‍♀", "🙋🏾‍♀️", "🙋🏾‍♂",
"🙋🏾‍♂️", "🙋🏿‍♀", "🙋🏿‍♀️", "🙋🏿‍♂", "🙋🏿‍♂️", "🙍‍♀",
"🙍‍♀️", "🙍‍♂", "🙍‍♂️", "🙍🏻‍♀", "🙍🏻‍♀️", "🙍🏻‍♂",
"🙍🏻‍♂️", "🙍🏼‍♀", "🙍🏼‍♀️", "🙍🏼‍♂", "🙍🏼‍♂️",
"🙍🏽‍♀", "🙍🏽‍♀️", "🙍🏽‍♂", "🙍🏽‍♂️", "🙍🏾‍♀",
"🙍🏾‍♀️", "🙍🏾‍♂", "🙍🏾‍♂️", "🙍🏿‍♀", "🙍🏿‍♀️",
"🙍🏿‍♂", "🙍🏿‍♂️", "🙎‍♀", "🙎‍♀️", "🙎‍♂", "🙎‍♂️",
"🙎🏻‍♀", "🙎🏻‍♀️", "🙎🏻‍♂", "🙎🏻‍♂️", "🙎🏼‍♀",
"🙎🏼‍♀️", "🙎🏼‍♂", "🙎🏼‍♂️", "🙎🏽‍♀", "🙎🏽‍♀️",
"🙎🏽‍♂", "🙎🏽‍♂️", "🙎🏾‍♀", "🙎🏾‍♀️", "🙎🏾‍♂",
"🙎🏾‍♂️", "🙎🏿‍♀", "🙎🏿‍♀️", "🙎🏿‍♂", "🙎🏿‍♂️", "🚣‍♀",
"🚣‍♀️", "🚣‍♂", "🚣‍♂️", "🚣🏻‍♀", "🚣🏻‍♀️", "🚣🏻‍♂",
"🚣🏻‍♂️", "🚣🏼‍♀", "🚣🏼‍♀️", "🚣🏼‍♂", "🚣🏼‍♂️",
"🚣🏽‍♀", "🚣🏽‍♀️", "🚣🏽‍♂", "🚣🏽‍♂️", "🚣🏾‍♀",
"🚣🏾‍♀️", "🚣🏾‍♂", "🚣🏾‍♂️", "🚣🏿‍♀", "🚣🏿‍♀️",
"🚣🏿‍♂", "🚣🏿‍♂️", "🚴‍♀", "🚴‍♀️", "🚴‍♂", "🚴‍♂️",
"🚴🏻‍♀", "🚴🏻‍♀️", "🚴🏻‍♂", "🚴🏻‍♂️", "🚴🏼‍♀",
"🚴🏼‍♀️", "🚴🏼‍♂", "🚴🏼‍♂️", "🚴🏽‍♀", "🚴🏽‍♀️",
"🚴🏽‍♂", "🚴🏽‍♂️", "🚴🏾‍♀", "🚴🏾‍♀️", "🚴🏾‍♂",
"🚴🏾‍♂️", "🚴🏿‍♀", "🚴🏿‍♀️", "🚴🏿‍♂", "🚴🏿‍♂️", "🚵‍♀",
"🚵‍♀️", "🚵‍♂", "🚵‍♂️", "🚵🏻‍♀", "🚵🏻‍♀️", "🚵🏻‍♂",
"🚵🏻‍♂️", "🚵🏼‍♀", "🚵🏼‍♀️", "🚵🏼‍♂", "🚵🏼‍♂️",
"🚵🏽‍♀", "🚵🏽‍♀️", "🚵🏽‍♂", "🚵🏽‍♂️", "🚵🏾‍♀",
"🚵🏾‍♀️", "🚵🏾‍♂", "🚵🏾‍♂️", "🚵🏿‍♀", "🚵🏿‍♀️",
"🚵🏿‍♂", "🚵🏿‍♂️", "🚶‍♀", "🚶‍♀️", "🚶‍♂", "🚶‍♂️",
"🚶🏻‍♀", "🚶🏻‍♀️", "🚶🏻‍♂", "🚶🏻‍♂️", "🚶🏼‍♀",
"🚶🏼‍♀️", "🚶🏼‍♂", "🚶🏼‍♂️", "🚶🏽‍♀", "🚶🏽‍♀️",
"🚶🏽‍♂", "🚶🏽‍♂️", "🚶🏾‍♀", "🚶🏾‍♀️", "🚶🏾‍♂",
"🚶🏾‍♂️", "🚶🏿‍♀", "🚶🏿‍♀️", "🚶🏿‍♂", "🚶🏿‍♂️", "🤦‍♀",
"🤦‍♀️", "🤦‍♂", "🤦‍♂️", "🤦🏻‍♀", "🤦🏻‍♀️", "🤦🏻‍♂",
"🤦🏻‍♂️", "🤦🏼‍♀", "🤦🏼‍♀️", "🤦🏼‍♂", "🤦🏼‍♂️",
"🤦🏽‍♀", "🤦🏽‍♀️", "🤦🏽‍♂", "🤦🏽‍♂️", "🤦🏾‍♀",
"🤦🏾‍♀️", "🤦🏾‍♂", "🤦🏾‍♂️", "🤦🏿‍♀", "🤦🏿‍♀️",
"🤦🏿‍♂", "🤦🏿‍♂️", "🤵‍♀", "🤵‍♀️", "🤵‍♂", "🤵‍♂️",
"🤵🏻‍♀", "🤵🏻‍♀️", "🤵🏻‍♂", "🤵🏻‍♂️", "🤵🏼‍♀",
"🤵🏼‍♀️", "🤵🏼‍♂", "🤵🏼‍♂️", "🤵🏽‍♀", "🤵🏽‍♀️",
"🤵🏽‍♂", "🤵🏽‍♂️", "🤵🏾‍♀", "🤵🏾‍♀️", "🤵🏾‍♂",
"🤵🏾‍♂️", "🤵🏿‍♀", "🤵🏿‍♀️", "🤵🏿‍♂", "🤵🏿‍♂️", "🤷‍♀",
"🤷‍♀️", "🤷‍♂", "🤷‍♂️", "🤷🏻‍♀", "🤷🏻‍♀️", "🤷🏻‍♂",
"🤷🏻‍♂️", "🤷🏼‍♀", "🤷🏼‍♀️", "🤷🏼‍♂", "🤷🏼‍♂️",
"🤷🏽‍♀", "🤷🏽‍♀️", "🤷🏽‍♂", "🤷🏽‍♂️", "🤷🏾‍♀",
"🤷🏾‍♀️", "🤷🏾‍♂", "🤷🏾‍♂️", "🤷🏿‍♀", "🤷🏿‍♀️",
"🤷🏿‍♂", "🤷🏿‍♂️", "🤸‍♀", "🤸‍♀️", "🤸‍♂", "🤸‍♂️",
"🤸🏻‍♀", "🤸🏻‍♀️", "🤸🏻‍♂", "🤸🏻‍♂️", "🤸🏼‍♀",
"🤸🏼‍♀️", "🤸🏼‍♂", "🤸🏼‍♂️", "🤸🏽‍♀", "🤸🏽‍♀️",
"🤸🏽‍♂", "🤸🏽‍♂️", "🤸🏾‍♀", "🤸🏾‍♀️", "🤸🏾‍♂",
"🤸🏾‍♂️", "🤸🏿‍♀", "🤸🏿‍♀️", "🤸🏿‍♂", "🤸🏿‍♂️", "🤹‍♀",
"🤹‍♀️", "🤹‍♂", "🤹‍♂️", "🤹🏻‍♀", "🤹🏻‍♀️", "🤹🏻‍♂",
"🤹🏻‍♂️", "🤹🏼‍♀", "🤹🏼‍♀️", "🤹🏼‍♂", "🤹🏼‍♂️",
"🤹🏽‍♀", "🤹🏽‍♀️", "🤹🏽‍♂", "🤹🏽‍♂️", "🤹🏾‍♀",
"🤹🏾‍♀️", "🤹🏾‍♂", "🤹🏾‍♂️", "🤹🏿‍♀", "🤹🏿‍♀️",
"🤹🏿‍♂", "🤹🏿‍♂️", "🤼‍♀", "🤼‍♀️", "🤼‍♂", "🤼‍♂️", "🤽‍♀",
"🤽‍♀️", "🤽‍♂", "🤽‍♂️", "🤽🏻‍♀", "🤽🏻‍♀️", "🤽🏻‍♂",
"🤽🏻‍♂️", "🤽🏼‍♀", "🤽🏼‍♀️", "🤽🏼‍♂", "🤽🏼‍♂️",
"🤽🏽‍♀", "🤽🏽‍♀️", "🤽🏽‍♂", "🤽🏽‍♂️", "🤽🏾‍♀",
"🤽🏾‍♀️", "🤽🏾‍♂", "🤽🏾‍♂️", "🤽🏿‍♀", "🤽🏿‍♀️",
"🤽🏿‍♂", "🤽🏿‍♂️", "🤾‍♀", "🤾‍♀️", "🤾‍♂", "🤾‍♂️",
"🤾🏻‍♀", "🤾🏻‍♀️", "🤾🏻‍♂", "🤾🏻‍♂️", "🤾🏼‍♀",
"🤾🏼‍♀️", "🤾🏼‍♂", "🤾🏼‍♂️", "🤾🏽‍♀", "🤾🏽‍♀️",
"🤾🏽‍♂", "🤾🏽‍♂️", "🤾🏾‍♀", "🤾🏾‍♀️", "🤾🏾‍♂",
"🤾🏾‍♂️", "🤾🏿‍♀", "🤾🏿‍♀️", "🤾🏿‍♂", "🤾🏿‍♂️", "🦸‍♀",
"🦸‍♀️", "🦸‍♂", "🦸‍♂️", "🦸🏻‍♀", "🦸🏻‍♀️", "🦸🏻‍♂",
"🦸🏻‍♂️", "🦸🏼‍♀", "🦸🏼‍♀️", "🦸🏼‍♂", "🦸🏼‍♂️",
"🦸🏽‍♀", "🦸🏽‍♀️", "🦸🏽‍♂", "🦸🏽‍♂️", "🦸🏾‍♀",
"🦸🏾‍♀️", "🦸🏾‍♂", "🦸🏾‍♂️", "🦸🏿‍♀", "🦸🏿‍♀️",
"🦸🏿‍♂", "🦸🏿‍♂️", "🦹‍♀", "🦹‍♀️", "🦹‍♂", "🦹‍♂️",
"🦹🏻‍♀", "🦹🏻‍♀️", "🦹🏻‍♂", "🦹🏻‍♂️", "🦹🏼‍♀",
"🦹🏼‍♀️", "🦹🏼‍♂", "🦹🏼‍♂️", "🦹🏽‍♀", "🦹🏽‍♀️",
"🦹🏽‍♂", "🦹🏽‍♂️", "🦹🏾‍♀", "🦹🏾‍♀️", "🦹🏾‍♂",
"🦹🏾‍♂️", "🦹🏿‍♀", "🦹🏿‍♀️", "🦹🏿‍♂", "🦹🏿‍♂️", "🧍‍♀",
"🧍‍♀️", "🧍‍♂", "🧍‍♂️", "🧍🏻‍♀", "🧍🏻‍♀️", "🧍🏻‍♂",
"🧍🏻‍♂️", "🧍🏼‍♀", "🧍🏼‍♀️", "🧍🏼‍♂", "🧍🏼‍♂️",
"🧍🏽‍♀", "🧍🏽‍♀️", "🧍🏽‍♂", "🧍🏽‍♂️", "🧍🏾‍♀",
"🧍🏾‍♀️", "🧍🏾‍♂", "🧍🏾‍♂️", "🧍🏿‍♀", "🧍🏿‍♀️",
"🧍🏿‍♂", "🧍🏿‍♂️", "🧎‍♀", "🧎‍♀️", "🧎‍♂", "🧎‍♂️",
"🧎🏻‍♀", "🧎🏻‍♀️", "🧎🏻‍♂", "🧎🏻‍♂️", "🧎🏼‍♀",
"🧎🏼‍♀️", "🧎🏼‍♂", "🧎🏼‍♂️", "🧎🏽‍♀", "🧎🏽‍♀️",
"🧎🏽‍♂", "🧎🏽‍♂️", "🧎🏾‍♀", "🧎🏾‍♀️", "🧎🏾‍♂",
"🧎🏾‍♂️", "🧎🏿‍♀", "🧎🏿‍♀️", "🧎🏿‍♂", "🧎🏿‍♂️", "🧏‍♀",
"🧏‍♀️", "🧏‍♂", "🧏‍♂️", "🧏🏻‍♀", "🧏🏻‍♀️", "🧏🏻‍♂",
"🧏🏻‍♂️", "🧏🏼‍♀", "🧏🏼‍♀️", "🧏🏼‍♂", "🧏🏼‍♂️",
"🧏🏽‍♀", "🧏🏽‍♀️", "🧏🏽‍♂", "🧏🏽‍♂️", "🧏🏾‍♀",
"🧏🏾‍♀️", "🧏🏾‍♂", "🧏🏾‍♂️", "🧏🏿‍♀", "🧏🏿‍♀️",
"🧏🏿‍♂", "🧏🏿‍♂️", "🧔‍♀", "🧔‍♀️", "🧔‍♂", "🧔‍♂️",
"🧔🏻‍♀", "🧔🏻‍♀️", "🧔🏻‍♂", "🧔🏻‍♂️", "🧔🏼‍♀",
"🧔🏼‍♀️", "🧔🏼‍♂", "🧔🏼‍♂️", "🧔🏽‍♀", "🧔🏽‍♀️",
"🧔🏽‍♂", "🧔🏽‍♂️", "🧔🏾‍♀", "🧔🏾‍♀️", "🧔🏾‍♂",
"🧔🏾‍♂️", "🧔🏿‍♀", "🧔🏿‍♀️", "🧔🏿‍♂", "🧔🏿‍♂️", "🧖‍♀",
"🧖‍♀️", "🧖‍♂", "🧖‍♂️", "🧖🏻‍♀", "🧖🏻‍♀️", "🧖🏻‍♂",
"🧖🏻‍♂️", "🧖🏼‍♀", "🧖🏼‍♀️", "🧖🏼‍♂", "🧖🏼‍♂️",
"🧖🏽‍♀", "🧖🏽‍♀️", "🧖🏽‍♂", "🧖🏽‍♂️", "🧖🏾‍♀",
"🧖🏾‍♀️", "🧖🏾‍♂", "🧖🏾‍♂️", "🧖🏿‍♀", "🧖🏿‍♀️",
"🧖🏿‍♂", "🧖🏿‍♂️", "🧗‍♀", "🧗‍♀️", "🧗‍♂", "🧗‍♂️",
"🧗🏻‍♀", "🧗🏻‍♀️", "🧗🏻‍♂", "🧗🏻‍♂️", "🧗🏼‍♀",
"🧗🏼‍♀️", "🧗🏼‍♂", "🧗🏼‍♂️", "🧗🏽‍♀", "🧗🏽‍♀️",
"🧗🏽‍♂", "🧗🏽‍♂️", "🧗🏾‍♀", "🧗🏾‍♀️", "🧗🏾‍♂",
"🧗🏾‍♂️", "🧗🏿‍♀", "🧗🏿‍♀️", "🧗🏿‍♂", "🧗🏿‍♂️", "🧘‍♀",
"🧘‍♀️", "🧘‍♂", "🧘‍♂️", "🧘🏻‍♀", "🧘🏻‍♀️", "🧘🏻‍♂",
"🧘🏻‍♂️", "🧘🏼‍♀", "🧘🏼‍♀️", "🧘🏼‍♂", "🧘🏼‍♂️",
"🧘🏽‍♀", "🧘🏽‍♀️", "🧘🏽‍♂", "🧘🏽‍♂️", "🧘🏾‍♀",
"🧘🏾‍♀️", "🧘🏾‍♂", "🧘🏾‍♂️", "🧘🏿‍♀", "🧘🏿‍♀️",
"🧘🏿‍♂", "🧘🏿‍♂️", "🧙‍♀", "🧙‍♀️", "🧙‍♂", "🧙‍♂️",
"🧙🏻‍♀", "🧙🏻‍♀️", "🧙🏻‍♂", "🧙🏻‍♂️", "🧙🏼‍♀",
"🧙🏼‍♀️", "🧙🏼‍♂", "🧙🏼‍♂️", "🧙🏽‍♀", "🧙🏽‍♀️",
"🧙🏽‍♂", "🧙🏽‍♂️", "🧙🏾‍♀", "🧙🏾‍♀️", "🧙🏾‍♂",
"🧙🏾‍♂️", "🧙🏿‍♀", "🧙🏿‍♀️", "🧙🏿‍♂", "🧙🏿‍♂️", "🧚‍♀",
"🧚‍♀️", "🧚‍♂", "🧚‍♂️", "🧚🏻‍♀", "🧚🏻‍♀️", "🧚🏻‍♂",
"🧚🏻‍♂️", "🧚🏼‍♀", "🧚🏼‍♀️", "🧚🏼‍♂", "🧚🏼‍♂️",
"🧚🏽‍♀", "🧚🏽‍♀️", "🧚🏽‍♂", "🧚🏽‍♂️", "🧚🏾‍♀",
"🧚🏾‍♀️", "🧚🏾‍♂", "🧚🏾‍♂️", "🧚🏿‍♀", "🧚🏿‍♀️",
"🧚🏿‍♂", "🧚🏿‍♂️", "🧛‍♀", "🧛‍♀️", "🧛‍♂", "🧛‍♂️",
"🧛🏻‍♀", "🧛🏻‍♀️", "🧛🏻‍♂", "🧛🏻‍♂️", "🧛🏼‍♀",
"🧛🏼‍♀️", "🧛🏼‍♂", "🧛🏼‍♂️", "🧛🏽‍♀", "🧛🏽‍♀️",
"🧛🏽‍♂", "🧛🏽‍♂️", "🧛🏾‍♀", "🧛🏾‍♀️", "🧛🏾‍♂",
"🧛🏾‍♂️", "🧛🏿‍♀", "🧛🏿‍♀️", "🧛🏿‍♂", "🧛🏿‍♂️", "🧜‍♀",
"🧜‍♀️", "🧜‍♂", "🧜‍♂️", "🧜🏻‍♀", "🧜🏻‍♀️", "🧜🏻‍♂",
"🧜🏻‍♂️", "🧜🏼‍♀", "🧜🏼‍♀️", "🧜🏼‍♂", "🧜🏼‍♂️",
"🧜🏽‍♀", "🧜🏽‍♀️", "🧜🏽‍♂", "🧜🏽‍♂️", "🧜🏾‍♀",
"🧜🏾‍♀️", "🧜🏾‍♂", "🧜🏾‍♂️", "🧜🏿‍♀", "🧜🏿‍♀️",
"🧜🏿‍♂", "🧜🏿‍♂️", "🧝‍♀", "🧝‍♀️", "🧝‍♂", "🧝‍♂️",
"🧝🏻‍♀", "🧝🏻‍♀️", "🧝🏻‍♂", "🧝🏻‍♂️", "🧝🏼‍♀",
"🧝🏼‍♀️", "🧝🏼‍♂", "🧝🏼‍♂️", "🧝🏽‍♀", "🧝🏽‍♀️",
"🧝🏽‍♂", "🧝🏽‍♂️", "🧝🏾‍♀", "🧝🏾‍♀️", "🧝🏾‍♂",
"🧝🏾‍♂️", "🧝🏿‍♀", "🧝🏿‍♀️", "🧝🏿‍♂", "🧝🏿‍♂️", "🧞‍♀",
"🧞‍♀️", "🧞‍♂", "🧞‍♂️", "🧟‍♀", "🧟‍♀️", "🧟‍♂", "🧟‍♂️",
"👨‍🦰", "👨‍🦱", "👨‍🦲", "👨‍🦳", "👨🏻‍🦰", "👨🏻‍🦱",
"👨🏻‍🦲", "👨🏻‍🦳", "👨🏼‍🦰", "👨🏼‍🦱", "👨🏼‍🦲", "👨🏼‍🦳",
"👨🏽‍🦰", "👨🏽‍🦱", "👨🏽‍🦲", "👨🏽‍🦳", "👨🏾‍🦰", "👨🏾‍🦱",
"👨🏾‍🦲", "👨🏾‍🦳", "👨🏿‍🦰", "👨🏿‍🦱", "👨🏿‍🦲", "👨🏿‍🦳",
"👩‍🦰", "👩‍🦱", "👩‍🦲", "👩‍🦳", "👩🏻‍🦰", "👩🏻‍🦱",
"👩🏻‍🦲", "👩🏻‍🦳", "👩🏼‍🦰", "👩🏼‍🦱", "👩🏼‍🦲", "👩🏼‍🦳",
"👩🏽‍🦰", "👩🏽‍🦱", "👩🏽‍🦲", "👩🏽‍🦳", "👩🏾‍🦰", "👩🏾‍🦱",
"👩🏾‍🦲", "👩🏾‍🦳", "👩🏿‍🦰", "👩🏿‍🦱", "👩🏿‍🦲", "👩🏿‍🦳",
"🧑‍🦰", "🧑‍🦱", "🧑‍🦲", "🧑‍🦳", "🧑🏻‍🦰", "🧑🏻‍🦱",
"🧑🏻‍🦲", "🧑🏻‍🦳", "🧑🏼‍🦰", "🧑🏼‍🦱", "🧑🏼‍🦲", "🧑🏼‍🦳",
"🧑🏽‍🦰", "🧑🏽‍🦱", "🧑🏽‍🦲", "🧑🏽‍🦳", "🧑🏾‍🦰", "🧑🏾‍🦱",
"🧑🏾‍🦲", "🧑🏾‍🦳", "🧑🏿‍🦰", "🧑🏿‍🦱", "🧑🏿‍🦲", "🧑🏿‍🦳",
"❤‍🔥", "❤️‍🔥", "❤‍🩹", "❤️‍🩹", "🏳‍⚧", "🏳️‍⚧️", "🏳‍🌈",
"🏳️‍🌈", "🏴‍☠", "🏴‍☠️", "🐈‍⬛", "🐕‍🦺", "🐻‍❄", "🐻‍❄️",
"👁‍🗨", "👁️‍🗨️", "😮‍💨", "😵‍💫", "😶‍🌫", "😶‍🌫️",
// comment for clang-format
"🧑‍🎄"};
if (str.size() >= 40) {
constexpr size_t MAX_EMOJI_LENGTH = 35;
static const std::unordered_set<Slice, SliceHash> emojis = [max_emoji_length = MAX_EMOJI_LENGTH] {
#if TD_HAVE_ZLIB
Slice packed_emojis(
"eJyNnety27i2rV8lVfvf-XXul7frVmfZ6cS6kKJskrLj7tUUaUmOI1uObcUEWLUfJecBdledFzgaJIfJiUkyqyqAJ4wxJzE_QABF2fGPs_"
"jdj7Plux_T9bFsjmV7LLfHsjuW_bsfF-ZY7LsfYXAsi3c_og_H8vuxfDyWT8dydizjY5kcy_RYZsfiHYt_LPNjKd79iI9f4z-P5Rg_"
"PsaPjzHjY8zl-2P5x7EcfZfH-Mtj__LhWI7XXX47lu_HctReHjWXx-tdHq93efPux9XxmldHn6tj3Kuj39VxXFcX7358Pn79fH4ssI_j_"
"3y89u0xt9vLdz--HMf1ZfHu7-tf3h-r36bH6mSCykPlo5qjClBBd3KO6gJViCpCFR-rD7-iKq2vqB5Q7VE9ovqG6gnVC6oDqu_"
"H6vcZKlzt7BdUiHI2QvUbKozq7B-oTlCdovqA6ndUH1F9QnWGaowKoz9DHmeIfFZGRh5nyOMMeZwhjzPkcYY8zpDHGfI4w-"
"iPs36sLlFdofqM6hrVH6i-oLpDhSzPdqjuUSHfM-R7hnzPkO_ZMyrke4Z8z5Dv2SuqHJVBZVEVx2qM9MdIf4z0x0h_jPTHSH-M9MdIf4z0x0h_"
"jPTHSH-M9MdIf4z0x0h_jPTHSH-M9MdIf4z0x0h_jPTHSH-M9MdIf4z0x0h_jPTHSH-M9MdIf4z0x3-i-"
"ieqv1AlqFaoUlQZqhtUa1QbVFtUt6iAbgx0Y6AbA90Y6MZANwa6MdCNgW6MpTIGvzH4jcFvDH5j8BuD3xjoxkA3AboJ0E2AbgJ0E6CbAN0E6CZ"
"ANwG6CdBNgG4CdBOgmwDdBOgmQDcBugnQTYBuAnQTpD9B-hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_QfoTpD9B-"
"hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_wfKZgMEEDKZgMAWDKRhMwWAKBlMwmILBFAymYDAFgykYTJHvFPlOke8U-U6R7xT5TpHvFPlOke8U-"
"U6R7xT5TpHvFPlOke8U-U6R7xT5TpHvFPlOke8U-"
"U6R4BQZTZHRFBlNkdEUGU2R0RQZTZHRDBnNkNEMGc2Q0QwZzZDRDBnNkNEMGc2Q0QwZzTCrM8zqDLM6w6zOkOUMWc6Q5QxZzpDlDC-"
"IGV4QM7wgZnhBzPCCmOEFMcMLYoYXxAwviBleEDO8IGZ4QcxAaAZCMxCagdAMhGYgNAOhGQjNQGgGQjMQmoHQDIRmIDQDoRkIzUBohhUxw4qYY"
"UXMAGyGFTHDiphhRczAbwZ-M_Cbgd8M_GbgNwM_D-"
"g8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8"
"UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9S8khpWnQ90PladD34--Png54OfD34--"
"Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--"
"Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--M3Bbw5-c_Cbg98c_ObgNwe_"
"OfjNwW8OfnPwm4PfHPzm4DcHvzn4zcFvDn5z8JuD3xz85uA3B785-M3Bbw5-c_Cbg98c_ObgNwe_"
"OfjNwW8OfnPwm4PfHPzm4DcHvzn4zcFvDn5z8JuD3xz85uA3B785-M3Bbw5-c_Cbg98c_ObgNwe_"
"OfjNwW8OfnPwm4PfHPzmoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWg"
"BqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWgBqAagFoBaAWoBVt0D6C6S_"
"QPoLpL9A5gtkvkDmC2S-QOYLZL5A5gtkvkDmC2S-QOYLZL5A5gtkvkDmC2S-QOYLZL5A5gtkvkDmC2S-QOYLZL7A-"
"M5xjXNc4xySCwz3AsO9wHAv8CK5wHSHmO4Q0x1iukNMd4jpDjHdIaY7xHSHmO4Q0x1iukPkGyLfEPmGyDfEdIdIOkTSIZIOkXSIpEMMKMSAQiQ"
"dIukQSYdIOkTSIZIOkXSIpEMkHSLpEEmHSDpE0iEyCpF0iKRDJB1iukNMd4jpDjHdIaY7xHSHmO4Q0x1iukNMd4jpDjHdIaY7xHSHmO4Q0x1iu"
"kNMdwicIfiF4BeCXwh-IfhF4BeBXwR-EfhF4BeBXwR-EfhF4BeBXwR-EfhF4BeBXwR-"
"EfjFiBcjXox4MeLFiBcjXox4MeLFiBcjXox4MeLFiBcjXox4cRkP8xFjPmLMR4z5iDEfMeYjxnzEmI8Y8xFjPmLMR4z5iDEfMeYjxnzEmI8Y8x"
"FjPmLMR4z5iDEfMeYjxnzEmI8Y8xFjPmLMR4z5iDEfMeYjxnzEmI8Y8xFjPmLMR4z5iDEfMeYjxnzEmI8Y8xFjPmLMR4z5iDEfMeYjxnzEmI8l"
"-C3Bbwl-S_Bbgt8S_JYgtASSJZAsgWQJEEuAWALEEvkuke8S-"
"S4x8CUGvsSolhjVEqNaYlRLjGqJUS0xqiVGtcSoroHpGpiugekamK6B6RqYroHpGpiugekamK6B6RpXuwaSBCNNMJcJ5jLBXCYYeIKBJxh4grl"
"MMJcJUkiQQoIUEsxlgrlMMJcJ5jLBXCbILUFuCXJLMMgEg0wwyASDTDDIBINMMMgEg0wwyASDTDDIBINMgCTBXCaYywRzmZQDx1wmmMsEc5mAW"
"gJqCagloJaAWgJqCaglAJZgGhNMY4JpXGEaV5jGFaZxhWlcYRpXmMYVXgErvAJWeAWs8ApY4RWwArUVqK1AbQVqK1BbgdoK1FagtgK1FaitQG0"
"FaitQW4HaCtRWoLYCtRWorUBtBWorUFuB2grUVqC2ArUVqK1AbQVqK1BbgdoK1FagtgK1FaitQG0FaitQW4HaCtRWoLYCtRWorUBtBWorUFuB2"
"grUVlhrK6BbAd0K6FZAlwJdCnQp0KVAlwJdCnQpdpAU_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_"
"FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_FLwS8EvBb8U_"
"DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_"
"DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_DLwy8AvA78M_NaIt0a8NeKtEW-"
"NeGsEWCPAGgHWCLBGgA2Ib0B8A-IbEN-A-AbENyC-"
"AcQNIG4AcQOIG0DcAOIGEDeAuAHEDSBuAHEDiBtA3ADiBhA3gLgBxA0gbgBxA4gbQNwA4gYQN4C4AcQNIG4AcQOIG0DcIMsNstwgyw2y3CDLDa"
"htQG0DahskvUHSGyS9RapbpLpFqlukukWqW6S6RZZbZLlFlltkuUWWW2S5RZZbZLlFlltkuUUeW-SxRR5b5LFFHlvksUUeW-"
"SxxXC3GO4Ww91iuFsMd4vhbp_e_fv6-O8_Xqbv_v3u-A_Gj19ylNL89XeU0nz_"
"T5TKPKCU5kmAUpkLlMo8R6nMC5TKDFEqM0KpzDVKZW5QSvPsBqU0p2WpzC8olXmHUplfUSrzHqUyH1Aq8wWlMg8olfkdpTSP7xOPpTTPNyiVuU"
"WpzCeU0rz4BaUyX1EqM0cpzeOd9bFU5q8olTlCqczfUCrzPUplTlAq00OpzBClMq9QKvMPlMr8J0pl_oVSmSlKZW5QKvMOpTK_"
"olTmC0plHlAq8ztKaR5vdY-lMkcolXmNUpl_oFTmXyiVuUKpzBSlMm9QKvMVpTItSmkeb1ePpTIDlMpcoFTmOUplXqBUZoRSmUuUyrxEqcw_"
"UCozQ6nMHUpl3qOU5vIDSmWWpTI9lMqco1TmGqUydyiVeY9SmY8olfmMUpkvKJV5QCnNyxFKZX5AqczfUSrzDKUyxyiVWZbK9FEqM0CpzHOUyr"
"xCqcw_USpzj1KZjyilefUepTJPUSrzL5TKTFBK8_OfKKV5vPc7lsr8hlKat_9AqcwTlMo8RYH5f3_ZoVSmQSnNjxcolRmhwPz7-h-"
"7sqob92VVN2xZ1Y2irKrGh1FZ1Y3nsqoaZ3-"
"WVd1IyqpurMqqbqRlVTeysqobN2VVN9ZlVTc2ZVU3tmVVN27Lqm48lVXVGJuyqhqT87KqGxdlVTeisqobcVnVjWVZ1Y3PZVU3rsuqakw_"
"lVXdOCurujEuq7oxKau6EZRV3ViUVd04L6u6cVFWdSMsq7oRlVXdiMuqbizLqm5cllXduCqruvG5rOrGdVnVjX1Z1Y1vZVU3nsuqaszKZ_"
"v1ovB-LauqMTdlVTUWv5dV3fhYVnXja1nVjV1Z1Y19WdWNx7KqG9_Kqm48lVXdeC6ruvFSVnXjUFZV4_y0rOrGx7KqG5_"
"Kqm6clVXdGJdV3ZiVVd1YlVXduCmrunFfVnXjoazqRl5WVeNiVFZ147eyqhvvy6pueGVVN_yyqhvzsqobl2VVN67Kqm58Lqu68WdZ1Y2_"
"yqpu3JRV3fhaVnVjX1Z143tZVY3lp7KqG-OyqhuTsqobVVU3_iiruvFnWdWNf5ZV3firrOpGUlZ1Y1VWdWNdVnVjV1Z1Y19WaPzbj9_-"
"evdvRwtf_1NZ1Y3_XFZ147-UVd34r2VVN_5bWdWN_15WdeN_lFXd-J9lVTf-V1nVjf9dVnXj_5RV3fj7-"
"jQ9lhsaaxobGlsatzTuaNzTeKDxSOOJxjONFxoHGt9p5DQMDbzATzM0aGQ01jQ2NLY0bml8oXFH4yuNexoPNPY0Hmk80Xim8ULjQOOVRk7D0ig"
"Hf8PB3xDvDQd_wzHfcMw3HPMNxwxjR-OexgONPY1HGt9oPNP4TuOVRk7D0LA0yjGvSXXNga2Jbs3xrDmMNa--pvuGKW-"
"Y8oYBNwy4YaYbDnVDvBvi3XDwW9LYchhbDmPLYWw5jC0D3nIYt1w2tyR_y_HccgpuObBbDuyWF70l-"
"Vte65bkbwn8lqvltn31FxoHGt9p5DRK8l-Yzhde4gsv8YUBvzDOF8a5I9475nXHvO445jsGvGPAO4K645jveIk7jvmO1_"
"rKgF8Z5yvdvzL3HTU7MtyR4Y4Md3TfcRi7tvszjZyGpVGuqHtO5T2n8p653_MS92R4z4D3TOee6dwTHYxXGuW1HniJB0Z-INUHJvjA1fLATB-"
"Y6QOv_kDyD0z5gSk_EN0Dc3_gFDxwzA8c8wPH_"
"MAxP3DMDwQFw9B4y6Iktmc6e6azZxZ7ZrFnFnsy3HPwew51z6HuOcI9x7PntR6Z6Tde9Buv9Y3X-sZrfSOxbyT2jRf91o6zp_"
"FM44XGgUZOo8z9iVd_5tWfmcUz3Z85-Ge6v9ALRkbjhsaaxobGLY0vNO5ofKWxo3FP44HGnsYjjWcaLzQONF5pGBqWRkn-wMEfOOYDx3wg-"
"QPHfOCYDxzqgUM9cKgHDvXAoR441AOHeuAIDxzhgTAPHNh3Duw7r_6dkb8z8nem_J15faf7K91fmdcrp-CVAV9J_"
"pUBXzm5OXPPeQnDTC3jWGZR8FoFR1jU6Uwf_98fv2bH8s9jWR3LXd0u3M79sfx1LI9dnc_HcluLCjz6KX9Qqfqa119N_dXWXws8Sah05de8_"
"mrqr7b-WuDHNitd-TWvv5r6q62_Qvep1n2qdZ9q3ada96nWndW6s1p3VuvOat1ZrRvXunGtG9e6ca0b1z98NflH_ZNZpZHTMDQsjZLciD_"
"GNeKPco3441wj_kjXiOLfKP6N4t8o_o3i3yh-T_F7it9T_"
"J7i9xSfUnxK8SnFpxSfUvyR4o8Uf6T4I8UfKf5E8SeKP1H8ieJPFJ9RfEbxGcVnFJ_VYo_oPKLziM4jOo_oPKLziM4jOo_oPKLzTig-"
"ofiE4hOKTygmOo_oPKLziM4jOu8DxR8o_kDxB4o_UPw7xb9T_DvFv1P8O8WcFI-T4nFSPE6Kx0nxOCkeJ8XjpHicFI-"
"T4nFSPE6Kx0nxOCne26SMKR5TPKZ4TDFfKd6E4gnFE4onFE8onlI8pXhK8ZTiKcUzimcUzyieUTyjOKU4pTilOKU4pTijOKM4ozijOKP4huIbi"
"m8ovqH4huI1xWuK1xSvKV5TvKV4S_GW4i3FW4pvKb6l-JbiW4pvKf5C8ReKv1D8heIvFN9RfEfxHcV3FN9RvKN4R_GO4h3FO4rvKb6n-"
"J7ie4rvKX6g-IHiB4ofKH6geE_xnuI9xXuK9xQ_UvxI8SPFjxQ_UvyN4m8Uf6P4G8XfKH6i-IniJ4qfKH6i-JniZ4qfKX6m-JniF4pfKH6h-"
"IXiF4pzinOKc4pzivNa7P9ai0sjp2FoWBqlmPuzz_3Z5_7sc3_2uT_73J997s8-92ef-7PP_dnnCevzhPV5wvo8YX2esD43c5-buc_"
"N3Odm7nMz97mZ-"
"9zMfW7mPjdzn5u5zx3J547kc0fyuSP53JF8j2KPYo9ij2KP4g3FG4o3FG8o3tTiBZfogkt0wSW64BJdcIkuuEQXXKILLtEFl-iCS3TxneLvFH-"
"n-DvF32vx-awWl0ZOw9CwNErxguIFxQuKFxQvKD6n-"
"Jzic4rPKT6vxRHXRsS1EXFtRFwbEddGxLURcW1EXBsR10bEtRFxbURcGxHXRsS1EXFtRDxhI56wEU_"
"YiCdsxBM24gkb8YSNeMJGPGEjnrART9iIJ2zEEzbiCRvxhI14wkY8YSOesBFP2IgnbMT1HHE9R1zPEddzxPUc_"
"1WLSyOnYWhYGqWYSzTmEo25RGMu0ZhLNOYSjblEYy7RmEs05hKNuYvG3EVj7qIxd9GYu-"
"jyl1pcGjkNQ8PSKMWclCUnZclJWXJSlpyUhOKE4oTihOLkTUzOCTkn5JyQc0LOSUhxSHFIcUhxSHFEcURxRHFEcURxTHFMcUxxTHFM8ZLiJcVL"
"ipcULym-pPiS4kuKLym-pPiK4iuKryi-"
"oviK4s8Uf6b4M8WfKf5M8TXF1xRfU3xN8TXFvAlMeBOY8CYw4U1gwpvAZEfxjuIdxTuKdxTzTibhnUzCO5mEdzIJ72QS3skkvJNJeCeT8E4m4Z"
"1MwjuZhHcyCe9kEt7JJLyTSfgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTPgaTHgnk_"
"BOJuGdTMI7mYR3MgnvZBLeySS8k0l4J5PwTiY5UHyg-"
"EDxgeIDxYZiQ7Gh2FBsKLYUW4otxZZiW4tXTHDFBFdMcMUEV0wwJeeUnFNyTsk5JeeUnFNyTsk5JeeUnFOiS4kuJbqU6FKiS4kuJbqU6FKiS4k"
"ufaX4leJXil8pfq3FGU-rjKdVxtMq42mV8bTKeFplPK0ynlYZT6uMp1XGXTTjLppxF824i2bcRTPefWW8-8p495Xx7ivj3VfmU-xT7FPsU-"
"xTPKd4TvGc4jnFc4oDigOKA4oDigOKeY-U8R4p4z1SxnukjPdIGe-RMt4jZbxHyniPlPEeKbug-"
"ILiC4ovKL6gmAdQxgMo4wGU8QDKeABlPIAyHkAZD6CMB1DGAyjjAZTxAMp4AGU8gDIeQBkPoIwHUMYDKOMBlPEAyngAZTyAMh5AGQ-"
"gjAdQxgMo4wGU8QDKeABlPIC2fJ-y5fuULd-nbPk-Zcv3KVs-gtvyEdyWj-"
"C2fAS35SO4LW9ct7xx3fLGdcsb1y1vXLc7incU7yjeUbyjmAfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQlgfQ"
"lgfQlgfQlgfQlgfQlhvjlhvjlhvjlhvjlhvjlhvjlhvjlhvjlhvj9u2t9M2PX8Y_rpJj_Xf9q5L1d_CjAeqb5Xf8T_1q1fd3_"
"auYtPW3srbd0a1VHXH7w3dcpf9i_ddca_G695rrXv-Oa647rokZFBPzd-vJ2as7QR2dTU8-"
"5OZ2Nj1myM3tbHrskJvb2fQUQ25up1xoA2SGNErQx2lIowR91IY0StDHcEijBH1EezV4X9GTqewy_V22v-"
"vtWnnvus6H1rVyy4fc3M6O0Xe5uZ0dmXW5uZ39WXev67Zb55p1vIc0_Wt2IEgnrs41OxCkE17nmh0I0omyc80OBBEauQxf-"
"7tMf5ft73q7luld12ZoXSu3fMjN7ewYfZeb29mRWZeb29mfdfe6brt1rlnHe0jTv2YHgnTi6lyzA0E64XWu2YEgnSg71-xAEKHpWde6K-_"
"vsv1db9eyvevaDq1r5ZYPubmdHfS73NzOjsy63NzO_qy713XbrXPNOt5Dmv41OxCkE1fnmh0I0gmvc80OBOlE2blmB4IITc-"
"61l15f5fp73q7VtG7rouhda3c8iE3t7NjiF1ubmcH7y43t7M_6-"
"513XbrXLOO95Cmf80OBOnE1blmB4J0wutcswNBOlF2rtmBIELTs651V97fZfq7qp-HEPPe-o58My9k6y7Z2pWJ9_XrnpT7nLouofr-"
"rt8Vd7yrbr-d7ngf3fEGuv5WR9z-8B1X6b9Y1zX_7nvT3urRm0qnWz7k5nY666PPze10106Pm9vpLO8-"
"N7ezxjaEpKuz6elF0tXZ9PQi6epsenqRdHU2Pb1Iujq799AO7yFN9x76kyCduNQe-pMgnfDUHvqTIJ0o1R76kyCDYIeW3JBGCX4GtncdtgU_"
"A9u7KtuCn4HtXaNtwc_AKk3HKdXdZfq7bH9Xz7UUVdnVcy1FQHa9Xav7YVSrp3u_7n4Y1efmdnaQ6n0Y1elmh9zczg7CvQ-"
"jlNt6CElXZ8ekdbl1I1kPIenq7JjsLrduJF0LoXu_Hnw-1-E9pOnfi3_2fG4oSB-1IU3_Xvyz53NDQfqIDmn69-KeIL0rr3MvHggyCHZoOQ5p-"
"vfigSCDYIeWaq-m4w1Hd5fp77L9XT3XUlPTs1_rrp5riby6H7K2err36-"
"6HrH1ubmcHqd6HrJ1udsjN7ewg3PuQVbmth5B0dXa8FLrcupGsh5B0dXZMdpdbN5KuhdC9Xw8-d-7wHtL078U_e-48FKSP2pCmfy_-"
"2XPnoSB9RIc0_XtxT5Delde5Fw8EGQQ7tByHNP178UCQQbBDS7VX07Nf9zzQ7-6y_V0911JT03Prrbt6riXy6v7woNXTvV93f3jQ5-"
"Z2dryEej886HSzQ25uZwfh3g8PlNt6CElXZ8fMdLl1I1kPIenq7JjsLrduJF0LoXu_Hvw8pcN7SNO_F__s85ShIH3UhjT9e_"
"HPPk8ZCtJHdEjTvxf3BOldeZ178UCQQbBDy3FI078XDwQZBDu0VHs1Pft1zwdV3V2mv6vnWmpqevZr3dVzLZFX94dirZ7u_br7Q7E-"
"N7ezA0fvh2KdbnbIze3sINz7oZhyWw8h6ersmJkut24k6yEkXZ0dy73LrRtJ10Lo3q_7PhPrIzOk6d-"
"LB4J04urciweCdMLr3IsHgnSi7NyLB4IMgh1ackOa_r14IMgg2KHlOKTp34sHggyCHVqqvZqe_Vp35f1dpr_LdnepqenZr3VXz7UIJ_Pa367-"
"h-ry9zo0L_lrHt3P-zs1SmD-hSCuRgnsvxDE1ShB8S8EcTU_h9FPYTD9_rwHE-7PdDBF3Vn_lbJW-21Z1Muvpyvv7zL9Xba_"
"621YHY8xnV876p83V9O_xAaCuJr-JTYQxNX0L7GBIJ1MBmH0UxhMvz_vwYT7Mx1MUXe2l6F4iKvz6VmGusv0d9n-"
"rrdhdTydkYPpfjrTqenfLgeCuJr-JTYQxNX0L7GBIJ1MBmH0UxhMvz_vwYT7Mx1MUXe2l6F4NqXz6VmGusv0d9n-"
"rrdhdbzplIPpftPZqelfYgNBXE3_djkQxNX0L7GBIJ1MBmH0UxhMvz_vwYT7Mx1MUXe2l6F4y63z6VmGusv0d9n-"
"rrdhddxLy8F030t3avqX2EAQV9O_xAaCuJr-7XIgiKv5OYx-CoPp9-c9mHB_poMp6s72MhTvJHQ-PctQd5n-Ltvf1fz2bfm95ldqne-Zju_"
"Zju-9xcub7712fM90fM92fO8tnumIZzrGbDrimY54tiOe7YhnO8ZsO-IVHfGKjnhFR7xCjrn6_d3yr8nR_I_qP9SvWucts91x-"
"aFltjrqP0Rd2-XfYqadN3b5h5BpJy279WvD5R8Apv2lsf3Xlt2KGbR-27b8S5G1Xf5JPtp-Y5d_fYp2K04qfz-zRtM0mezbd86dpiuoUTVNR_"
"CG7K1dY2vazm9j1viaduK0b2S7Rtm0v8i2_-q0nesFmdO-le0acdP2ZbtG3bSd-"
"A3yXCLPFfJcIs8V8lwizxXy3EGeO8hzB3nuIM8d5LmDPHeQ5w7y3EGeO8hzB3nuIM8d5LmDPHeQ5w7y3EFuJHKjkBuJ3CjkRiI3CrlxkBsHuXG"
"QGwe5cZAbB7lxkBsHuXGQGwe5cZAbB7lxkBsHuXGQGwe5cZBbidwq5FYitwq5lcitQm4d5NZBbh3k1kFuHeTWQW4d5NZBbh3k1kFuHeTWQW4d5"
"NZBbh3k1kFuHeSFRF4o5IVEXijkhUReKOSFg7xwkBcO8sJBXjjICwd54SAvHOSFg7xwkBcO8sJBXjjICwd54SAvHORFC_"
"m6wb0WqNcN5rVAvG7wrgXadQvruoV03cK5bqFctzCuWwjXLXzrFrp1C9u6hWzdwrVuoVq3MK1biNYtPOsWmnUbi7yraDWZrLyraDVdQY1K3VXw"
"O2_InLuKVtv5sfoan3NX0WrfyHaN0rmraNr-"
"q9N2rhdkTvtWtmvEzl1F065RO3cVrbb8udwGea6Q5xJ5rpDnEnmukOcO8txBnjvIcwd57iDPHeS5gzx3kOcO8txBnjvIcwd57iDPHeS5gzx3kO"
"cOciORG4XcSORGITcSuVHIjYPcOMiNg9w4yI2D3DjIjYPcOMiNg9w4yI2D3DjIjYPcOMiNg9w4yI2D3ErkViG3ErlVyK1EbhVy6yC3DnLrILcO"
"cusgtw5y6yC3DnLrILcOcusgtw5y6yC3DnLrILcOcusgLyTyQiEvJPJCIS8k8kIhLxzkhYO8cJAXDvLCQV44yAsHeeEgLxzkhYO8cJAXDvLCQV"
"44yAsHeeEgb91VlJ94V7hpVqDq1nnLbHeUeGm2OmqstV0ipZ03domSdtKybxq7xEf7S2P7ry27FbPERfu2sUtMtP3GLvHQbsVJ5WfONRr3rqL1"
"nXOn6QpqVO5dxdt33pDJu4p22_lIucYn7yra7RvZrlHKu4pW23c-"
"zfad69Vo5V1Fq10jlncVrXaNWt5VtNvy89UGea6Q5xJ5rpDnEnmukOcO8txBnjvIcwd57iDPHeS5gzx3kOcO8txBnjvIcwd57iDPHeS5gzx3kO"
"cOciORG4XcSORGITcSuVHIjYPcOMiNg9w4yI2D3DjIjYPcOMiNg9w4yI2D3DjIjYPcOMiNg9w4yI2D3ErkViG3ErlVyK1EbhVy6yC3DnLrILcO"
"cusgtw5y6yC3DnLrILcOcusgtw5y6yC3DnLrILcOcusgLyTyQiEvJPJCIS8k8kIhLxzkhYO8cJAXDvLCQV44yAsHeeEgLxzkhYO8cJAXDvLCQV"
"44yAsHeeEgf7urqP_QFgDhD6S3W8DV_sZItpruXHjnrncuvHPX2whv43ob4W1cbyu8retthbd1vQvhXbjehfAuhHfjWX302Dg2Tuyp_"
"7zw9LfaqTFFx6hlsqM1ObKpBCOn2RLkMkKuIuQyQq4iGBnBqAhGRjAqgpURrIpgZQSrIhQyQqEiFDJCISK8b7zfC8_"
"3jdd76SHZt5pKMHKaLUEuI-QqQi4j5CqCkRGMimBkBKMiWBnBqghWRrAqQiEjFCpCISNI9h8b74_"
"C82Pj9VF6SPatphKMnGZLkMsIuYqQywi5imBkBKMiGBnBqAhWRrAqgpURrIpQyAiFilDICJL9J0my1VSCkdNsCXIZIVcRchkhVxGMjGBUBCMjG"
"BXByghWRbAyglURChmhUBEKGcEh2fIWe_"
"5b30j00e9MzkCrqQQjp9kS5DJCriLkMkKuIhgZwagIRkYwKoKVEayKYGUEqyIUMkKhIhQygpyBs5a3OwNnLUdnBry7Nz-"
"aomPUMtkh5qzdVIKR02wJchkhVxFyGSFXEYyMYFQEIyMYFcHKCFZFsDKCVREKGaFQEQoZQcyZ97Xx_"
"io8vzZeX4XHrvHYCY9d47GTHnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq27hvve-"
"F533jdSw_J_l6xv5fs7xX7e8n-XrG_l-zvFft7yf5esb-X7O8V-3vJ_l6xv5fs7xX7e8n-XrG_l-zvFft9470XnvvGay89JPu9Yr-X7PeK_"
"V6y3yv2e8l-r9jvJfu9Yr-X7PeK_V6y3yv2e8l-r9jvJfu9Yr-X7PeK_XPj_Sw8nxuvZ-kh2T8r9s-S_bNi_yzZPyv2z5L9s2L_LNk_K_"
"bPkv2zYv8s2T8r9s-S_bNi_yzZPyv2z5L9s8ve__"
"XNm6boGLVMdgj27aYSjJxmS5DLCLmKkMsIuYpgZASjIhgZwagIVkawKoKVEayKUMgIhYpQyAiS_ajxHgnPUeM1kh6S_UixH0n2I8V-"
"JNmPFPuRZD9S7EeS_UixH0n2I8V-JNmPFPuRZD9S7EeS_UixH0n2I8X-pPE-EZ4njdeJ9JDsTxT7E8n-RLE_kexPFPsTyf5EsT-R7E8U-xPJ_"
"kSxP5HsTxT7E8n-RLE_kexPFPsTyf5EsT9tvE-F52njdSo9JPtTxf5Usj9V7E8l-1PF_lSyP1XsTyX7U8X-VLI_VexPJftTxf5Usj9V7E8l-"
"1PF_lSyP3XZV39z-C1Cu6kEI6fZEuQyQq4i5DJCriIYGcGoCEZGMCqClRGsimBlBKsiFDJCoSIUMoJDsuXtvB-u-0air_aL_"
"vHmR1N0jFomO8SctZtKMHKaLUEuI-"
"QqQi4j5CqCkRGMimBkBKMiWBnBqghWRrAqQiEjFCpCISOIOYuaXT8Su37U7PrRifSQ7NWuH8ldP1K7fiR3_Ujt-pHc9SO160dy14_Urh_"
"JXT9Su34kd_1I7fqR3PUjtetHcteP1K4fyV0_Urt-1Oz6kdj1o2bXj06lh2Svdv1I7vqR2vUjuetHateP5K4fqV0_krt-"
"pHb9SO76kdr1I7nrR2rXj-SuH6ldP5K7fqR2_Uju-pHa9aPm6SlN0TFqmeyQ7NWT60g-uY7Uk-tIPrmO1JPrSD65jtST60g-uY7Uk-"
"tIPrmO1JPrSD65jtST60g-uY7Uk-tIPrmO1JPrSD65jtST62jceI-F57jxGksPyX6s2I8l-7FiP5bsx4r9WLIfK_ZjyX6s2I8l-"
"7FiP5bsx4r9WLIfK_ZjyX6s2I8l-7FiP2m8J8Jz0nhNpIdkP1HsJ5L9RLGfSPYTxX4i2U8U-4lkP1HsJ5L9RLGfSPYTxX4i2U8U-"
"4lkP1HsJ5L9xGUf__XmTVN0jFomOwT7dlMJRk6zJchlhFxFyGWEXEUwMoJREYyMYFQEKyNYFcHKCFZFKGSEQkUoZATJ_rHxfhSej43Xo_"
"SQ7B8V-0fJ_lGxf5TsHxX7R8n-UbF_lOwfFftHyf5RsX-U7B8V-0fJ_lGxf5TsHxX7R8n-UbFv3hfQFB2jlskOyV69J2t9Z-"
"Q0W4JcRshVhFxGyFUEIyMYFcHICEZFsDKCVRGsjGBVhEJGKFSEQkaQ7J8a7yfh-dR4PUkPyf5JsX-S7J8U-yfJ_kmxf5LsnxT7J8n-SbF_"
"kuyfFPsnyf5JsX-S7J8U-yfJ_"
"kmxf5Lsn1z2SfrmTVN0jFomOwT7dlMJRk6zJchlhFxFyGWEXEUwMoJREYyMYFQEKyNYFcHKCFZFKGSEQkUoZATJvtlzErHnJM2ek3yTHpK92nM"
"Sueckas9J5J6TqD0nkXtOovacRO45idpzErnnJGrPSeSek6g9J5F7TqL2nETuOYnacxK55yRqz0maT68S8elV0nx6lTxLD8lefXrV-"
"s7IabYEuYyQqwi5jJCrCEZGMCqCkRGMimBlBKsiWBnBqgiFjFCoCIWMINm_NN4vwvOl8XqRHpL9i2L_Itm_KPYvkv2LYv8i2b8o9i-S_Yti_"
"yLZvyj2L5L9i2L_Itm_KPYvkv2LYv8i2b8o9ofG-yA8D43XQXpI9gfF_iDZHxT7g2R_UOwPkv1BsT9I9gfF_iDZHxT7g2R_"
"UOwPkv1BsT9I9gfF_"
"iDZHxT7FgOZfyt3mXcrZ5lvK1eZp5GzZdRsGTlbRs2WkbNl1GwZOVtGzZaRs2XUbBk5W0bNlpGzZdRsGTlbRs2WkbNl1GwZOVtGzVbr-"
"vLarevKa1rJ3ir2VrK3ir2V7K1ibyV7q9hbyd4q9layt4q9leytYm8le6vYW8neKvZWsrcu-7Q5IVJxQqTNCZG-SA_"
"BPlUnRCpPiFSdEKk8IVJ1QqTyhEjVCZHKEyJVJ0QqT4hUnRCpPCFSdUKk8oRI1QmRyhMiVSdEKk-"
"IVJ0QaXNCpOKESJsTIj1ID8lenRCpPCFSdUKk8oRI1QmRyhMiVSdEKk-"
"IVJ0QqTwhUnVCpPKESNUJkcoTIlUnRCpPiFSdEKk8IVJ1QmTNU2eaomPUMtkh2LebSjBymi1BLiPkKkIuI-"
"QqgpERjIpgZASjIlgZwaoIVkawKkIhIxQqQiEjSPbNU2eaomPUMtkh2aunzq3vjJxmS5DLCLmKkMsIuYpgZASjIhgZwagIVkawKoKVEayKUMgI"
"hYpQyAiS_bTxFh_J161Ry2SHZD9V7KeS_VSxn0r2U8V-KtlPFfupZD9V7KeS_VSxn0r2U8V-KtlPFfupZD9V7KeS_"
"VSxDxrvQHgGjVcgPST7QLEPJPtAsQ8k-0CxDyT7QLEPJPtAsQ8k-0CxDyT7QLEPJPtAsQ8k-0CxDyT7QLE_b7zPhed543UuPST7c8X-XLI_V-"
"zPJftzxf5csj9X7M8l-3PF_lyyP1fszyX7c8X-XLI_V-zPJftzxf5csj9X7C8a7wvhedF4XUgPyf5Csb-Q7C8U-wvJ_kKxv5DsLxT7C8n-QrG_"
"kOwvFPsLyf5Csb-Q7C8U-wvJ_kKxv5DsLxT7sPEOhWfYeIXSQ7IPFftQsg8V-1CyDxX7ULIPFftQsg8V-1CyDxX7ULIPFftQsg8V-"
"1CyDxX7ULIPFfuo8Y6EZ9R4RdJDso8U-0iyjxT7SLKPFPtIso8U-0iyjxT7SLKPFPtIso8U-0iyjxT7SLKPFPtIso8U-"
"7jxjoVn3HjF0kOyjxX7WLKPFftYso8V-1iyjxX7WLKPFftYso8V-1iyjxX7WLKPFftYso8V-1iyjxX7ZeO9FJ7LxmspPST7pWK_"
"lOyXiv1Ssl8q9kvJfqnYLyX7pWK_lOyXiv1Ssl8q9kvJfqnYLyX7pWK_lOyXiv1l430pPC8br0vpIdlfKvaXkv2lYn8p2V8q9peS_"
"aVifynZXyr2l5L9pWJ_KdlfKvaXkv2lYn8p2V8q9peS_aVif9V4XwnPq8brSnpI9leK_ZVkf6XYX0n2V4r9lWR_pdhfSfZXiv2VZH-"
"l2F9J9leK_ZVkf6XYX0n2V4r9lWR_pdh_brw_C8_Pjddn4XHdeFwLj-vG47rtwf-Cftey71v2Q8t2_o_42qdp3zvtB6ft_IfnjX_u-OeOf-"
"74G8ffOP7G8TeOv3X8reNvHX_r-BeOf-H4F45_0fJft3zXLb91y2fd1ju81w7vtcN77fBeO7zXDu-1w3vt8F47vNcO77XDe-"
"3wXju81w7vtcN77fBeO7zXDu-1w3stefO_Ldy17PuW_dCynf9XsPaRvNvtB6ft_Cd5jX_u-OeOf-74G8ffOP7G8TeOv3X8reNvHX_r-BeOf-"
"H4F47_G2_-wZhg9a71F2KqVmmuD-2OY-voXf6GdZyVZtVzbFW7U9n39_XZh1Ynm-WPMoZ_tMzKZ_YBrdslzEU1sO-wyyPm6n3LrHfAX0vRRfn_"
"k_7Ka1zcVL3hXdn00Rt-q2z8H2PhUzUUYdd7bf2_db5_9_8BRjaqUQ");
static string all_emojis_str = gzdecode(base64url_decode(packed_emojis).ok()).as_slice().str();
constexpr size_t EMOJI_COUNT = 4682;
#else
string all_emojis_str;
constexpr size_t EMOJI_COUNT = 0;
#endif
std::unordered_set<Slice, SliceHash> all_emojis;
all_emojis.reserve(EMOJI_COUNT);
for (size_t i = 0; i < all_emojis_str.size(); i++) {
CHECK(all_emojis_str[i] != ' ');
CHECK(all_emojis_str[i + 1] != ' ');
size_t j = i + 2;
while (j < all_emojis_str.size() && all_emojis_str[j] != ' ') {
j++;
}
CHECK(j < all_emojis_str.size());
all_emojis.insert(Slice(&all_emojis_str[i], &all_emojis_str[j]));
CHECK(j - i <= max_emoji_length);
i = j;
}
CHECK(all_emojis.size() == EMOJI_COUNT);
return all_emojis;
}();
if (str.size() > MAX_EMOJI_LENGTH) {
return false;
}
return emojis.count(str) != 0;

View File

@ -0,0 +1,173 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/algorithm.h"
#include "td/utils/ChainScheduler.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
#include "td/utils/Span.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tests.h"
#include <memory>
#include <numeric>
TEST(ChainScheduler, Basic) {
td::ChainScheduler<int> scheduler;
using ChainId = td::ChainScheduler<int>::ChainId;
using TaskId = td::ChainScheduler<int>::TaskId;
for (int i = 0; i < 100; i++) {
scheduler.create_task({ChainId{1}}, i);
}
int j = 0;
while (j != 100) {
td::vector<TaskId> tasks;
while (true) {
auto o_task_id = scheduler.start_next_task();
if (!o_task_id) {
break;
}
auto task_id = o_task_id.value().task_id;
auto extra = *scheduler.get_task_extra(task_id);
auto parents =
td::transform(o_task_id.value().parents, [&](auto parent) { return *scheduler.get_task_extra(parent); });
LOG(INFO) << "Start " << extra << parents;
CHECK(extra == j);
j++;
tasks.push_back(task_id);
}
for (auto &task_id : tasks) {
auto extra = *scheduler.get_task_extra(task_id);
LOG(INFO) << "Finish " << extra;
scheduler.finish_task(task_id);
}
}
}
struct Query;
using QueryPtr = std::shared_ptr<Query>;
using ChainId = td::ChainScheduler<QueryPtr>::ChainId;
using TaskId = td::ChainScheduler<QueryPtr>::TaskId;
struct Query {
int id{};
TaskId task_id{};
bool is_ok{};
friend td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &q) {
return sb << "Q{" << q.id << "}";
}
};
TEST(ChainScheduler, Stress) {
td::Random::Xorshift128plus rnd(123);
int max_query_id = 1000;
int MAX_INFLIGHT_QUERIES = 20;
int ChainsN = 4;
struct QueryWithParents {
QueryPtr id;
td::vector<QueryPtr> parents;
};
td::vector<QueryWithParents> active_queries;
td::ChainScheduler<QueryPtr> scheduler;
td::vector<td::vector<QueryPtr>> chains(ChainsN);
int inflight_queries{};
int current_query_id{};
bool done = false;
auto schedule_new_query = [&] {
if (current_query_id > max_query_id) {
if (inflight_queries == 0) {
done = true;
}
return;
}
if (inflight_queries >= MAX_INFLIGHT_QUERIES) {
return;
}
auto query_id = current_query_id++;
auto query = std::make_shared<Query>();
query->id = query_id;
int chain_n = rnd.fast(1, ChainsN);
td::vector<ChainId> chain_ids(ChainsN);
std::iota(chain_ids.begin(), chain_ids.end(), 0);
td::random_shuffle(td::as_mutable_span(chain_ids), rnd);
chain_ids.resize(chain_n);
for (auto chain_id : chain_ids) {
chains[td::narrow_cast<size_t>(chain_id)].push_back(query);
}
auto task_id = scheduler.create_task(chain_ids, query);
query->task_id = task_id;
inflight_queries++;
};
auto check_parents_ok = [&](const QueryWithParents &query_with_parents) -> bool {
return td::all_of(query_with_parents.parents, [](auto &parent) { return parent->is_ok; });
};
auto to_query_ptr = [&](TaskId task_id) {
return *scheduler.get_task_extra(task_id);
};
auto flush_pending_queries = [&] {
while (true) {
auto o_task_with_parents = scheduler.start_next_task();
if (!o_task_with_parents) {
break;
}
auto task_with_parents = o_task_with_parents.unwrap();
QueryWithParents query_with_parents;
query_with_parents.id = to_query_ptr(task_with_parents.task_id);
query_with_parents.parents = td::transform(task_with_parents.parents, to_query_ptr);
active_queries.push_back(query_with_parents);
}
};
auto execute_one_query = [&] {
if (active_queries.empty()) {
return;
}
auto it = active_queries.begin() + rnd.fast(0, static_cast<int>(active_queries.size()) - 1);
auto query_with_parents = *it;
active_queries.erase(it);
auto query = query_with_parents.id;
if (rnd.fast(0, 20) == 0) {
scheduler.finish_task(query->task_id);
inflight_queries--;
LOG(INFO) << "Fail " << query->id;
} else if (check_parents_ok(query_with_parents)) {
query->is_ok = true;
scheduler.finish_task(query->task_id);
inflight_queries--;
LOG(INFO) << "OK " << query->id;
} else {
scheduler.reset_task(query->task_id);
}
};
td::RandomSteps steps({{schedule_new_query, 100}, {execute_one_query, 100}});
while (!done) {
steps.step(rnd);
flush_pending_queries();
// LOG(INFO) << scheduler;
}
for (auto &chain : chains) {
int prev_ok = -1;
int failed_cnt = 0;
int ok_cnt = 0;
for (auto &q : chain) {
if (q->is_ok) {
CHECK(prev_ok < q->id);
prev_ok = q->id;
ok_cnt++;
} else {
failed_cnt++;
}
}
LOG(INFO) << "Chain ok " << ok_cnt << " failed " << failed_cnt;
}
}

View File

@ -4,6 +4,7 @@
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
#include "td/utils/ByteFlow.h"
#include "td/utils/common.h"
@ -16,8 +17,6 @@
#include "td/utils/tests.h"
#include "td/utils/Time.h"
#include <algorithm>
static void encode_decode(const td::string &s) {
auto r = td::gzencode(s, 2);
ASSERT_TRUE(!r.empty());
@ -128,7 +127,7 @@ TEST(Gzip, encode_decode_flow_big) {
td::clear_thread_locals();
auto start_mem = td::BufferAllocator::get_buffer_mem();
{
auto str = std::string(200000, 'a');
auto str = td::string(200000, 'a');
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
@ -145,7 +144,7 @@ TEST(Gzip, encode_decode_flow_big) {
auto validate = [&](td::Slice chunk) {
CHECK(chunk.size() <= left_size);
left_size -= chunk.size();
ASSERT_TRUE(std::all_of(chunk.begin(), chunk.end(), [](auto c) { return c == 'a'; }));
ASSERT_TRUE(td::all_of(chunk, [](auto c) { return c == 'a'; }));
};
for (size_t i = 0; i < n; i++) {
@ -171,7 +170,7 @@ TEST(Gzip, encode_decode_flow_big) {
}
TEST(Gzip, decode_encode_flow_bomb) {
std::string gzip_bomb_str;
td::string gzip_bomb_str;
size_t N = 200;
{
td::ChainBufferWriter input_writer;
@ -181,7 +180,7 @@ TEST(Gzip, decode_encode_flow_bomb) {
td::ByteFlowSink sink;
source >> gzip_flow >> sink;
std::string s(1 << 16, 'a');
td::string s(1 << 16, 'a');
for (size_t i = 0; i < N; i++) {
input_writer.append(s);
source.wakeup();
@ -224,7 +223,7 @@ TEST(Gzip, decode_encode_flow_bomb) {
auto validate = [&](td::Slice chunk) {
CHECK(chunk.size() <= left_size);
left_size -= chunk.size();
ASSERT_TRUE(std::all_of(chunk.begin(), chunk.end(), [](auto c) { return c == 'a'; }));
ASSERT_TRUE(td::all_of(chunk, [](auto c) { return c == 'a'; }));
};
input_writer.append(gzip_bomb_str);

View File

@ -1230,6 +1230,9 @@ TEST(Misc, is_emoji) {
ASSERT_TRUE(!td::is_emoji("1234567890123456789012345678901234567890123456789012345678901234567890"));
ASSERT_TRUE(td::is_emoji("❤️"));
ASSERT_TRUE(td::is_emoji(""));
ASSERT_TRUE(td::is_emoji(""));
ASSERT_TRUE(td::is_emoji("🎄"));
ASSERT_TRUE(td::is_emoji("🧑‍🎄"));
}
TEST(Misc, serialize) {

View File

@ -36,221 +36,221 @@
#include <memory>
#include <unordered_map>
using namespace td;
template <class ContainerT>
static typename ContainerT::value_type &rand_elem(ContainerT &cont) {
CHECK(0 < cont.size() && cont.size() <= static_cast<size_t>(std::numeric_limits<int>::max()));
return cont[Random::fast(0, static_cast<int>(cont.size()) - 1)];
CHECK(0 < cont.size() && cont.size() <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
return cont[td::Random::fast(0, static_cast<int>(cont.size()) - 1)];
}
TEST(DB, binlog_encryption_bug) {
CSlice binlog_name = "test_binlog";
Binlog::destroy(binlog_name).ignore();
td::CSlice binlog_name = "test_binlog";
td::Binlog::destroy(binlog_name).ignore();
auto cucumber = DbKey::password("cucu'\"mb er");
auto empty = DbKey::empty();
auto cucumber = td::DbKey::password("cucu'\"mb er");
auto empty = td::DbKey::empty();
{
Binlog binlog;
td::Binlog binlog;
binlog
.init(
binlog_name.str(), [&](const BinlogEvent &x) {}, cucumber)
binlog_name.str(), [&](const td::BinlogEvent &x) {}, cucumber)
.ensure();
}
{
Binlog binlog;
td::Binlog binlog;
binlog
.init(
binlog_name.str(), [&](const BinlogEvent &x) {}, cucumber)
binlog_name.str(), [&](const td::BinlogEvent &x) {}, cucumber)
.ensure();
}
}
TEST(DB, binlog_encryption) {
CSlice binlog_name = "test_binlog";
Binlog::destroy(binlog_name).ignore();
td::CSlice binlog_name = "test_binlog";
td::Binlog::destroy(binlog_name).ignore();
auto hello = DbKey::raw_key(std::string(32, 'A'));
auto cucumber = DbKey::password("cucu'\"mb er");
auto empty = DbKey::empty();
auto long_data = string(10000, 'Z');
auto hello = td::DbKey::raw_key(td::string(32, 'A'));
auto cucumber = td::DbKey::password("cucu'\"mb er");
auto empty = td::DbKey::empty();
auto long_data = td::string(10000, 'Z');
{
Binlog binlog;
binlog.init(binlog_name.str(), [](const BinlogEvent &x) {}).ensure();
binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("AAAA")),
BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("BBBB")),
BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer(long_data)),
BinlogDebugInfo{__FILE__, __LINE__});
td::Binlog binlog;
binlog.init(binlog_name.str(), [](const td::BinlogEvent &x) {}).ensure();
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("AAAA")),
td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("BBBB")),
td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer(long_data)),
td::BinlogDebugInfo{__FILE__, __LINE__});
LOG(INFO) << "SET PASSWORD";
binlog.change_key(cucumber);
binlog.change_key(hello);
LOG(INFO) << "OK";
binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("CCCC")),
BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("CCCC")),
td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.close().ensure();
}
return;
auto add_suffix = [&] {
auto fd = FileFd::open(binlog_name, FileFd::Flags::Write | FileFd::Flags::Append).move_as_ok();
auto fd = td::FileFd::open(binlog_name, td::FileFd::Flags::Write | td::FileFd::Flags::Append).move_as_ok();
fd.write("abacabadaba").ensure();
};
add_suffix();
{
std::vector<string> v;
td::vector<td::string> v;
LOG(INFO) << "RESTART";
Binlog binlog;
td::Binlog binlog;
binlog
.init(
binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, hello)
binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, hello)
.ensure();
CHECK(v == std::vector<string>({"AAAA", "BBBB", long_data, "CCCC"}));
CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
add_suffix();
{
std::vector<string> v;
td::vector<td::string> v;
LOG(INFO) << "RESTART";
Binlog binlog;
td::Binlog binlog;
auto status = binlog.init(
binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
CHECK(status.is_error());
}
add_suffix();
{
std::vector<string> v;
td::vector<td::string> v;
LOG(INFO) << "RESTART";
Binlog binlog;
td::Binlog binlog;
auto status = binlog.init(
binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber, hello);
CHECK(v == std::vector<string>({"AAAA", "BBBB", long_data, "CCCC"}));
binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber, hello);
CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
}
TEST(DB, sqlite_lfs) {
string path = "test_sqlite_db";
SqliteDb::destroy(path).ignore();
auto db = SqliteDb::open_with_key(path, true, DbKey::empty()).move_as_ok();
td::string path = "test_sqlite_db";
td::SqliteDb::destroy(path).ignore();
auto db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
db.exec("PRAGMA journal_mode=WAL").ensure();
db.exec("PRAGMA user_version").ensure();
SqliteDb::destroy(path).ignore();
td::SqliteDb::destroy(path).ignore();
}
TEST(DB, sqlite_encryption) {
string path = "test_sqlite_db";
SqliteDb::destroy(path).ignore();
td::string path = "test_sqlite_db";
td::SqliteDb::destroy(path).ignore();
auto empty = DbKey::empty();
auto cucumber = DbKey::password("cucu'\"mb er");
auto tomato = DbKey::raw_key(string(32, 'a'));
auto empty = td::DbKey::empty();
auto cucumber = td::DbKey::password("cucu'\"mb er");
auto tomato = td::DbKey::raw_key(td::string(32, 'a'));
{
auto db = SqliteDb::open_with_key(path, true, empty).move_as_ok();
auto db = td::SqliteDb::open_with_key(path, true, empty).move_as_ok();
db.set_user_version(123).ensure();
auto kv = SqliteKeyValue();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
kv.set("a", "b");
}
SqliteDb::open_with_key(path, false, cucumber).ensure_error();
td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
SqliteDb::change_key(path, false, cucumber, empty).ensure();
SqliteDb::change_key(path, false, cucumber, empty).ensure();
td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
SqliteDb::open_with_key(path, false, tomato).ensure_error();
td::SqliteDb::open_with_key(path, false, tomato).ensure_error();
{
auto db = SqliteDb::open_with_key(path, false, cucumber).move_as_ok();
auto kv = SqliteKeyValue();
auto db = td::SqliteDb::open_with_key(path, false, cucumber).move_as_ok();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
SqliteDb::change_key(path, false, tomato, cucumber).ensure();
SqliteDb::change_key(path, false, tomato, cucumber).ensure();
td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
SqliteDb::open_with_key(path, false, cucumber).ensure_error();
td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
{
auto db = SqliteDb::open_with_key(path, false, tomato).move_as_ok();
auto kv = SqliteKeyValue();
auto db = td::SqliteDb::open_with_key(path, false, tomato).move_as_ok();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
SqliteDb::change_key(path, false, empty, tomato).ensure();
SqliteDb::change_key(path, false, empty, tomato).ensure();
td::SqliteDb::change_key(path, false, empty, tomato).ensure();
td::SqliteDb::change_key(path, false, empty, tomato).ensure();
{
auto db = SqliteDb::open_with_key(path, false, empty).move_as_ok();
auto kv = SqliteKeyValue();
auto db = td::SqliteDb::open_with_key(path, false, empty).move_as_ok();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
SqliteDb::open_with_key(path, false, cucumber).ensure_error();
SqliteDb::destroy(path).ignore();
td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
td::SqliteDb::destroy(path).ignore();
}
TEST(DB, sqlite_encryption_migrate_v3) {
string path = "test_sqlite_db";
SqliteDb::destroy(path).ignore();
auto cucumber = DbKey::password("cucumber");
auto empty = DbKey::empty();
td::string path = "test_sqlite_db";
td::SqliteDb::destroy(path).ignore();
auto cucumber = td::DbKey::password("cucumber");
auto empty = td::DbKey::empty();
if (false) {
// sqlite_sample_db was generated by the following code using SQLCipher based on SQLite 3.15.2
{
auto db = SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
auto db = td::SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
db.set_user_version(123).ensure();
auto kv = SqliteKeyValue();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
kv.set("hello", "world");
}
LOG(ERROR) << base64_encode(read_file(path).move_as_ok());
LOG(ERROR) << td::base64_encode(td::read_file(path).move_as_ok());
}
write_file(path, base64_decode(Slice(sqlite_sample_db_v3, sqlite_sample_db_v3_size)).move_as_ok()).ensure();
td::write_file(path, td::base64_decode(td::Slice(sqlite_sample_db_v3, sqlite_sample_db_v3_size)).move_as_ok())
.ensure();
{
auto db = SqliteDb::open_with_key(path, true, cucumber).move_as_ok();
auto kv = SqliteKeyValue();
auto db = td::SqliteDb::open_with_key(path, true, cucumber).move_as_ok();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("hello") == "world");
CHECK(db.user_version().ok() == 123);
}
SqliteDb::destroy(path).ignore();
td::SqliteDb::destroy(path).ignore();
}
TEST(DB, sqlite_encryption_migrate_v4) {
string path = "test_sqlite_db";
SqliteDb::destroy(path).ignore();
auto cucumber = DbKey::password("cucu'\"mb er");
auto empty = DbKey::empty();
td::string path = "test_sqlite_db";
td::SqliteDb::destroy(path).ignore();
auto cucumber = td::DbKey::password("cucu'\"mb er");
auto empty = td::DbKey::empty();
if (false) {
// sqlite_sample_db was generated by the following code using SQLCipher 4.4.0
{
auto db = SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
auto db = td::SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
db.set_user_version(123).ensure();
auto kv = SqliteKeyValue();
auto kv = td::SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
kv.set("hello", "world");
}
LOG(ERROR) << base64_encode(read_file(path).move_as_ok());
LOG(ERROR) << td::base64_encode(td::read_file(path).move_as_ok());
}
write_file(path, base64_decode(Slice(sqlite_sample_db_v4, sqlite_sample_db_v4_size)).move_as_ok()).ensure();
td::write_file(path, td::base64_decode(td::Slice(sqlite_sample_db_v4, sqlite_sample_db_v4_size)).move_as_ok())
.ensure();
{
auto r_db = SqliteDb::open_with_key(path, true, cucumber);
auto r_db = td::SqliteDb::open_with_key(path, true, cucumber);
if (r_db.is_error()) {
LOG(ERROR) << r_db.error();
return;
}
auto db = r_db.move_as_ok();
auto kv = SqliteKeyValue();
auto kv = td::SqliteKeyValue();
auto status = kv.init_with_connection(db.clone(), "kv");
if (status.is_error()) {
LOG(ERROR) << status;
@ -259,16 +259,16 @@ TEST(DB, sqlite_encryption_migrate_v4) {
CHECK(db.user_version().ok() == 123);
}
}
SqliteDb::destroy(path).ignore();
td::SqliteDb::destroy(path).ignore();
}
using SeqNo = uint64;
using SeqNo = td::uint64;
struct DbQuery {
enum class Type { Get, Set, Erase } type = Type::Get;
SeqNo tid = 0;
int32 id = 0;
string key;
string value;
td::int32 id = 0;
td::string key;
td::string value;
};
template <class ImplT>
@ -323,64 +323,64 @@ class SeqQueryHandler {
class SqliteKV {
public:
string get(const string &key) {
td::string get(const td::string &key) {
return kv_->get().get(key);
}
SeqNo set(const string &key, const string &value) {
SeqNo set(const td::string &key, const td::string &value) {
kv_->get().set(key, value);
return 0;
}
SeqNo erase(const string &key) {
SeqNo erase(const td::string &key) {
kv_->get().erase(key);
return 0;
}
Status init(const string &name) {
auto sql_connection = std::make_shared<SqliteConnectionSafe>(name, DbKey::empty());
kv_ = std::make_shared<SqliteKeyValueSafe>("kv", sql_connection);
return Status::OK();
td::Status init(const td::string &name) {
auto sql_connection = std::make_shared<td::SqliteConnectionSafe>(name, td::DbKey::empty());
kv_ = std::make_shared<td::SqliteKeyValueSafe>("kv", sql_connection);
return td::Status::OK();
}
void close() {
kv_.reset();
}
private:
std::shared_ptr<SqliteKeyValueSafe> kv_;
std::shared_ptr<td::SqliteKeyValueSafe> kv_;
};
class BaselineKV {
public:
string get(const string &key) {
td::string get(const td::string &key) {
return map_[key];
}
SeqNo set(const string &key, string value) {
SeqNo set(const td::string &key, td::string value) {
map_[key] = std::move(value);
return ++current_tid_;
}
SeqNo erase(const string &key) {
SeqNo erase(const td::string &key) {
map_.erase(key);
return ++current_tid_;
}
private:
std::map<string, string> map_;
std::map<td::string, td::string> map_;
SeqNo current_tid_ = 0;
};
TEST(DB, key_value) {
std::vector<std::string> keys;
std::vector<std::string> values;
td::vector<td::string> keys;
td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 10; i++) {
values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
int queries_n = 3000;
std::vector<DbQuery> queries(queries_n);
td::vector<DbQuery> queries(queries_n);
for (auto &q : queries) {
int op = Random::fast(0, 2);
int op = td::Random::fast(0, 2);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op == 0) {
@ -397,18 +397,18 @@ TEST(DB, key_value) {
}
QueryHandler<BaselineKV> baseline;
QueryHandler<SeqKeyValue> kv;
QueryHandler<TsSeqKeyValue> ts_kv;
QueryHandler<BinlogKeyValue<Binlog>> new_kv;
QueryHandler<td::SeqKeyValue> kv;
QueryHandler<td::TsSeqKeyValue> ts_kv;
QueryHandler<td::BinlogKeyValue<td::Binlog>> new_kv;
CSlice new_kv_name = "test_new_kv";
Binlog::destroy(new_kv_name).ignore();
td::CSlice new_kv_name = "test_new_kv";
td::Binlog::destroy(new_kv_name).ignore();
new_kv.impl().init(new_kv_name.str()).ensure();
QueryHandler<SqliteKeyValue> sqlite_kv;
CSlice path = "test_sqlite_kv";
SqliteDb::destroy(path).ignore();
auto db = SqliteDb::open_with_key(path, true, DbKey::empty()).move_as_ok();
QueryHandler<td::SqliteKeyValue> sqlite_kv;
td::CSlice path = "test_sqlite_kv";
td::SqliteDb::destroy(path).ignore();
auto db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
sqlite_kv.impl().init_with_connection(std::move(db), "KV").ensure();
int cnt = 0;
@ -431,33 +431,33 @@ TEST(DB, key_value) {
new_kv.impl().init(new_kv_name.str()).ensure();
}
}
SqliteDb::destroy(path).ignore();
Binlog::destroy(new_kv_name).ignore();
td::SqliteDb::destroy(path).ignore();
td::Binlog::destroy(new_kv_name).ignore();
}
TEST(DB, key_value_set_all) {
std::vector<std::string> keys;
std::vector<std::string> values;
td::vector<td::string> keys;
td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 10; i++) {
values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
SqliteKeyValue sqlite_kv;
CSlice sqlite_kv_name = "test_sqlite_kv";
SqliteDb::destroy(sqlite_kv_name).ignore();
auto db = SqliteDb::open_with_key(sqlite_kv_name, true, DbKey::empty()).move_as_ok();
td::SqliteKeyValue sqlite_kv;
td::CSlice sqlite_kv_name = "test_sqlite_kv";
td::SqliteDb::destroy(sqlite_kv_name).ignore();
auto db = td::SqliteDb::open_with_key(sqlite_kv_name, true, td::DbKey::empty()).move_as_ok();
sqlite_kv.init_with_connection(std::move(db), "KV").ensure();
BaselineKV kv;
int queries_n = 100;
while (queries_n-- > 0) {
int cnt = Random::fast(0, 10);
std::unordered_map<string, string> key_values;
int cnt = td::Random::fast(0, 10);
std::unordered_map<td::string, td::string> key_values;
for (int i = 0; i < cnt; i++) {
auto key = rand_elem(keys);
auto value = rand_elem(values);
@ -471,28 +471,28 @@ TEST(DB, key_value_set_all) {
CHECK(kv.get(key) == sqlite_kv.get(key));
}
}
SqliteDb::destroy(sqlite_kv_name).ignore();
td::SqliteDb::destroy(sqlite_kv_name).ignore();
}
#if !TD_THREAD_UNSUPPORTED
TEST(DB, thread_key_value) {
std::vector<std::string> keys;
std::vector<std::string> values;
td::vector<td::string> keys;
td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
int threads_n = 4;
int queries_n = 10000;
std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
int op = Random::fast(0, 10);
int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op > 1) {
@ -510,10 +510,10 @@ TEST(DB, thread_key_value) {
}
QueryHandler<BaselineKV> baseline;
SeqQueryHandler<TsSeqKeyValue> ts_kv;
SeqQueryHandler<td::TsSeqKeyValue> ts_kv;
std::vector<td::thread> threads(threads_n);
std::vector<std::vector<DbQuery>> res(threads_n);
td::vector<td::thread> threads(threads_n);
td::vector<td::vector<DbQuery>> res(threads_n);
for (int i = 0; i < threads_n; i++) {
threads[i] = td::thread([&ts_kv, &queries, &res, i] {
for (auto q : queries[i]) {
@ -526,7 +526,7 @@ TEST(DB, thread_key_value) {
thread.join();
}
std::vector<std::size_t> pos(threads_n);
td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
@ -580,20 +580,19 @@ TEST(DB, thread_key_value) {
#endif
TEST(DB, persistent_key_value) {
using KeyValue = BinlogKeyValue<ConcurrentBinlog>;
// using KeyValue = PersistentKeyValue;
// using KeyValue = SqliteKV;
std::vector<std::string> keys;
std::vector<std::string> values;
CSlice path = "test_pmc";
Binlog::destroy(path).ignore();
SqliteDb::destroy(path).ignore();
using KeyValue = td::BinlogKeyValue<td::ConcurrentBinlog>;
// using KeyValue = td::SqliteKeyValue;
td::vector<td::string> keys;
td::vector<td::string> values;
td::CSlice path = "test_pmc";
td::Binlog::destroy(path).ignore();
td::SqliteDb::destroy(path).ignore();
for (int i = 0; i < 100; i++) {
keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
QueryHandler<BaselineKV> baseline;
@ -602,10 +601,10 @@ TEST(DB, persistent_key_value) {
int threads_n = 4;
int queries_n = 3000 / threads_n;
std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
int op = Random::fast(0, 10);
int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op > 1) {
@ -622,11 +621,11 @@ TEST(DB, persistent_key_value) {
}
}
std::vector<std::vector<DbQuery>> res(threads_n);
class Worker final : public Actor {
td::vector<td::vector<DbQuery>> res(threads_n);
class Worker final : public td::Actor {
public:
Worker(ActorShared<> parent, std::shared_ptr<SeqQueryHandler<KeyValue>> kv, const std::vector<DbQuery> *queries,
std::vector<DbQuery> *res)
Worker(td::ActorShared<> parent, std::shared_ptr<SeqQueryHandler<KeyValue>> kv,
const td::vector<DbQuery> *queries, td::vector<DbQuery> *res)
: parent_(std::move(parent)), kv_(std::move(kv)), queries_(queries), res_(res) {
}
void loop() final {
@ -638,14 +637,14 @@ TEST(DB, persistent_key_value) {
}
private:
ActorShared<> parent_;
td::ActorShared<> parent_;
std::shared_ptr<SeqQueryHandler<KeyValue>> kv_;
const std::vector<DbQuery> *queries_;
std::vector<DbQuery> *res_;
const td::vector<DbQuery> *queries_;
td::vector<DbQuery> *res_;
};
class Main final : public Actor {
class Main final : public td::Actor {
public:
Main(int threads_n, const std::vector<std::vector<DbQuery>> *queries, std::vector<std::vector<DbQuery>> *res)
Main(int threads_n, const td::vector<td::vector<DbQuery>> *queries, td::vector<td::vector<DbQuery>> *res)
: threads_n_(threads_n), queries_(queries), res_(res), ref_cnt_(threads_n) {
}
@ -653,7 +652,8 @@ TEST(DB, persistent_key_value) {
LOG(INFO) << "Start up";
kv_->impl().init("test_pmc").ensure();
for (int i = 0; i < threads_n_; i++) {
create_actor_on_scheduler<Worker>("Worker", i + 1, actor_shared(this, 2), kv_, &queries_->at(i), &res_->at(i))
td::create_actor_on_scheduler<Worker>("Worker", i + 1, actor_shared(this, 2), kv_, &queries_->at(i),
&res_->at(i))
.release();
}
}
@ -667,7 +667,7 @@ TEST(DB, persistent_key_value) {
ref_cnt_--;
if (ref_cnt_ == 0) {
kv_->impl().close();
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
stop();
}
}
@ -677,14 +677,14 @@ TEST(DB, persistent_key_value) {
private:
int threads_n_;
const std::vector<std::vector<DbQuery>> *queries_;
std::vector<std::vector<DbQuery>> *res_;
const td::vector<td::vector<DbQuery>> *queries_;
td::vector<td::vector<DbQuery>> *res_;
std::shared_ptr<SeqQueryHandler<KeyValue>> kv_{new SeqQueryHandler<KeyValue>()};
int ref_cnt_;
};
ConcurrentScheduler sched;
td::ConcurrentScheduler sched;
sched.init(threads_n);
sched.create_actor_unsafe<Main>(0, "Main", threads_n, &queries, &res).release();
sched.start();
@ -693,7 +693,7 @@ TEST(DB, persistent_key_value) {
}
sched.finish();
std::vector<std::size_t> pos(threads_n);
td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
@ -744,5 +744,5 @@ TEST(DB, persistent_key_value) {
pos[best]++;
}
}
SqliteDb::destroy(path).ignore();
td::SqliteDb::destroy(path).ignore();
}

View File

@ -41,18 +41,15 @@
#include "td/utils/UInt.h"
#include <algorithm>
#include <limits>
#include <condition_variable>
#include <limits>
#include <mutex>
using namespace td;
static string make_chunked(const string &str) {
auto v = rand_split(str);
string res;
static td::string make_chunked(const td::string &str) {
auto v = td::rand_split(str);
td::string res;
for (auto &s : v) {
res += PSTRING() << format::as_hex_dump(static_cast<int32>(s.size()));
res += PSTRING() << td::format::as_hex_dump(static_cast<td::int32>(s.size()));
res += "\r\n";
res += s;
res += "\r\n";
@ -61,33 +58,33 @@ static string make_chunked(const string &str) {
return res;
}
static string gen_http_content() {
int t = Random::fast(0, 2);
static td::string gen_http_content() {
int t = td::Random::fast(0, 2);
int len;
if (t == 0) {
len = Random::fast(1, 10);
len = td::Random::fast(1, 10);
} else if (t == 1) {
len = Random::fast(100, 200);
len = td::Random::fast(100, 200);
} else {
len = Random::fast(1000, 20000);
len = td::Random::fast(1000, 20000);
}
return rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
return td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
}
static string make_http_query(string content, bool is_json, bool is_chunked, bool is_gzip, double gzip_k = 5,
string zip_override = string()) {
HttpHeaderCreator hc;
static td::string make_http_query(td::string content, bool is_json, bool is_chunked, bool is_gzip, double gzip_k = 5,
td::string zip_override = td::string()) {
td::HttpHeaderCreator hc;
hc.init_post("/");
hc.add_header("jfkdlsahhjk", rand_string('a', 'z', Random::fast(1, 2000)));
hc.add_header("jfkdlsahhjk", td::rand_string('a', 'z', td::Random::fast(1, 2000)));
if (is_json) {
hc.add_header("content-type", "application/json");
}
if (is_gzip) {
BufferSlice zip;
td::BufferSlice zip;
if (zip_override.empty()) {
zip = gzencode(content, gzip_k);
zip = td::gzencode(content, gzip_k);
} else {
zip = BufferSlice(zip_override);
zip = td::BufferSlice(zip_override);
}
if (!zip.empty()) {
hc.add_header("content-encoding", "gzip");
@ -100,22 +97,19 @@ static string make_http_query(string content, bool is_json, bool is_chunked, boo
} else {
hc.set_content_size(content.size());
}
string res;
auto r_header = hc.finish();
CHECK(r_header.is_ok());
res += r_header.ok().str();
res += content;
return res;
return PSTRING() << r_header.ok() << content;
}
static string rand_http_query(string content) {
bool is_chunked = Random::fast_bool();
bool is_gzip = Random::fast_bool();
static td::string rand_http_query(td::string content) {
bool is_chunked = td::Random::fast_bool();
bool is_gzip = td::Random::fast_bool();
return make_http_query(std::move(content), false, is_chunked, is_gzip);
}
static string join(const std::vector<string> &v) {
string res;
static td::string join(const td::vector<td::string> &v) {
td::string res;
for (auto &s : v) {
res += s;
}
@ -123,10 +117,10 @@ static string join(const std::vector<string> &v) {
}
TEST(Http, stack_overflow) {
ChainBufferWriter writer;
BufferSlice slice(string(256, 'A'));
td::ChainBufferWriter writer;
td::BufferSlice slice(td::string(256, 'A'));
for (int i = 0; i < 1000000; i++) {
ChainBufferWriter tmp_writer;
td::ChainBufferWriter tmp_writer;
writer.append(slice.clone());
}
{
@ -139,12 +133,12 @@ TEST(Http, reader) {
#if TD_ANDROID || TD_TIZEN
return;
#endif
clear_thread_locals();
auto start_mem = BufferAllocator::get_buffer_mem();
auto start_size = BufferAllocator::get_buffer_slice_size();
td::clear_thread_locals();
auto start_mem = td::BufferAllocator::get_buffer_mem();
auto start_size = td::BufferAllocator::get_buffer_slice_size();
{
BufferSlice a("test test");
BufferSlice b = std::move(a);
td::BufferSlice a("test test");
td::BufferSlice b = std::move(a);
#if TD_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
@ -157,32 +151,32 @@ TEST(Http, reader) {
#pragma clang diagnostic pop
#endif
a = std::move(b);
BufferSlice c = a.from_slice(a);
td::BufferSlice c = a.from_slice(a);
CHECK(c.size() == a.size());
}
clear_thread_locals();
ASSERT_EQ(start_mem, BufferAllocator::get_buffer_mem());
ASSERT_EQ(start_size, BufferAllocator::get_buffer_slice_size());
td::clear_thread_locals();
ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
ASSERT_EQ(start_size, td::BufferAllocator::get_buffer_slice_size());
for (int i = 0; i < 20; i++) {
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
HttpReader reader;
td::HttpReader reader;
int max_post_size = 10000;
reader.init(&input, max_post_size, 0);
std::vector<string> contents(100);
td::vector<td::string> contents(100);
std::generate(contents.begin(), contents.end(), gen_http_content);
auto v = td::transform(contents, rand_http_query);
auto vec_str = rand_split(join(v));
auto vec_str = td::rand_split(join(v));
HttpQuery q;
std::vector<string> res;
td::HttpQuery q;
td::vector<td::string> res;
for (auto &str : vec_str) {
input_writer.append(str);
input.sync_with_writer();
while (true) {
auto r_state = reader.read_next(&q);
LOG_IF(ERROR, r_state.is_error()) << r_state.error() << tag("ok", res.size());
LOG_IF(ERROR, r_state.is_error()) << r_state.error() << td::tag("ok", res.size());
ASSERT_TRUE(r_state.is_ok());
auto state = r_state.ok();
if (state == 0) {
@ -192,11 +186,11 @@ TEST(Http, reader) {
ASSERT_EQ(expected, q.content_.str());
res.push_back(q.content_.str());
} else {
auto r_fd = FileFd::open(q.files_[0].temp_file_name, FileFd::Read);
auto r_fd = td::FileFd::open(q.files_[0].temp_file_name, td::FileFd::Read);
ASSERT_TRUE(r_fd.is_ok());
auto fd = r_fd.move_as_ok();
string content(td::narrow_cast<size_t>(q.files_[0].size), '\0');
auto r_size = fd.read(MutableSlice(content));
td::string content(td::narrow_cast<std::size_t>(q.files_[0].size), '\0');
auto r_size = fd.read(td::MutableSlice(content));
ASSERT_TRUE(r_size.is_ok());
ASSERT_TRUE(r_size.ok() == content.size());
ASSERT_TRUE(td::narrow_cast<int>(content.size()) > max_post_size);
@ -212,24 +206,26 @@ TEST(Http, reader) {
ASSERT_EQ(contents.size(), res.size());
ASSERT_EQ(contents, res);
}
clear_thread_locals();
ASSERT_EQ(start_mem, BufferAllocator::get_buffer_mem());
ASSERT_EQ(start_size, BufferAllocator::get_buffer_slice_size());
td::clear_thread_locals();
ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
ASSERT_EQ(start_size, td::BufferAllocator::get_buffer_slice_size());
}
TEST(Http, gzip_bomb) {
#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test should be disabled on low-memory systems
#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test must be disabled on low-memory systems
return;
#endif
auto gzip_bomb_str =
gzdecode(gzdecode(base64url_decode(Slice(gzip_bomb, gzip_bomb_size)).ok()).as_slice()).as_slice().str();
td::gzdecode(td::gzdecode(td::base64url_decode(td::Slice(gzip_bomb, gzip_bomb_size)).ok()).as_slice())
.as_slice()
.str();
auto query = make_http_query("", false, false, true, 0.01, gzip_bomb_str);
auto parts = rand_split(query);
auto parts = td::rand_split(query);
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
HttpReader reader;
HttpQuery q;
td::HttpReader reader;
td::HttpQuery q;
reader.init(&input, 100000000, 0);
for (auto &part : parts) {
input_writer.append(part);
@ -244,39 +240,39 @@ TEST(Http, gzip_bomb) {
}
TEST(Http, gzip) {
auto gzip_str = gzdecode(base64url_decode(Slice(gzip, gzip_size)).ok()).as_slice().str();
auto gzip_str = td::gzdecode(td::base64url_decode(td::Slice(gzip, gzip_size)).ok()).as_slice().str();
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
HttpReader reader;
td::HttpReader reader;
reader.init(&input, 0, 0);
auto query = make_http_query("", true, false, true, 0.01, gzip_str);
input_writer.append(query);
input.sync_with_writer();
HttpQuery q;
td::HttpQuery q;
auto r_state = reader.read_next(&q);
ASSERT_TRUE(r_state.is_error());
ASSERT_EQ(413, r_state.error().code());
}
TEST(Http, aes_ctr_encode_decode_flow) {
auto str = rand_string('a', 'z', 1000000);
auto parts = rand_split(str);
auto str = td::rand_string('a', 'z', 1000000);
auto parts = td::rand_split(str);
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
ByteFlowSource source(&input);
UInt256 key;
UInt128 iv;
Random::secure_bytes(key.raw, sizeof(key));
Random::secure_bytes(iv.raw, sizeof(iv));
AesCtrByteFlow aes_encode;
td::ByteFlowSource source(&input);
td::UInt256 key;
td::UInt128 iv;
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
AesCtrByteFlow aes_decode;
td::AesCtrByteFlow aes_decode;
aes_decode.init(key, iv);
ByteFlowSink sink;
td::ByteFlowSink sink;
source >> aes_encode >> aes_decode >> sink;
ASSERT_TRUE(!sink.is_ready());
@ -285,7 +281,7 @@ TEST(Http, aes_ctr_encode_decode_flow) {
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
source.close_input(Status::OK());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@ -293,25 +289,25 @@ TEST(Http, aes_ctr_encode_decode_flow) {
}
TEST(Http, aes_file_encryption) {
auto str = rand_string('a', 'z', 1000000);
CSlice name = "test_encryption";
unlink(name).ignore();
UInt256 key;
UInt128 iv;
Random::secure_bytes(key.raw, sizeof(key));
Random::secure_bytes(iv.raw, sizeof(iv));
auto str = td::rand_string('a', 'z', 1000000);
td::CSlice name = "test_encryption";
td::unlink(name).ignore();
td::UInt256 key;
td::UInt128 iv;
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
{
BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Write | FileFd::Create).move_as_ok());
td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Write | td::FileFd::Create).move_as_ok());
auto parts = rand_split(str);
auto parts = td::rand_split(str);
ChainBufferWriter output_writer;
td::ChainBufferWriter output_writer;
auto output_reader = output_writer.extract_reader();
ByteFlowSource source(&output_reader);
AesCtrByteFlow aes_encode;
td::ByteFlowSource source(&output_reader);
td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
ByteFlowSink sink;
td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_output_reader(sink.get_output());
@ -325,18 +321,18 @@ TEST(Http, aes_file_encryption) {
}
{
BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Read).move_as_ok());
td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Read).move_as_ok());
ChainBufferWriter input_writer;
td::ChainBufferWriter input_writer;
auto input_reader = input_writer.extract_reader();
ByteFlowSource source(&input_reader);
AesCtrByteFlow aes_encode;
td::ByteFlowSource source(&input_reader);
td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
ByteFlowSink sink;
td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_input_writer(&input_writer);
fd.get_poll_info().add_flags(PollFlags::Read());
fd.get_poll_info().add_flags(td::PollFlags::Read());
while (can_read_local(fd)) {
fd.flush_read(4096).ensure();
source.wakeup();
@ -344,7 +340,7 @@ TEST(Http, aes_file_encryption) {
fd.close();
source.close_input(Status::OK());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@ -354,20 +350,20 @@ TEST(Http, aes_file_encryption) {
}
TEST(Http, chunked_flow) {
auto str = rand_string('a', 'z', 100);
auto parts = rand_split(make_chunked(str));
auto str = td::rand_string('a', 'z', 100);
auto parts = td::rand_split(make_chunked(str));
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
ByteFlowSource source(&input);
HttpChunkedByteFlow chunked_flow;
ByteFlowSink sink;
td::ByteFlowSource source(&input);
td::HttpChunkedByteFlow chunked_flow;
td::ByteFlowSink sink;
source >> chunked_flow >> sink;
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
source.close_input(Status::OK());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@ -377,16 +373,16 @@ TEST(Http, chunked_flow) {
}
TEST(Http, chunked_flow_error) {
auto str = rand_string('a', 'z', 100000);
auto str = td::rand_string('a', 'z', 100000);
for (int d = 1; d < 100; d += 10) {
auto new_str = make_chunked(str);
new_str.resize(str.size() - d);
auto parts = rand_split(new_str);
auto parts = td::rand_split(new_str);
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
ByteFlowSource source(&input);
HttpChunkedByteFlow chunked_flow;
ByteFlowSink sink;
td::ByteFlowSource source(&input);
td::HttpChunkedByteFlow chunked_flow;
td::ByteFlowSink sink;
source >> chunked_flow >> sink;
for (auto &part : parts) {
@ -394,29 +390,29 @@ TEST(Http, chunked_flow_error) {
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
source.close_input(Status::OK());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
ASSERT_TRUE(!sink.status().is_ok());
}
}
TEST(Http, gzip_chunked_flow) {
auto str = rand_string('a', 'z', 1000000);
auto parts = rand_split(make_chunked(gzencode(str, 2.0).as_slice().str()));
auto str = td::rand_string('a', 'z', 1000000);
auto parts = td::rand_split(make_chunked(td::gzencode(str, 2.0).as_slice().str()));
ChainBufferWriter input_writer;
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
ByteFlowSource source(&input);
HttpChunkedByteFlow chunked_flow;
GzipByteFlow gzip_flow(Gzip::Mode::Decode);
ByteFlowSink sink;
td::ByteFlowSource source(&input);
td::HttpChunkedByteFlow chunked_flow;
td::GzipByteFlow gzip_flow(td::Gzip::Mode::Decode);
td::ByteFlowSink sink;
source >> chunked_flow >> gzip_flow >> sink;
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
source.close_input(Status::OK());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@ -424,21 +420,21 @@ TEST(Http, gzip_chunked_flow) {
}
TEST(Http, gzip_bomb_with_limit) {
std::string gzip_bomb_str;
td::string gzip_bomb_str;
{
ChainBufferWriter input_writer;
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
GzipByteFlow gzip_flow(Gzip::Mode::Encode);
ByteFlowSource source(&input);
ByteFlowSink sink;
td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode);
td::ByteFlowSource source(&input);
td::ByteFlowSink sink;
source >> gzip_flow >> sink;
std::string s(1 << 16, 'a');
td::string s(1 << 16, 'a');
for (int i = 0; i < 1000; i++) {
input_writer.append(s);
source.wakeup();
}
source.close_input(Status::OK());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@ -446,11 +442,11 @@ TEST(Http, gzip_bomb_with_limit) {
}
auto query = make_http_query("", false, false, true, 0.01, gzip_bomb_str);
auto parts = rand_split(query);
auto parts = td::rand_split(query);
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
HttpReader reader;
HttpQuery q;
td::HttpReader reader;
td::HttpQuery q;
reader.init(&input, 1000000);
bool ok = false;
for (auto &part : parts) {
@ -493,12 +489,7 @@ struct Baton {
TEST(Http, Darwin) {
Baton baton;
//LOG(ERROR) << "???";
td::DarwinHttp::get("http://example.com", [&](td::BufferSlice data) {
//LOG(ERROR) << data.as_slice();
baton.post();
});
//LOG(ERROR) << "!!!";
td::DarwinHttp::get("http://example.com", [&](td::BufferSlice data) { baton.post(); });
baton.wait();
}
#endif

View File

@ -109,6 +109,7 @@ TEST(MessageEntities, hashtag) {
check_hashtag(" #" + td::string(255, '1') + "a" + td::string(255, 'b') + "# ", {});
check_hashtag("#a#b #c #d", {"#c", "#d"});
check_hashtag("#test", {"#test"});
check_hashtag("#te·st", {"#te·st"});
check_hashtag(u8"\U0001F604\U0001F604\U0001F604\U0001F604 \U0001F604\U0001F604\U0001F604#" + td::string(200, '1') +
"ООО" + td::string(200, '2'),
{"#" + td::string(200, '1') + "ООО" + td::string(53, '2')});
@ -373,7 +374,7 @@ TEST(MessageEntities, is_email_address) {
"a.a.a.a.a.a+ab",
"a+a.a.a.a.a.ab",
"a.a.a.a.a.a.a",
"a.a.a.a.a.a.abcdefg",
"a.a.a.a.a.a.abcdefghi",
"a.a.a.a.a.a.ab0yz",
"a.a.a.a.a.a.ab9yz",
"a.a.a.a.a.a.ab-yz",
@ -694,6 +695,7 @@ TEST(MessageEntities, url) {
check_url("http://google.com/‖", {"http://google.com/"});
check_url("a@b@c.com", {}, {});
check_url("a@b.com:c@1", {}, {"a@b.com"});
check_url("test@test.software", {}, {"test@test.software"});
}
static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities,

View File

@ -46,22 +46,20 @@
#include "td/utils/tests.h"
#include "td/utils/Time.h"
using namespace td;
TEST(Mtproto, GetHostByNameActor) {
ConcurrentScheduler sched;
td::ConcurrentScheduler sched;
int threads_n = 1;
sched.init(threads_n);
int cnt = 1;
vector<ActorOwn<GetHostByNameActor>> actors;
td::vector<td::ActorOwn<td::GetHostByNameActor>> actors;
{
auto guard = sched.get_main_guard();
auto run = [&](ActorId<GetHostByNameActor> actor_id, string host, bool prefer_ipv6, bool allow_ok,
auto run = [&](td::ActorId<td::GetHostByNameActor> actor_id, td::string host, bool prefer_ipv6, bool allow_ok,
bool allow_error) {
auto promise = PromiseCreator::lambda([&cnt, &actors, num = cnt, host, allow_ok,
allow_error](Result<IPAddress> r_ip_address) {
auto promise = td::PromiseCreator::lambda([&cnt, &actors, num = cnt, host, allow_ok,
allow_error](td::Result<td::IPAddress> r_ip_address) {
if (r_ip_address.is_error() && !allow_error) {
LOG(ERROR) << num << " \"" << host << "\" " << r_ip_address.error();
}
@ -70,41 +68,42 @@ TEST(Mtproto, GetHostByNameActor) {
}
if (--cnt == 0) {
actors.clear();
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
}
});
cnt++;
send_closure_later(actor_id, &GetHostByNameActor::run, host, 443, prefer_ipv6, std::move(promise));
td::send_closure_later(actor_id, &td::GetHostByNameActor::run, host, 443, prefer_ipv6, std::move(promise));
};
std::vector<std::string> hosts = {"127.0.0.2",
"1.1.1.1",
"localhost",
"web.telegram.org",
"web.telegram.org.",
"москва.рф",
"",
"%",
" ",
"a",
"\x80",
"[]",
"127.0.0.1.",
"0x12.0x34.0x56.0x78",
"0x7f.001",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
"[[2001:0db8:85a3:0000:0000:8a2e:0370:7334]]"};
for (const auto &types : {vector<GetHostByNameActor::ResolverType>{GetHostByNameActor::ResolverType::Native},
vector<GetHostByNameActor::ResolverType>{GetHostByNameActor::ResolverType::Google},
vector<GetHostByNameActor::ResolverType>{GetHostByNameActor::ResolverType::Google,
GetHostByNameActor::ResolverType::Google,
GetHostByNameActor::ResolverType::Native}}) {
GetHostByNameActor::Options options;
td::vector<td::string> hosts = {"127.0.0.2",
"1.1.1.1",
"localhost",
"web.telegram.org",
"web.telegram.org.",
"москва.рф",
"",
"%",
" ",
"a",
"\x80",
"[]",
"127.0.0.1.",
"0x12.0x34.0x56.0x78",
"0x7f.001",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
"[[2001:0db8:85a3:0000:0000:8a2e:0370:7334]]"};
for (const auto &types :
{td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Native},
td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Google},
td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Google,
td::GetHostByNameActor::ResolverType::Google,
td::GetHostByNameActor::ResolverType::Native}}) {
td::GetHostByNameActor::Options options;
options.resolver_types = types;
options.scheduler_id = threads_n;
auto actor = create_actor<GetHostByNameActor>("GetHostByNameActor", std::move(options));
auto actor = td::create_actor<td::GetHostByNameActor>("GetHostByNameActor", std::move(options));
auto actor_id = actor.get();
actors.push_back(std::move(actor));
@ -127,20 +126,20 @@ TEST(Mtproto, GetHostByNameActor) {
}
TEST(Time, to_unix_time) {
ASSERT_EQ(0, HttpDate::to_unix_time(1970, 1, 1, 0, 0, 0).move_as_ok());
ASSERT_EQ(60 * 60 + 60 + 1, HttpDate::to_unix_time(1970, 1, 1, 1, 1, 1).move_as_ok());
ASSERT_EQ(24 * 60 * 60, HttpDate::to_unix_time(1970, 1, 2, 0, 0, 0).move_as_ok());
ASSERT_EQ(31 * 24 * 60 * 60, HttpDate::to_unix_time(1970, 2, 1, 0, 0, 0).move_as_ok());
ASSERT_EQ(365 * 24 * 60 * 60, HttpDate::to_unix_time(1971, 1, 1, 0, 0, 0).move_as_ok());
ASSERT_EQ(1562780559, HttpDate::to_unix_time(2019, 7, 10, 17, 42, 39).move_as_ok());
ASSERT_EQ(0, td::HttpDate::to_unix_time(1970, 1, 1, 0, 0, 0).move_as_ok());
ASSERT_EQ(60 * 60 + 60 + 1, td::HttpDate::to_unix_time(1970, 1, 1, 1, 1, 1).move_as_ok());
ASSERT_EQ(24 * 60 * 60, td::HttpDate::to_unix_time(1970, 1, 2, 0, 0, 0).move_as_ok());
ASSERT_EQ(31 * 24 * 60 * 60, td::HttpDate::to_unix_time(1970, 2, 1, 0, 0, 0).move_as_ok());
ASSERT_EQ(365 * 24 * 60 * 60, td::HttpDate::to_unix_time(1971, 1, 1, 0, 0, 0).move_as_ok());
ASSERT_EQ(1562780559, td::HttpDate::to_unix_time(2019, 7, 10, 17, 42, 39).move_as_ok());
}
TEST(Time, parse_http_date) {
ASSERT_EQ(784887151, HttpDate::parse_http_date("Tue, 15 Nov 1994 08:12:31 GMT").move_as_ok());
ASSERT_EQ(784887151, td::HttpDate::parse_http_date("Tue, 15 Nov 1994 08:12:31 GMT").move_as_ok());
}
TEST(Mtproto, config) {
ConcurrentScheduler sched;
td::ConcurrentScheduler sched;
int threads_n = 0;
sched.init(threads_n);
@ -149,35 +148,37 @@ TEST(Mtproto, config) {
auto guard = sched.get_main_guard();
auto run = [&](auto &func, bool is_test) {
auto promise = PromiseCreator::lambda([&, num = cnt](Result<SimpleConfigResult> r_simple_config_result) {
if (r_simple_config_result.is_ok()) {
auto simple_config_result = r_simple_config_result.move_as_ok();
auto date = simple_config_result.r_http_date.is_ok()
? to_string(simple_config_result.r_http_date.ok())
: (PSTRING() << simple_config_result.r_http_date.error());
auto config = simple_config_result.r_config.is_ok() ? to_string(simple_config_result.r_config.ok())
: (PSTRING() << simple_config_result.r_config.error());
LOG(ERROR) << num << " " << date << " " << config;
} else {
LOG(ERROR) << num << " " << r_simple_config_result.error();
}
if (--cnt == 0) {
Scheduler::instance()->finish();
}
});
auto promise =
td::PromiseCreator::lambda([&, num = cnt](td::Result<td::SimpleConfigResult> r_simple_config_result) {
if (r_simple_config_result.is_ok()) {
auto simple_config_result = r_simple_config_result.move_as_ok();
auto date = simple_config_result.r_http_date.is_ok()
? td::to_string(simple_config_result.r_http_date.ok())
: (PSTRING() << simple_config_result.r_http_date.error());
auto config = simple_config_result.r_config.is_ok()
? to_string(simple_config_result.r_config.ok())
: (PSTRING() << simple_config_result.r_config.error());
LOG(ERROR) << num << " " << date << " " << config;
} else {
LOG(ERROR) << num << " " << r_simple_config_result.error();
}
if (--cnt == 0) {
td::Scheduler::instance()->finish();
}
});
cnt++;
func(std::move(promise), nullptr, is_test, -1).release();
};
run(get_simple_config_azure, false);
run(get_simple_config_google_dns, false);
run(get_simple_config_mozilla_dns, false);
run(get_simple_config_azure, true);
run(get_simple_config_google_dns, true);
run(get_simple_config_mozilla_dns, true);
run(get_simple_config_firebase_remote_config, false);
run(get_simple_config_firebase_realtime, false);
run(get_simple_config_firebase_firestore, false);
run(td::get_simple_config_azure, false);
run(td::get_simple_config_google_dns, false);
run(td::get_simple_config_mozilla_dns, false);
run(td::get_simple_config_azure, true);
run(td::get_simple_config_google_dns, true);
run(td::get_simple_config_mozilla_dns, true);
run(td::get_simple_config_firebase_remote_config, false);
run(td::get_simple_config_firebase_realtime, false);
run(td::get_simple_config_firebase_firestore, false);
}
cnt--;
if (cnt != 0) {
@ -190,48 +191,49 @@ TEST(Mtproto, config) {
}
TEST(Mtproto, encrypted_config) {
string data =
td::string data =
" hO//tt \b\n\tiwPVovorKtIYtQ8y2ik7CqfJiJ4pJOCLRa4fBmNPixuRPXnBFF/3mTAAZoSyHq4SNylGHz0Cv1/"
"FnWWdEV+BPJeOTk+ARHcNkuJBt0CqnfcVCoDOpKqGyq0U31s2MOpQvHgAG+Tlpg02syuH0E4dCGRw5CbJPARiynteb9y5fT5x/"
"kmdp6BMR5tWQSQF0liH16zLh8BDSIdiMsikdcwnAvBwdNhRqQBqGx9MTh62MDmlebjtczE9Gz0z5cscUO2yhzGdphgIy6SP+"
"bwaqLWYF0XdPGjKLMUEJW+rou6fbL1t/EUXPtU0XmQAnO0Fh86h+AqDMOe30N4qKrPQ== ";
auto config = decode_config(data).move_as_ok();
auto config = td::decode_config(data).move_as_ok();
}
class TestPingActor final : public Actor {
class TestPingActor final : public td::Actor {
public:
TestPingActor(IPAddress ip_address, Status *result) : ip_address_(ip_address), result_(result) {
TestPingActor(td::IPAddress ip_address, td::Status *result) : ip_address_(ip_address), result_(result) {
}
private:
IPAddress ip_address_;
unique_ptr<mtproto::PingConnection> ping_connection_;
Status *result_;
td::IPAddress ip_address_;
td::unique_ptr<td::mtproto::PingConnection> ping_connection_;
td::Status *result_;
bool is_inited_ = false;
void start_up() final {
auto r_socket = SocketFd::open(ip_address_);
auto r_socket = td::SocketFd::open(ip_address_);
if (r_socket.is_error()) {
LOG(ERROR) << "Failed to open socket: " << r_socket.error();
return stop();
}
ping_connection_ = mtproto::PingConnection::create_req_pq(
mtproto::RawConnection::create(ip_address_, BufferedFd<SocketFd>(r_socket.move_as_ok()),
mtproto::TransportType{mtproto::TransportType::Tcp, 0, mtproto::ProxySecret()},
nullptr),
ping_connection_ = td::mtproto::PingConnection::create_req_pq(
td::mtproto::RawConnection::create(
ip_address_, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr),
3);
Scheduler::subscribe(ping_connection_->get_poll_info().extract_pollable_fd(this));
td::Scheduler::subscribe(ping_connection_->get_poll_info().extract_pollable_fd(this));
is_inited_ = true;
set_timeout_in(10);
yield();
}
void tear_down() final {
if (is_inited_) {
Scheduler::unsubscribe_before_close(ping_connection_->get_poll_info().get_pollable_fd_ref());
td::Scheduler::unsubscribe_before_close(ping_connection_->get_poll_info().get_pollable_fd_ref());
}
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
}
void loop() final {
@ -247,13 +249,13 @@ class TestPingActor final : public Actor {
}
void timeout_expired() final {
*result_ = Status::Error("Timeout expired");
*result_ = td::Status::Error("Timeout expired");
stop();
}
};
static IPAddress get_default_ip_address() {
IPAddress ip_address;
static td::IPAddress get_default_ip_address() {
td::IPAddress ip_address;
#if TD_EMSCRIPTEN
ip_address.init_host_port("venus.web.telegram.org/apiws", 443).ensure();
#else
@ -262,11 +264,11 @@ static IPAddress get_default_ip_address() {
return ip_address;
}
static int32 get_default_dc_id() {
static td::int32 get_default_dc_id() {
return 10002;
}
class Mtproto_ping final : public Test {
class Mtproto_ping final : public td::Test {
public:
using Test::Test;
bool step() final {
@ -290,60 +292,60 @@ class Mtproto_ping final : public Test {
private:
bool is_inited_ = false;
ConcurrentScheduler sched_;
Status result_;
td::ConcurrentScheduler sched_;
td::Status result_;
};
RegisterTest<Mtproto_ping> mtproto_ping("Mtproto_ping");
td::RegisterTest<Mtproto_ping> mtproto_ping("Mtproto_ping");
class HandshakeContext final : public mtproto::AuthKeyHandshakeContext {
class HandshakeContext final : public td::mtproto::AuthKeyHandshakeContext {
public:
mtproto::DhCallback *get_dh_callback() final {
td::mtproto::DhCallback *get_dh_callback() final {
return nullptr;
}
mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
td::mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
return &public_rsa_key;
}
private:
PublicRsaKeyShared public_rsa_key{DcId::empty(), true};
td::PublicRsaKeyShared public_rsa_key{td::DcId::empty(), true};
};
class HandshakeTestActor final : public Actor {
class HandshakeTestActor final : public td::Actor {
public:
HandshakeTestActor(int32 dc_id, Status *result) : dc_id_(dc_id), result_(result) {
HandshakeTestActor(td::int32 dc_id, td::Status *result) : dc_id_(dc_id), result_(result) {
}
private:
int32 dc_id_ = 0;
Status *result_;
td::int32 dc_id_ = 0;
td::Status *result_;
bool wait_for_raw_connection_ = false;
unique_ptr<mtproto::RawConnection> raw_connection_;
td::unique_ptr<td::mtproto::RawConnection> raw_connection_;
bool wait_for_handshake_ = false;
unique_ptr<mtproto::AuthKeyHandshake> handshake_;
Status status_;
td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
td::Status status_;
bool wait_for_result_ = false;
void tear_down() final {
if (raw_connection_) {
raw_connection_->close();
}
finish(Status::Error("Interrupted"));
finish(td::Status::Error("Interrupted"));
}
void loop() final {
if (!wait_for_raw_connection_ && !raw_connection_) {
auto ip_address = get_default_ip_address();
auto r_socket = SocketFd::open(ip_address);
auto r_socket = td::SocketFd::open(ip_address);
if (r_socket.is_error()) {
finish(Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
finish(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
return stop();
}
raw_connection_ = mtproto::RawConnection::create(
ip_address, BufferedFd<SocketFd>(r_socket.move_as_ok()),
mtproto::TransportType{mtproto::TransportType::Tcp, 0, mtproto::ProxySecret()}, nullptr);
raw_connection_ = td::mtproto::RawConnection::create(
ip_address, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr);
}
if (!wait_for_handshake_ && !handshake_) {
handshake_ = make_unique<mtproto::AuthKeyHandshake>(dc_id_, 3600);
handshake_ = td::make_unique<td::mtproto::AuthKeyHandshake>(dc_id_, 3600);
}
if (raw_connection_ && handshake_) {
if (wait_for_result_) {
@ -353,34 +355,37 @@ class HandshakeTestActor final : public Actor {
return stop();
}
if (!handshake_->is_ready_for_finish()) {
finish(Status::Error("Key is not ready.."));
finish(td::Status::Error("Key is not ready.."));
return stop();
}
finish(Status::OK());
finish(td::Status::OK());
return stop();
}
wait_for_result_ = true;
create_actor<mtproto::HandshakeActor>(
"HandshakeActor", std::move(handshake_), std::move(raw_connection_), make_unique<HandshakeContext>(), 10.0,
PromiseCreator::lambda([self = actor_id(this)](Result<unique_ptr<mtproto::RawConnection>> raw_connection) {
send_closure(self, &HandshakeTestActor::got_connection, std::move(raw_connection), 1);
}),
PromiseCreator::lambda([self = actor_id(this)](Result<unique_ptr<mtproto::AuthKeyHandshake>> handshake) {
send_closure(self, &HandshakeTestActor::got_handshake, std::move(handshake), 1);
}))
td::create_actor<td::mtproto::HandshakeActor>(
"HandshakeActor", std::move(handshake_), std::move(raw_connection_), td::make_unique<HandshakeContext>(),
10.0,
td::PromiseCreator::lambda(
[self = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> raw_connection) {
td::send_closure(self, &HandshakeTestActor::got_connection, std::move(raw_connection), 1);
}),
td::PromiseCreator::lambda(
[self = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> handshake) {
td::send_closure(self, &HandshakeTestActor::got_handshake, std::move(handshake), 1);
}))
.release();
wait_for_raw_connection_ = true;
wait_for_handshake_ = true;
}
}
void got_connection(Result<unique_ptr<mtproto::RawConnection>> r_raw_connection, int32 dummy) {
void got_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection, bool dummy) {
CHECK(wait_for_raw_connection_);
wait_for_raw_connection_ = false;
if (r_raw_connection.is_ok()) {
raw_connection_ = r_raw_connection.move_as_ok();
status_ = Status::OK();
status_ = td::Status::OK();
} else {
status_ = r_raw_connection.move_as_error();
}
@ -388,7 +393,7 @@ class HandshakeTestActor final : public Actor {
loop();
}
void got_handshake(Result<unique_ptr<mtproto::AuthKeyHandshake>> r_handshake, int32 dummy) {
void got_handshake(td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> r_handshake, bool dummy) {
CHECK(wait_for_handshake_);
wait_for_handshake_ = false;
CHECK(r_handshake.is_ok());
@ -396,17 +401,17 @@ class HandshakeTestActor final : public Actor {
loop();
}
void finish(Status status) {
void finish(td::Status status) {
if (!result_) {
return;
}
*result_ = std::move(status);
result_ = nullptr;
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
}
};
class Mtproto_handshake final : public Test {
class Mtproto_handshake final : public td::Test {
public:
using Test::Test;
bool step() final {
@ -430,55 +435,56 @@ class Mtproto_handshake final : public Test {
private:
bool is_inited_ = false;
ConcurrentScheduler sched_;
Status result_;
td::ConcurrentScheduler sched_;
td::Status result_;
};
RegisterTest<Mtproto_handshake> mtproto_handshake("Mtproto_handshake");
td::RegisterTest<Mtproto_handshake> mtproto_handshake("Mtproto_handshake");
class Socks5TestActor final : public Actor {
class Socks5TestActor final : public td::Actor {
public:
void start_up() final {
auto promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result<BufferedFd<SocketFd>> res) {
send_closure(actor_id, &Socks5TestActor::on_result, std::move(res), false);
});
auto promise =
td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result<td::BufferedFd<td::SocketFd>> res) {
td::send_closure(actor_id, &Socks5TestActor::on_result, std::move(res), false);
});
class Callback final : public TransparentProxy::Callback {
class Callback final : public td::TransparentProxy::Callback {
public:
explicit Callback(Promise<BufferedFd<SocketFd>> promise) : promise_(std::move(promise)) {
explicit Callback(td::Promise<td::BufferedFd<td::SocketFd>> promise) : promise_(std::move(promise)) {
}
void set_result(Result<BufferedFd<SocketFd>> result) final {
void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
promise_.set_result(std::move(result));
}
void on_connected() final {
}
private:
Promise<BufferedFd<SocketFd>> promise_;
td::Promise<td::BufferedFd<td::SocketFd>> promise_;
};
IPAddress socks5_ip;
td::IPAddress socks5_ip;
socks5_ip.init_ipv4_port("131.191.89.104", 43077).ensure();
IPAddress mtproto_ip_address = get_default_ip_address();
td::IPAddress mtproto_ip_address = get_default_ip_address();
auto r_socket = SocketFd::open(socks5_ip);
auto r_socket = td::SocketFd::open(socks5_ip);
if (r_socket.is_error()) {
return promise.set_error(Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
return promise.set_error(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
}
create_actor<Socks5>("socks5", r_socket.move_as_ok(), mtproto_ip_address, "", "",
make_unique<Callback>(std::move(promise)), actor_shared(this))
td::create_actor<td::Socks5>("socks5", r_socket.move_as_ok(), mtproto_ip_address, "", "",
td::make_unique<Callback>(std::move(promise)), actor_shared(this))
.release();
}
private:
void on_result(Result<BufferedFd<SocketFd>> res, bool dummy) {
void on_result(td::Result<td::BufferedFd<td::SocketFd>> res, bool dummy) {
res.ensure();
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
}
};
TEST(Mtproto, socks5) {
return;
ConcurrentScheduler sched;
td::ConcurrentScheduler sched;
int threads_n = 0;
sched.init(threads_n);
@ -491,7 +497,7 @@ TEST(Mtproto, socks5) {
}
TEST(Mtproto, notifications) {
vector<string> pushes = {
td::vector<td::string> pushes = {
"eyJwIjoiSkRnQ3NMRWxEaWhyVWRRN1pYM3J1WVU4TlRBMFhMb0N6UWRNdzJ1cWlqMkdRbVR1WXVvYXhUeFJHaG1QQm8yVElYZFBzX2N3b2RIb3lY"
"b2drVjM1dVl0UzdWeElNX1FNMDRKMG1mV3ZZWm4zbEtaVlJ0aFVBNGhYUWlaN0pfWDMyZDBLQUlEOWgzRnZwRjNXUFRHQWRaVkdFYzg3bnFPZ3hD"
"NUNMRkM2SU9fZmVqcEpaV2RDRlhBWWpwc1k2aktrbVNRdFZ1MzE5ZW04UFVieXZudFpfdTNud2hjQ0czMk96TGp4S1kyS1lzU21JZm1GMzRmTmw1"
@ -501,64 +507,67 @@ TEST(Mtproto, notifications) {
"TkY1aXJRQlZ4UUFLQlRBekhPTGZIS3BhQXdoaWd5b3NQd0piWnJVV2xRWmh4eEozUFUzZjBNRTEwX0xNT0pFN0xsVUFaY2dabUNaX2V1QmNPZWNK"
"VERxRkpIRGZjN2pBOWNrcFkyNmJRT2dPUEhCeHlEMUVrNVdQcFpLTnlBODVuYzQ1eHFPdERqcU5aVmFLU3pKb2VIcXBQMnJqR29kN2M5YkxsdGd5"
"Q0NGd2NBU3dJeDc3QWNWVXY1UnVZIn0"};
string key =
td::string key =
"uBa5yu01a-nJJeqsR3yeqMs6fJLYXjecYzFcvS6jIwS3nefBIr95LWrTm-IbRBNDLrkISz1Sv0KYpDzhU8WFRk1D0V_"
"qyO7XsbDPyrYxRBpGxofJUINSjb1uCxoSdoh1_F0UXEA2fWWKKVxL0DKUQssZfbVj3AbRglsWpH-jDK1oc6eBydRiS3i4j-"
"H0yJkEMoKRgaF9NaYI4u26oIQ-Ez46kTVU-R7e3acdofOJKm7HIKan_5ZMg82Dvec2M6vc_"
"I54Vs28iBx8IbBO1y5z9WSScgW3JCvFFKP2MXIu7Jow5-cpUx6jXdzwRUb9RDApwAFKi45zpv8eb3uPCDAmIQ";
vector<string> decrypted_payloads = {
td::vector<td::string> decrypted_payloads = {
"eyJsb2Nfa2V5IjoiTUVTU0FHRV9URVhUIiwibG9jX2FyZ3MiOlsiQXJzZW55IFNtaXJub3YiLCJhYmNkZWZnIl0sImN1c3RvbSI6eyJtc2dfaWQi"
"OiI1OTAwNDciLCJmcm9tX2lkIjoiNjI4MTQifSwiYmFkZ2UiOiI0MDkifQ",
"eyJsb2Nfa2V5IjoiIiwibG9jX2FyZ3MiOltdLCJjdXN0b20iOnsiY2hhbm5lbF9pZCI6IjExNzY4OTU0OTciLCJtYXhfaWQiOiIxMzU5In0sImJh"
"ZGdlIjoiMCJ9"};
key = base64url_decode(key).move_as_ok();
key = td::base64url_decode(key).move_as_ok();
for (size_t i = 0; i < pushes.size(); i++) {
auto push = base64url_decode(pushes[i]).move_as_ok();
auto decrypted_payload = base64url_decode(decrypted_payloads[i]).move_as_ok();
auto push = td::base64url_decode(pushes[i]).move_as_ok();
auto decrypted_payload = td::base64url_decode(decrypted_payloads[i]).move_as_ok();
auto key_id = mtproto::DhHandshake::calc_key_id(key);
ASSERT_EQ(key_id, NotificationManager::get_push_receiver_id(push).ok());
ASSERT_EQ(decrypted_payload, NotificationManager::decrypt_push(key_id, key, push).ok());
auto key_id = td::mtproto::DhHandshake::calc_key_id(key);
ASSERT_EQ(key_id, td::NotificationManager::get_push_receiver_id(push).ok());
ASSERT_EQ(decrypted_payload, td::NotificationManager::decrypt_push(key_id, key, push).ok());
}
}
class FastPingTestActor final : public Actor {
class FastPingTestActor final : public td::Actor {
public:
explicit FastPingTestActor(Status *result) : result_(result) {
explicit FastPingTestActor(td::Status *result) : result_(result) {
}
private:
Status *result_;
unique_ptr<mtproto::RawConnection> connection_;
unique_ptr<mtproto::AuthKeyHandshake> handshake_;
ActorOwn<> fast_ping_;
td::Status *result_;
td::unique_ptr<td::mtproto::RawConnection> connection_;
td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
td::ActorOwn<> fast_ping_;
int iteration_{0};
void start_up() final {
// Run handshake to create key and salt
auto ip_address = get_default_ip_address();
auto r_socket = SocketFd::open(ip_address);
auto r_socket = td::SocketFd::open(ip_address);
if (r_socket.is_error()) {
*result_ = Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error());
*result_ = td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error());
return stop();
}
auto raw_connection = mtproto::RawConnection::create(
ip_address, BufferedFd<SocketFd>(r_socket.move_as_ok()),
mtproto::TransportType{mtproto::TransportType::Tcp, 0, mtproto::ProxySecret()}, nullptr);
auto handshake = make_unique<mtproto::AuthKeyHandshake>(get_default_dc_id(), 60 * 100 /*temp*/);
create_actor<mtproto::HandshakeActor>(
"HandshakeActor", std::move(handshake), std::move(raw_connection), make_unique<HandshakeContext>(), 10.0,
PromiseCreator::lambda([self = actor_id(this)](Result<unique_ptr<mtproto::RawConnection>> raw_connection) {
send_closure(self, &FastPingTestActor::got_connection, std::move(raw_connection), 1);
}),
PromiseCreator::lambda([self = actor_id(this)](Result<unique_ptr<mtproto::AuthKeyHandshake>> handshake) {
send_closure(self, &FastPingTestActor::got_handshake, std::move(handshake), 1);
}))
auto raw_connection = td::mtproto::RawConnection::create(
ip_address, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr);
auto handshake = td::make_unique<td::mtproto::AuthKeyHandshake>(get_default_dc_id(), 60 * 100 /*temp*/);
td::create_actor<td::mtproto::HandshakeActor>(
"HandshakeActor", std::move(handshake), std::move(raw_connection), td::make_unique<HandshakeContext>(), 10.0,
td::PromiseCreator::lambda(
[self = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> raw_connection) {
td::send_closure(self, &FastPingTestActor::got_connection, std::move(raw_connection), 1);
}),
td::PromiseCreator::lambda(
[self = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> handshake) {
td::send_closure(self, &FastPingTestActor::got_handshake, std::move(handshake), 1);
}))
.release();
}
void got_connection(Result<unique_ptr<mtproto::RawConnection>> r_raw_connection, int32 dummy) {
void got_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection, bool dummy) {
if (r_raw_connection.is_error()) {
*result_ = r_raw_connection.move_as_error();
LOG(INFO) << "Receive " << *result_ << " instead of a connection";
@ -568,7 +577,7 @@ class FastPingTestActor final : public Actor {
loop();
}
void got_handshake(Result<unique_ptr<mtproto::AuthKeyHandshake>> r_handshake, int32 dummy) {
void got_handshake(td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> r_handshake, bool dummy) {
if (r_handshake.is_error()) {
*result_ = r_handshake.move_as_error();
LOG(INFO) << "Receive " << *result_ << " instead of a handshake";
@ -578,7 +587,7 @@ class FastPingTestActor final : public Actor {
loop();
}
void got_raw_connection(Result<unique_ptr<mtproto::RawConnection>> r_connection) {
void got_raw_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_connection) {
if (r_connection.is_error()) {
*result_ = r_connection.move_as_error();
LOG(INFO) << "Receive " << *result_ << " instead of a handshake";
@ -596,36 +605,37 @@ class FastPingTestActor final : public Actor {
if (iteration_ == 6) {
return stop();
}
unique_ptr<mtproto::AuthData> auth_data;
td::unique_ptr<td::mtproto::AuthData> auth_data;
if (iteration_ % 2 == 0) {
auth_data = make_unique<mtproto::AuthData>();
auth_data = td::make_unique<td::mtproto::AuthData>();
auth_data->set_tmp_auth_key(handshake_->get_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_server_salt(handshake_->get_server_salt(), td::Time::now());
auth_data->set_future_salts({td::mtproto::ServerSalt{0u, 1e20, 1e30}}, td::Time::now());
auth_data->set_use_pfs(true);
uint64 session_id = 0;
td::uint64 session_id = 0;
do {
Random::secure_bytes(reinterpret_cast<uint8 *>(&session_id), sizeof(session_id));
td::Random::secure_bytes(reinterpret_cast<td::uint8 *>(&session_id), sizeof(session_id));
} while (session_id == 0);
auth_data->set_session_id(session_id);
}
iteration_++;
fast_ping_ = create_ping_actor(
td::Slice(), std::move(connection_), std::move(auth_data),
PromiseCreator::lambda([self = actor_id(this)](Result<unique_ptr<mtproto::RawConnection>> r_raw_connection) {
send_closure(self, &FastPingTestActor::got_raw_connection, std::move(r_raw_connection));
}),
ActorShared<>());
td::PromiseCreator::lambda(
[self = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection) {
td::send_closure(self, &FastPingTestActor::got_raw_connection, std::move(r_raw_connection));
}),
td::ActorShared<>());
}
}
void tear_down() final {
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
}
};
class Mtproto_FastPing final : public Test {
class Mtproto_FastPing final : public td::Test {
public:
using Test::Test;
bool step() final {
@ -649,14 +659,14 @@ class Mtproto_FastPing final : public Test {
private:
bool is_inited_ = false;
ConcurrentScheduler sched_;
Status result_;
td::ConcurrentScheduler sched_;
td::Status result_;
};
RegisterTest<Mtproto_FastPing> mtproto_fastping("Mtproto_FastPing");
td::RegisterTest<Mtproto_FastPing> mtproto_fastping("Mtproto_FastPing");
TEST(Mtproto, Grease) {
std::string s(10000, '0');
mtproto::Grease::init(s);
td::string s(10000, '0');
td::mtproto::Grease::init(s);
for (auto c : s) {
CHECK((c & 0xF) == 0xA);
}
@ -666,47 +676,48 @@ TEST(Mtproto, Grease) {
}
TEST(Mtproto, TlsTransport) {
ConcurrentScheduler sched;
td::ConcurrentScheduler sched;
int threads_n = 1;
sched.init(threads_n);
{
auto guard = sched.get_main_guard();
class RunTest final : public Actor {
class RunTest final : public td::Actor {
void start_up() final {
class Callback final : public TransparentProxy::Callback {
class Callback final : public td::TransparentProxy::Callback {
public:
void set_result(Result<BufferedFd<SocketFd>> result) final {
void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
if (result.is_ok()) {
LOG(ERROR) << "Unexpectedly succeeded to connect to MTProto proxy";
} else if (result.error().message() != "Response hash mismatch") {
LOG(ERROR) << "Receive unexpected result " << result.error();
}
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
}
void on_connected() final {
}
};
const std::string domain = "www.google.com";
IPAddress ip_address;
const td::string domain = "www.google.com";
td::IPAddress ip_address;
auto resolve_status = ip_address.init_host_port(domain, 443);
if (resolve_status.is_error()) {
LOG(ERROR) << resolve_status;
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
return;
}
auto r_socket = SocketFd::open(ip_address);
auto r_socket = td::SocketFd::open(ip_address);
if (r_socket.is_error()) {
LOG(ERROR) << "Failed to open socket: " << r_socket.error();
Scheduler::instance()->finish();
td::Scheduler::instance()->finish();
return;
}
create_actor<mtproto::TlsInit>("TlsInit", r_socket.move_as_ok(), domain, "0123456789secret",
make_unique<Callback>(), ActorShared<>(), Clocks::system() - Time::now())
td::create_actor<td::mtproto::TlsInit>("TlsInit", r_socket.move_as_ok(), domain, "0123456789secret",
td::make_unique<Callback>(), td::ActorShared<>(),
td::Clocks::system() - td::Time::now())
.release();
}
};
create_actor<RunTest>("RunTest").release();
td::create_actor<RunTest>("RunTest").release();
}
sched.start();

View File

@ -23,6 +23,7 @@ TEST(Poll, get_vote_percentage) {
check_vote_percentage({999}, 999, {100});
check_vote_percentage({0}, 0, {0});
check_vote_percentage({2, 1}, 3, {67, 33});
check_vote_percentage({4, 1, 1}, 6, {66, 17, 17});
check_vote_percentage({100, 100}, 200, {50, 50});
check_vote_percentage({101, 99}, 200, {50, 50});
check_vote_percentage({102, 98}, 200, {51, 49});
@ -51,7 +52,6 @@ TEST(Poll, get_vote_percentage) {
check_vote_percentage({1234, 2301, 3500, 2841}, 9876,
{12 /* 12.49 */, 23 /* 23.29 */, 35 /* 35.43 */, 29 /* 28.76 */});
check_vote_percentage({200, 200, 200, 270, 270, 60}, 1200, {17, 17, 17, 22, 22, 5});
check_vote_percentage({200, 200, 200, 270, 270, 60}, 1200, {17, 17, 17, 22, 22, 5});
check_vote_percentage({200, 200, 200, 300, 240, 60}, 1200, {16, 16, 16, 25, 20, 5});
check_vote_percentage({200, 200, 200, 250, 250, 20}, 1120, {18, 18, 18, 22, 22, 2});
check_vote_percentage({200, 200, 200, 250, 250, 40}, 1140, {17, 17, 17, 22, 22, 4});

View File

@ -12,37 +12,34 @@
#include "td/utils/SliceBuilder.h"
#include "td/utils/tests.h"
using namespace td;
TEST(SecureStorage, secret) {
using namespace td::secure_storage;
auto secret = Secret::create_new();
std::string key = "cucumber";
auto encrypted_secret = secret.encrypt(key, "", secure_storage::EnryptionAlgorithm::Sha512);
auto secret = td::secure_storage::Secret::create_new();
td::string key = "cucumber";
auto encrypted_secret = secret.encrypt(key, "", td::secure_storage::EnryptionAlgorithm::Sha512);
ASSERT_TRUE(encrypted_secret.as_slice() != secret.as_slice());
auto decrypted_secret = encrypted_secret.decrypt(key, "", secure_storage::EnryptionAlgorithm::Sha512).ok();
auto decrypted_secret = encrypted_secret.decrypt(key, "", td::secure_storage::EnryptionAlgorithm::Sha512).ok();
ASSERT_TRUE(secret.as_slice() == decrypted_secret.as_slice());
ASSERT_TRUE(encrypted_secret.decrypt("notcucumber", "", secure_storage::EnryptionAlgorithm::Sha512).is_error());
ASSERT_TRUE(encrypted_secret.decrypt("notcucumber", "", td::secure_storage::EnryptionAlgorithm::Sha512).is_error());
}
TEST(SecureStorage, simple) {
using namespace td::secure_storage;
BufferSlice value("Small tale about cucumbers");
auto value_secret = Secret::create_new();
td::BufferSlice value("Small tale about cucumbers");
auto value_secret = td::secure_storage::Secret::create_new();
{
BufferSliceDataView value_view(value.copy());
BufferSlice prefix = gen_random_prefix(value_view.size());
BufferSliceDataView prefix_view(std::move(prefix));
ConcatDataView full_value_view(prefix_view, value_view);
auto hash = calc_value_hash(full_value_view).move_as_ok();
td::secure_storage::BufferSliceDataView value_view(value.copy());
td::BufferSlice prefix = td::secure_storage::gen_random_prefix(value_view.size());
td::secure_storage::BufferSliceDataView prefix_view(std::move(prefix));
td::secure_storage::ConcatDataView full_value_view(prefix_view, value_view);
auto hash = td::secure_storage::calc_value_hash(full_value_view).move_as_ok();
Encryptor encryptor(calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()),
full_value_view);
td::secure_storage::Encryptor encryptor(
td::secure_storage::calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()),
full_value_view);
auto encrypted_value = encryptor.pread(0, encryptor.size()).move_as_ok();
Decryptor decryptor(calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()));
td::secure_storage::Decryptor decryptor(
td::secure_storage::calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()));
auto res = decryptor.append(encrypted_value.copy()).move_as_ok();
auto decrypted_hash = decryptor.finish().ok();
ASSERT_TRUE(decrypted_hash.as_slice() == hash.as_slice());
@ -50,23 +47,24 @@ TEST(SecureStorage, simple) {
}
{
auto encrypted_value = encrypt_value(value_secret, value.as_slice()).move_as_ok();
auto encrypted_value = td::secure_storage::encrypt_value(value_secret, value.as_slice()).move_as_ok();
auto decrypted_value =
decrypt_value(value_secret, encrypted_value.hash, encrypted_value.data.as_slice()).move_as_ok();
td::secure_storage::decrypt_value(value_secret, encrypted_value.hash, encrypted_value.data.as_slice())
.move_as_ok();
ASSERT_TRUE(decrypted_value.as_slice() == value.as_slice());
}
{
std::string value_path = "value.txt";
std::string encrypted_path = "encrypted.txt";
std::string decrypted_path = "decrypted.txt";
td::string value_path = "value.txt";
td::string encrypted_path = "encrypted.txt";
td::string decrypted_path = "decrypted.txt";
td::unlink(value_path).ignore();
td::unlink(encrypted_path).ignore();
td::unlink(decrypted_path).ignore();
std::string file_value(100000, 'a');
td::string file_value(100000, 'a');
td::write_file(value_path, file_value).ensure();
auto hash = encrypt_file(value_secret, value_path, encrypted_path).move_as_ok();
decrypt_file(value_secret, hash, encrypted_path, decrypted_path).ensure();
auto hash = td::secure_storage::encrypt_file(value_secret, value_path, encrypted_path).move_as_ok();
td::secure_storage::decrypt_file(value_secret, hash, encrypted_path, decrypted_path).ensure();
ASSERT_TRUE(td::read_file(decrypted_path).move_as_ok().as_slice() == file_value);
}
}

View File

@ -17,8 +17,6 @@
#include <set>
#include <utility>
using namespace td;
template <class T>
class OldSetWithPosition {
public:
@ -33,7 +31,7 @@ class OldSetWithPosition {
if (it == values_.end()) {
return;
}
size_t i = it - values_.begin();
std::size_t i = it - values_.begin();
values_.erase(it);
if (pos_ > i) {
pos_--;
@ -51,25 +49,25 @@ class OldSetWithPosition {
}
void merge(OldSetWithPosition &&other) {
OldSetWithPosition res;
for (size_t i = 0; i < pos_; i++) {
for (std::size_t i = 0; i < pos_; i++) {
res.add(values_[i]);
}
for (size_t i = 0; i < other.pos_; i++) {
for (std::size_t i = 0; i < other.pos_; i++) {
res.add(other.values_[i]);
}
res.pos_ = res.values_.size();
for (size_t i = pos_; i < values_.size(); i++) {
for (std::size_t i = pos_; i < values_.size(); i++) {
res.add(values_[i]);
}
for (size_t i = other.pos_; i < other.values_.size(); i++) {
for (std::size_t i = other.pos_; i < other.values_.size(); i++) {
res.add(other.values_[i]);
}
*this = std::move(res);
}
private:
std::vector<T> values_;
size_t pos_{0};
td::vector<T> values_;
std::size_t pos_{0};
};
template <class T, template <class> class SetWithPosition>
@ -126,25 +124,24 @@ class CheckedSetWithPosition {
}
s_.merge(std::move(other.s_));
}
size_t size() const {
std::size_t size() const {
return checked_.size() + not_checked_.size();
}
private:
std::set<T> checked_;
std::set<T> not_checked_;
SetWithPosition<T> s_;
td::SetWithPosition<T> s_;
};
template <template <class> class RawSet>
static void test_hands() {
using Set = CheckedSetWithPosition<int, RawSet>;
Set a;
CheckedSetWithPosition<int, RawSet> a;
a.add(1);
a.add(2);
a.next();
Set b;
CheckedSetWithPosition<int, RawSet> b;
b.add(1);
b.add(3);
@ -157,12 +154,12 @@ static void test_hands() {
#if !TD_CLANG
template <template <class> class RawSet>
static void test_stress() {
Random::Xorshift128plus rnd(123);
td::Random::Xorshift128plus rnd(123);
using Set = CheckedSetWithPosition<int, RawSet>;
for (int t = 0; t < 10; t++) {
std::vector<unique_ptr<Set>> sets(100);
td::vector<td::unique_ptr<Set>> sets(100);
for (auto &s : sets) {
s = make_unique<Set>();
s = td::make_unique<Set>();
}
int n;
auto merge = [&] {
@ -202,7 +199,7 @@ static void test_stress() {
std::function<void()> func;
td::uint32 weight;
};
std::vector<Step> steps{{merge, 1}, {next, 10}, {add, 10}, {remove, 10}, {reset_position, 5}};
td::vector<Step> steps{{merge, 1}, {next, 10}, {add, 10}, {remove, 10}, {reset_position, 5}};
td::uint32 steps_sum = 0;
for (auto &step : steps) {
steps_sum += step.weight;
@ -228,13 +225,13 @@ static void test_stress() {
template <template <class> class RawSet>
static void test_speed() {
Random::Xorshift128plus rnd(123);
td::Random::Xorshift128plus rnd(123);
using Set = CheckedSetWithPosition<int, RawSet>;
const size_t total_size = 1 << 13;
std::vector<unique_ptr<Set>> sets(total_size);
td::vector<td::unique_ptr<Set>> sets(total_size);
for (size_t i = 0; i < sets.size(); i++) {
sets[i] = make_unique<Set>();
sets[i]->add(narrow_cast<int>(i));
sets[i] = td::make_unique<Set>();
sets[i]->add(td::narrow_cast<int>(i));
}
for (size_t d = 1; d < sets.size(); d *= 2) {
for (size_t i = 0; i < sets.size(); i += 2 * d) {
@ -247,20 +244,20 @@ static void test_speed() {
}
TEST(SetWithPosition, hands) {
test_hands<FastSetWithPosition>();
test_hands<td::FastSetWithPosition>();
test_hands<OldSetWithPosition>();
test_hands<SetWithPosition>();
test_hands<td::SetWithPosition>();
}
#if !TD_CLANG
TEST(SetWithPosition, stress) {
test_stress<FastSetWithPosition>();
test_stress<td::FastSetWithPosition>();
test_stress<OldSetWithPosition>();
test_stress<SetWithPosition>();
test_stress<td::SetWithPosition>();
}
#endif
TEST(SetWithPosition, speed) {
test_speed<FastSetWithPosition>();
test_speed<SetWithPosition>();
test_speed<td::FastSetWithPosition>();
test_speed<td::SetWithPosition>();
}

View File

@ -9,35 +9,34 @@
#include "td/utils/Slice.h"
#include "td/utils/tests.h"
using namespace td;
TEST(StringCleaning, clean_name) {
ASSERT_EQ("@mention", clean_name("@mention", 1000000));
ASSERT_EQ("@mention", clean_name(" @mention ", 1000000));
ASSERT_EQ("@MENTION", clean_name("@MENTION", 1000000));
ASSERT_EQ("ЛШТШФУМ", clean_name("ЛШТШФУМ", 1000000));
ASSERT_EQ("....", clean_name("....", 1000000));
ASSERT_EQ(". ASD ..", clean_name(". ASD ..", 1000000));
ASSERT_EQ(". ASD", clean_name(". ASD ..", 10));
ASSERT_EQ(". ASD", clean_name(".\n\n\nASD\n\n\n..", 10));
ASSERT_EQ("", clean_name("\n\n\n\n\n\n", 1000000));
ASSERT_EQ("", clean_name("\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\n\n\n\n\n\n \n\xC2\xA0 \xC2\xA0 \n", 100000));
ASSERT_EQ("abc", clean_name("\xC2\xA0\xC2\xA0"
"abc\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0",
1000000));
ASSERT_EQ("@mention", td::clean_name("@mention", 1000000));
ASSERT_EQ("@mention", td::clean_name(" @mention ", 1000000));
ASSERT_EQ("@MENTION", td::clean_name("@MENTION", 1000000));
ASSERT_EQ("ЛШТШФУМ", td::clean_name("ЛШТШФУМ", 1000000));
ASSERT_EQ("....", td::clean_name("....", 1000000));
ASSERT_EQ(". ASD ..", td::clean_name(". ASD ..", 1000000));
ASSERT_EQ(". ASD", td::clean_name(". ASD ..", 10));
ASSERT_EQ(". ASD", td::clean_name(".\n\n\nASD\n\n\n..", 10));
ASSERT_EQ("", td::clean_name("\n\n\n\n\n\n", 1000000));
ASSERT_EQ("",
td::clean_name("\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\n\n\n\n\n\n \n\xC2\xA0 \xC2\xA0 \n", 100000));
ASSERT_EQ("abc", td::clean_name("\xC2\xA0\xC2\xA0"
"abc\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0",
1000000));
}
TEST(StringCleaning, clean_username) {
ASSERT_EQ("@mention", clean_username("@mention"));
ASSERT_EQ("@mention", clean_username(" @mention "));
ASSERT_EQ("@mention", clean_username("@MENTION"));
ASSERT_EQ("ЛШТШФУМ", clean_username("ЛШТШФУМ"));
ASSERT_EQ("", clean_username("...."));
ASSERT_EQ("asd", clean_username(". ASD .."));
ASSERT_EQ("@mention", td::clean_username("@mention"));
ASSERT_EQ("@mention", td::clean_username(" @mention "));
ASSERT_EQ("@mention", td::clean_username("@MENTION"));
ASSERT_EQ("ЛШТШФУМ", td::clean_username("ЛШТШФУМ"));
ASSERT_EQ("", td::clean_username("...."));
ASSERT_EQ("asd", td::clean_username(". ASD .."));
}
static void check_clean_input_string(string str, const string &expected, bool expected_result) {
auto result = clean_input_string(str);
static void check_clean_input_string(td::string str, const td::string &expected, bool expected_result) {
auto result = td::clean_input_string(str);
ASSERT_EQ(expected_result, result);
if (result) {
ASSERT_EQ(expected, str);
@ -46,7 +45,7 @@ static void check_clean_input_string(string str, const string &expected, bool ex
TEST(StringCleaning, clean_input_string) {
check_clean_input_string("/abc", "/abc", true);
check_clean_input_string(string(50000, 'a'), string(34996, 'a'), true);
check_clean_input_string(td::string(50000, 'a'), td::string(34996, 'a'), true);
check_clean_input_string("\xff", "", false);
check_clean_input_string("\xc0\x80", "", false);
check_clean_input_string("\xd0", "", false);
@ -57,8 +56,8 @@ TEST(StringCleaning, clean_input_string) {
check_clean_input_string("\xf4\x8f\xbf\xc0", "", false);
check_clean_input_string("\r\r\r\r\r\r\r", "", true);
check_clean_input_string("\r\n\r\n\r\n\r\n\r\n\r\n\r", "\n\n\n\n\n\n", true);
check_clean_input_string(Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
"\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
check_clean_input_string(td::Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"
"\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
.str(),
" \x0a \x21", true);
check_clean_input_string(
@ -73,9 +72,9 @@ TEST(StringCleaning, clean_input_string) {
check_clean_input_string("\xcc\xb3\xcc\xbf\xcc\x8a", "", true);
}
static void check_strip_empty_characters(string str, size_t max_length, const string &expected,
static void check_strip_empty_characters(td::string str, std::size_t max_length, const td::string &expected,
bool strip_rtlo = false) {
ASSERT_EQ(expected, strip_empty_characters(std::move(str), max_length, strip_rtlo));
ASSERT_EQ(expected, td::strip_empty_characters(std::move(str), max_length, strip_rtlo));
}
TEST(StringCleaning, strip_empty_characters) {
@ -83,12 +82,12 @@ TEST(StringCleaning, strip_empty_characters) {
check_strip_empty_characters("/abc", 3, "/ab");
check_strip_empty_characters("/abc", 0, "");
check_strip_empty_characters("/abc", 10000000, "/abc");
string spaces =
td::string spaces =
u8"\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2800\u3000\uFFFC"
u8"\uFFFC";
string spaces_replace = " ";
string rtlo = u8"\u202E";
string empty = "\xE2\x80\x8B\xE2\x80\x8C\xE2\x80\x8D\xE2\x80\x8E\xE2\x80\x8F\xE2\x80\xAE\xC2\xA0\xC2\xA0";
td::string spaces_replace = " ";
td::string rtlo = u8"\u202E";
td::string empty = "\xE2\x80\x8B\xE2\x80\x8C\xE2\x80\x8D\xE2\x80\x8E\xE2\x80\x8F\xE2\x80\xAE\xC2\xA0\xC2\xA0";
check_strip_empty_characters(spaces, 1000000, "");
check_strip_empty_characters(spaces + rtlo, 1000000, "");
@ -101,14 +100,13 @@ TEST(StringCleaning, strip_empty_characters) {
check_strip_empty_characters(spaces + spaces + empty + spaces + spaces + empty + empty, 1000000, "");
check_strip_empty_characters("\r\r\r\r\r\r\r", 1000000, "");
check_strip_empty_characters("\r\n\r\n\r\n\r\n\r\n\r\n\r", 1000000, "");
check_strip_empty_characters(Slice(" \t\r\n\0\va\v\0\n\r\t ").str(), 1000000, "a");
check_strip_empty_characters(
Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
"\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
.str(),
1000000,
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
"\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21");
check_strip_empty_characters(td::Slice(" \t\r\n\0\va\v\0\n\r\t ").str(), 1000000, "a");
check_strip_empty_characters(td::Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12"
"\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
.str(),
1000000,
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
"\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21");
check_strip_empty_characters("\xcc\xb3\xcc\xbf\xcc\x8a", 2, "\xcc\xb3\xcc\xbf");
check_strip_empty_characters(
"\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9\xe2\x80\xaa\xe2\x80\xab\xe2\x80\xac\xe2\x80\xad\xe2\x80\xae", 3,