diff --git a/CMakeLists.txt b/CMakeLists.txt index 9926dfa86..4ccfa2617 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -392,6 +392,7 @@ set(TDLIB_SOURCE td/telegram/PhotoSizeSource.cpp td/telegram/PollManager.cpp td/telegram/QueryCombiner.cpp + td/telegram/RecentDialogList.cpp td/telegram/ReplyMarkup.cpp td/telegram/ReportReason.cpp td/telegram/RestrictionReason.cpp @@ -601,6 +602,7 @@ set(TDLIB_SOURCE td/telegram/PtsManager.h td/telegram/PublicDialogType.h td/telegram/QueryCombiner.h + td/telegram/RecentDialogList.h td/telegram/ReplyMarkup.h td/telegram/ReportReason.h td/telegram/RequestActor.h diff --git a/build.html b/build.html index 909e7de92..e30e1960d 100644 --- a/build.html +++ b/build.html @@ -61,7 +61,7 @@ select.large { font-size: large; } - + @@ -639,7 +639,7 @@ function onOptionsChanged() { break; case 'Debian 8': case 'Debian 9': - case 'Debian 10': + case 'Debian 10+': case 'Ubuntu 14': case 'Ubuntu 16': case 'Ubuntu 18': @@ -668,7 +668,7 @@ function onOptionsChanged() { } if (use_clang) { packages += ' clang' + getClangVersionSuffix() + ' libc++-dev'; - if (linux_distro === 'Debian 10' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20') { + if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20') { packages += ' libc++abi-dev'; } } else { @@ -725,12 +725,12 @@ function onOptionsChanged() { commands.push('cd vcpkg'); commands.push(local + 'bootstrap-vcpkg.bat'); if (target === 'C++/CX') { - commands.push(local + 'vcpkg.exe install gperf openssl-uwp:arm-uwp openssl-uwp:x64-uwp openssl-uwp:x86-uwp zlib:arm-uwp zlib:x64-uwp zlib:x86-uwp'); + commands.push(local + 'vcpkg.exe install gperf:x86-windows openssl-uwp:arm-uwp openssl-uwp:x64-uwp openssl-uwp:x86-uwp zlib:arm-uwp zlib:x64-uwp zlib:x86-uwp'); } else { if (build_64bit) { - commands.push(local + 'vcpkg.exe install gperf openssl:x64-windows zlib:x64-windows'); + commands.push(local + 'vcpkg.exe install gperf:x64-windows openssl:x64-windows zlib:x64-windows'); } else { - commands.push(local + 'vcpkg.exe install gperf openssl:x86-windows zlib:x86-windows'); + commands.push(local + 'vcpkg.exe install gperf:x86-windows openssl:x86-windows zlib:x86-windows'); } } commands.push('cd ..'); diff --git a/example/csharp/README.md b/example/csharp/README.md index d54e08480..b20d65947 100644 --- a/example/csharp/README.md +++ b/example/csharp/README.md @@ -7,13 +7,12 @@ This is an example of building TDLib with `C++/CLI` support and an example of TD * Download and install Microsoft Visual Studio 2015 or later. * Download and install [CMake](https://cmake.org/download/); choose "Add CMake to the system PATH" option while installing. * Install [vcpkg](https://github.com/Microsoft/vcpkg#quick-start) or update it to the latest version using `vcpkg update` and following received instructions. -* Install `zlib` and `openssl` using `vcpkg`: +* Install `gperf`, `zlib`, and `openssl` using `vcpkg`: ``` cd -.\vcpkg.exe install openssl:x64-windows openssl:x86-windows zlib:x64-windows zlib:x86-windows +.\vcpkg.exe install gperf:x64-windows gperf:x86-windows openssl:x64-windows openssl:x86-windows zlib:x64-windows zlib:x86-windows ``` * (Optional. For XML documentation generation.) Download [PHP](https://windows.php.net/download#php-7.2). Add the path to php.exe to the PATH environment variable. -* Download and install [gperf](https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/). Add the path to gperf.exe to the PATH environment variable. * Build `TDLib` with CMake enabling `.NET` support and specifying correct path to `vcpkg` toolchain file: ``` cd /example/csharp diff --git a/example/uwp/README.md b/example/uwp/README.md index 41b681ad7..5ffe37840 100644 --- a/example/uwp/README.md +++ b/example/uwp/README.md @@ -7,13 +7,12 @@ This is an example of building TDLib SDK for Universal Windows Platform and an e * Download and install Microsoft Visual Studio 2015+ with Windows 10 SDK. We recommend to use the latest available versions of Microsoft Visual Studio and Windows 10 SDK. * Download and install [CMake](https://cmake.org/download/). * Install [vcpkg](https://github.com/Microsoft/vcpkg#quick-start) or update it to the latest version using `vcpkg update` and following received instructions. -* Install `zlib` and `openssl` for all UWP architectures using `vcpkg`: +* Install `zlib` and `openssl` for all UWP architectures and `gperf` for x86 using `vcpkg`: ``` cd -.\vcpkg.exe install openssl-uwp:arm-uwp openssl-uwp:x64-uwp openssl-uwp:x86-uwp zlib:arm-uwp zlib:x64-uwp zlib:x86-uwp +.\vcpkg.exe install gperf:x86-windows openssl-uwp:arm-uwp openssl-uwp:x64-uwp openssl-uwp:x86-uwp zlib:arm-uwp zlib:x64-uwp zlib:x86-uwp ``` * (Optional. For XML documentation generation.) Download [PHP](https://windows.php.net/download#php-7.2). Add the path to php.exe to the PATH environment variable. -* Download and install [gperf](https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/). Add the path to gperf.exe to the PATH environment variable. * Download and install [7-Zip](http://www.7-zip.org/download.html) archiver, which is used by the `build.ps1` script to create a Telegram.Td.UWP Visual Studio Extension. Add the path to 7z.exe to the PATH environment variable. Alternatively `build.ps1` supports compressing using [WinRAR](https://en.wikipedia.org/wiki/WinRAR) with option `-compress winrar` and compressing using [zip](http://gnuwin32.sourceforge.net/packages/zip.htm) with `-compress zip`. * Build `TDLib` using provided `build.ps1` script (TDLib should be built 6 times for multiple platforms in Debug and Release configurations, so it make take few hours). Pass path to vcpkg.exe as `-vcpkg-root` argument, for example: @@ -23,7 +22,7 @@ powershell -ExecutionPolicy ByPass .\build.ps1 -vcpkg_root C:\vcpkg If you need to restart the build from scratch, call `.\build.ps1 -mode clean` first. * Install Visual Studio Extension "TDLib for Universal Windows Platform" located at `build-uwp\vsix\tdlib.vsix`, which was created on the previous step by `build.ps1` script. -Now `TDLib` can be freely used from any UWP project, built in Visual Studio. +Now `TDLib` can be used from any UWP project, built in Visual Studio. ## Example of usage The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or later and run. diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 91ab2077d..cd166e5a6 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -950,7 +950,7 @@ voiceChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@unread_mention_count Number of unread messages with a mention/reply in the chat //@notification_settings Notification settings for this chat //@message_ttl_setting 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 the theme set for the chat +//@theme_name If non-empty, name of a theme set for the chat //@action_bar Describes actions which should be possible to do through a chat action bar; may be null //@voice_chat Contains information about voice chat of the chat //@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 @@ -1790,7 +1790,7 @@ messagePinMessage message_id:int53 = MessageContent; //@description A screenshot of a message in the chat has been taken messageScreenshotTaken = MessageContent; -//@description A theme in the chat has been changed @theme_name If non-empty, name of the new theme set for the chat. Otherwise chat theme was reset to the default one +//@description A theme in the chat has been changed @theme_name If non-empty, name of a new theme set for the chat. Otherwise chat theme was reset to the default one messageChatSetTheme theme_name:string = MessageContent; //@description The TTL (Time To Live) setting for messages in the chat has been changed @ttl New message TTL setting @@ -2532,7 +2532,7 @@ chatEventUsernameChanged old_username:string new_username:string = ChatEventActi //@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 chat theme was changed @old_theme_name Previous chat theme name; empty if default one @new_theme_name New chat theme name; empty if default one +//@description The chat theme was changed. This event shouldn't be received until chat themes would be supported in supergroups @old_theme_name Previous chat theme name; empty if the previous theme was default one @new_theme_name New chat theme name; empty if the new theme is default one chatEventThemeChanged old_theme_name:string new_theme_name:string = ChatEventAction; //@description The can_invite_users permission of a supergroup chat was toggled @can_invite_users New value of can_invite_users permission @@ -2702,9 +2702,10 @@ backgroundTypeWallpaper is_blurred:Bool is_moving:Bool = BackgroundType; //@description A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user //@fill Description of the background fill -//@intensity Intensity of the pattern when it is shown above the filled background; -100-100. If negative, the pattern color and the filled background colors needs to be inverted +//@intensity Intensity of the pattern when it is shown above the filled background; 0-100. +//@is_inverted True, if the background fill must be applied only to the pattern itself. All other pixels are black in this case. For dark themes only //@is_moving True, if the background needs to be slightly moved when device is tilted -backgroundTypePattern fill:BackgroundFill intensity:int32 is_moving:Bool = BackgroundType; +backgroundTypePattern fill:BackgroundFill intensity:int32 is_inverted:Bool is_moving:Bool = BackgroundType; //@description A filled background @fill Description of the background fill backgroundTypeFill fill:BackgroundFill = BackgroundType; @@ -2747,9 +2748,6 @@ themeSettings accent_color:int32 background:background message_fill:BackgroundFi //@dark_settings Theme settings for a dark chat theme chatTheme name:string light_settings:themeSettings dark_settings:themeSettings = ChatTheme; -//@description Contains a list of chat themes @chat_themes A list of chat themes -chatThemes chat_themes:vector = ChatThemes; - //@description Contains a list of hashtags @hashtags A list of hashtags hashtags hashtags:vector = Hashtags; @@ -2893,7 +2891,7 @@ pushMessageContentChatChangePhoto = PushMessageContent; //@description A chat title was edited @title New chat title pushMessageContentChatChangeTitle title:string = PushMessageContent; -//@description A chat theme was edited @theme_name If non-empty, name of the new theme set for the chat. Otherwise chat theme was reset to the default one +//@description A chat theme was edited @theme_name If non-empty, name of a new theme set for the chat. Otherwise chat theme was reset to the default one pushMessageContentChatChangeTheme theme_name:string = PushMessageContent; //@description A chat member was deleted @member_name Name of the deleted member @is_current_user True, if the current user was deleted from the group @@ -3700,7 +3698,7 @@ updateChatMessageTtlSetting chat_id:int53 message_ttl_setting:int32 = 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 theme was changed @chat_id Chat identifier @theme_name The new name of the chat theme; may be empty if none +//@description The chat theme was changed @chat_id Chat identifier @theme_name The new name of the chat theme; may be empty if theme was reset to default updateChatTheme chat_id:int53 theme_name:string = Update; //@description The default chat reply markup was changed. Can occur because new messages with reply markup were received or because an old reply markup was hidden by the user @@ -3841,6 +3839,9 @@ updateSavedAnimations animation_ids:vector = Update; //@description The selected background has changed @for_dark_theme True, if background for dark theme has changed @background The new selected background; may be null updateSelectedBackground for_dark_theme:Bool background:background = Update; +//@description The list of available chat themes has changed @chat_themes The new list of chat themes +updateChatThemes chat_themes:vector = Update; + //@description Some language pack strings have been updated @localization_target Localization target to which the language pack belongs @language_pack_id Identifier of the updated language pack @strings List of changed language pack strings updateLanguagePackStrings localization_target:string language_pack_id:string strings:vector = Update; @@ -4156,6 +4157,9 @@ removeRecentlyFoundChat chat_id:int53 = Ok; //@description Clears the list of recently found chats clearRecentlyFoundChats = Ok; +//@description Returns recently opened chats, this is an offline request. Returns chats in the order of last opening @limit The maximum number of chats to be returned +getRecentlyOpenedChats limit:int32 = Chats; + //@description Checks whether a username can be set for a chat @chat_id Chat identifier; should be identifier of a supergroup chat, or a channel chat, or a private chat with self, or zero if the chat is being created @username Username to be checked checkChatUsername chat_id:int53 username:string = CheckChatUsernameResult; @@ -4624,8 +4628,7 @@ setChatMessageTtlSetting chat_id:int53 ttl:int32 = Ok; //@chat_id Chat identifier @permissions New non-administrator members permissions in the chat setChatPermissions chat_id:int53 permissions:chatPermissions = Ok; -//@description Changes the chat theme. Supported only in private and secret chats @chat_id Chat identifier -//@theme_name Name of the new chat theme; may be empty to return the default theme +//@description Changes the chat theme. Supported only in private and secret chats @chat_id Chat identifier @theme_name Name of the new chat theme; may be empty to return the default theme setChatTheme chat_id:int53 theme_name:string = Ok; //@description Changes the draft message in a chat @chat_id Chat identifier @message_thread_id If not 0, a message thread identifier in which the draft was changed @draft_message New draft message; may be null @@ -5290,10 +5293,6 @@ removeBackground background_id:int64 = Ok; resetBackgrounds = Ok; -//@description Returns the list of available chat themes -getChatThemes = ChatThemes; - - //@description Returns information about the current localization target. This is an offline request if only_local is true. Can be called before authorization @only_local If true, returns only locally available information without sending network requests getLocalizationTargetInfo only_local:Bool = LocalizationTargetInfo; diff --git a/td/mtproto/SessionConnection.cpp b/td/mtproto/SessionConnection.cpp index ad044ac05..2c4b48687 100644 --- a/td/mtproto/SessionConnection.cpp +++ b/td/mtproto/SessionConnection.cpp @@ -25,12 +25,14 @@ #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" #include "td/utils/tl_parsers.h" +#include "td/utils/TlDowncastHelper.h" #include "td/mtproto/mtproto_api.h" #include "td/mtproto/mtproto_api.hpp" #include #include +#include namespace td { @@ -171,22 +173,6 @@ namespace mtproto { * */ -class OnPacket { - const MsgInfo &info_; - SessionConnection *connection_; - Status *status_; - - public: - OnPacket(const MsgInfo &info, SessionConnection *connection, Status *status) - : info_(info), connection_(connection), status_(status) { - } - - template - void operator()(const T &func) const { - *status_ = connection_->on_packet(info_, func); - } -}; - unique_ptr SessionConnection::move_as_raw_connection() { return std::move(raw_connection_); } @@ -230,7 +216,6 @@ Status SessionConnection::on_packet_container(const MsgInfo &info, Slice packet) }; TlParser parser(packet); - parser.fetch_int(); int32 size = parser.fetch_int(); if (parser.get_error()) { return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_container: " << parser.get_error()); @@ -243,7 +228,6 @@ Status SessionConnection::on_packet_container(const MsgInfo &info, Slice packet) Status SessionConnection::on_packet_rpc_result(const MsgInfo &info, Slice packet) { TlParser parser(packet); - parser.fetch_int(); uint64 req_msg_id = parser.fetch_long(); if (parser.get_error()) { return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_result: " << parser.get_error()); @@ -275,7 +259,7 @@ Status SessionConnection::on_packet_rpc_result(const MsgInfo &info, Slice packet return callback_->on_message_result_ok(req_msg_id, std::move(object), info.size); } default: - packet.remove_prefix(4 + sizeof(req_msg_id)); + packet.remove_prefix(sizeof(req_msg_id)); return callback_->on_message_result_ok(req_msg_id, as_buffer_slice(packet), info.size); } } @@ -285,12 +269,15 @@ Status SessionConnection::on_packet(const MsgInfo &info, const T &packet) { LOG(ERROR) << "Unsupported: " << to_string(packet); return Status::OK(); } + Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_ok &destroy_auth_key) { return on_destroy_auth_key(destroy_auth_key); } + Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_none &destroy_auth_key) { return on_destroy_auth_key(destroy_auth_key); } + Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_fail &destroy_auth_key) { return on_destroy_auth_key(destroy_auth_key); } @@ -479,52 +466,66 @@ Status SessionConnection::on_slice_packet(const MsgInfo &info, Slice packet) { if (info.seq_no & 1) { send_ack(info.message_id); } - TlParser parser(packet); - tl_object_ptr object = mtproto_api::Object::fetch(parser); - parser.fetch_end(); - if (parser.get_error()) { - // msg_container is not real tl object - if (packet.size() >= 4 && as(packet.begin()) == mtproto_api::msg_container::ID) { - return on_packet_container(info, packet); - } - if (packet.size() >= 4 && as(packet.begin()) == mtproto_api::rpc_result::ID) { - return on_packet_rpc_result(info, packet); - } - - // It is an update... I hope. - auto status = auth_data_->check_update(info.message_id); - auto recheck_status = auth_data_->recheck_update(info.message_id); - if (recheck_status.is_error() && recheck_status.code() == 2) { - LOG(WARNING) << "Receive very old update from " << get_name() << " created in " << (Time::now() - created_at_) - << " in container " << container_id_ << " from session " << auth_data_->get_session_id() - << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ - << ", seq_no = " << info.seq_no << " and original size " << info.size << ": " << status << ' ' - << recheck_status; - } - if (status.is_error()) { - if (status.code() == 2) { - LOG(WARNING) << "Receive too old update from " << get_name() << " created in " << (Time::now() - created_at_) - << " in container " << container_id_ << " from session " << auth_data_->get_session_id() - << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ - << ", seq_no = " << info.seq_no << " and original size " << info.size << ": " << status; - callback_->on_session_failed(Status::Error("Receive too old update")); - return status; - } - VLOG(mtproto) << "Skip update " << info.message_id << " of size " << info.size << " with seq_no " << info.seq_no - << " from " << get_name() << " created in " << (Time::now() - created_at_) << ": " << status; - return Status::OK(); - } else { - VLOG(mtproto) << "Got update from " << get_name() << " created in " << (Time::now() - created_at_) - << " in container " << container_id_ << " from session " << auth_data_->get_session_id() - << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ - << ", seq_no = " << info.seq_no << " and original size " << info.size; - return callback_->on_update(as_buffer_slice(packet)); - } + if (packet.size() < 4) { + callback_->on_session_failed(Status::Error("Receive too small packet")); + return Status::Error(PSLICE() << "Receive packet of size " << packet.size()); } + int32 constructor_id = as(packet.begin()); + if (constructor_id == mtproto_api::msg_container::ID) { + return on_packet_container(info, packet.substr(4)); + } + if (constructor_id == mtproto_api::rpc_result::ID) { + return on_packet_rpc_result(info, packet.substr(4)); + } + + TlDowncastHelper helper(constructor_id); Status status; - downcast_call(*object, OnPacket(info, this, &status)); - return status; + bool is_mtproto_api = downcast_call(static_cast(helper), [&](auto &dummy) { + // a constructor from mtproto_api + using Type = std::decay_t; + TlParser parser(packet.substr(4)); + auto object = Type::fetch(parser); + parser.fetch_end(); + if (parser.get_error()) { + status = parser.get_status(); + } else { + status = this->on_packet(info, static_cast(*object)); + } + }); + if (is_mtproto_api) { + return status; + } + + // It is an update... I hope. + status = auth_data_->check_update(info.message_id); + auto recheck_status = auth_data_->recheck_update(info.message_id); + if (recheck_status.is_error() && recheck_status.code() == 2) { + LOG(WARNING) << "Receive very old update from " << get_name() << " created in " << (Time::now() - created_at_) + << " in container " << container_id_ << " from session " << auth_data_->get_session_id() + << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ + << ", seq_no = " << info.seq_no << " and original size " << info.size << ": " << status << ' ' + << recheck_status; + } + if (status.is_error()) { + if (status.code() == 2) { + LOG(WARNING) << "Receive too old update from " << get_name() << " created in " << (Time::now() - created_at_) + << " in container " << container_id_ << " from session " << auth_data_->get_session_id() + << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ + << ", seq_no = " << info.seq_no << " and original size " << info.size << ": " << status; + callback_->on_session_failed(Status::Error("Receive too old update")); + return status; + } + VLOG(mtproto) << "Skip update " << info.message_id << " of size " << info.size << " with seq_no " << info.seq_no + << " from " << get_name() << " created in " << (Time::now() - created_at_) << ": " << status; + return Status::OK(); + } else { + VLOG(mtproto) << "Got update from " << get_name() << " created in " << (Time::now() - created_at_) + << " in container " << container_id_ << " from session " << auth_data_->get_session_id() + << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ + << ", seq_no = " << info.seq_no << " and original size " << info.size; + return callback_->on_update(as_buffer_slice(packet)); + } } Status SessionConnection::parse_packet(TlParser &parser) { @@ -579,6 +580,7 @@ void SessionConnection::on_message_failed(uint64 id, Status status) { on_message_failed_inner(id); } } + void SessionConnection::on_message_failed_inner(uint64 id) { auto it = service_queries_.find(id); if (it == service_queries_.end()) { diff --git a/td/mtproto/SessionConnection.h b/td/mtproto/SessionConnection.h index 9ef287ca9..3da23a4fc 100644 --- a/td/mtproto/SessionConnection.h +++ b/td/mtproto/SessionConnection.h @@ -203,8 +203,6 @@ class SessionConnection final SessionConnection::Callback *callback_ = nullptr; BufferSlice *current_buffer_slice_; - friend class OnPacket; - BufferSlice as_buffer_slice(Slice packet); auto set_buffer_slice(BufferSlice *buffer_slice) TD_WARN_UNUSED_RESULT { auto old_buffer_slice = current_buffer_slice_; diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index cc7fd58ab..978235dfc 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -26,6 +26,7 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/ThemeManager.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/UpdatesManager.h" @@ -782,6 +783,7 @@ void AuthManager::on_get_authorization(tl_object_ptrmessages_manager_->on_authorization_success(); td->notification_manager_->init(); td->stickers_manager_->init(); + td->theme_manager_->init(); send_closure(td->top_dialog_manager_, &TopDialogManager::do_start_up); td->updates_manager_->get_difference("on_get_authorization"); td->on_online_updated(false, true); diff --git a/td/telegram/BackgroundManager.cpp b/td/telegram/BackgroundManager.cpp index 7e40a49f5..c9e694dcc 100644 --- a/td/telegram/BackgroundManager.cpp +++ b/td/telegram/BackgroundManager.cpp @@ -1060,7 +1060,9 @@ string BackgroundManager::get_background_name_database_key(const string &name) { std::pair BackgroundManager::on_get_background( BackgroundId expected_background_id, const string &expected_background_name, telegram_api::object_ptr wallpaper_ptr, bool replace_type) { - if (!(wallpaper_ptr != nullptr)) return {}; + if (wallpaper_ptr == nullptr) { + return {}; + } if (wallpaper_ptr->get_id() == telegram_api::wallPaperNoFile::ID) { auto wallpaper = move_tl_object_as(wallpaper_ptr); @@ -1071,10 +1073,13 @@ std::pair BackgroundManager::on_get_background( } auto background_id = BackgroundId(wallpaper->id_); - if (!background_id.is_valid() || background_id.is_local()) { + if (background_id.is_local()) { LOG(ERROR) << "Receive " << to_string(wallpaper); return {}; } + if (!background_id.is_valid()) { + background_id = get_next_local_background_id(); + } Background background; background.id = background_id; diff --git a/td/telegram/BackgroundType.cpp b/td/telegram/BackgroundType.cpp index de2aa77a3..ef0b8f8d5 100644 --- a/td/telegram/BackgroundType.cpp +++ b/td/telegram/BackgroundType.cpp @@ -26,10 +26,23 @@ static bool is_valid_color(int32 color) { return 0 <= color && color <= 0xFFFFFF; } +static bool validate_alpha_color(int32 &color) { + if (-0x1000000 <= color && color <= 0xFFFFFF) { + color &= 0xFFFFFF; + return true; + } + color = 0; + return false; +} + static bool is_valid_rotation_angle(int32 rotation_angle) { return 0 <= rotation_angle && rotation_angle < 360 && rotation_angle % 45 == 0; } +static bool is_valid_intensity(int32 intensity, bool allow_negative) { + return (allow_negative ? -100 : 0) <= intensity && intensity <= 100; +} + BackgroundFill::BackgroundFill(const telegram_api::wallPaperSettings *settings) { if (settings == nullptr) { return; @@ -38,35 +51,30 @@ BackgroundFill::BackgroundFill(const telegram_api::wallPaperSettings *settings) auto flags = settings->flags_; if ((flags & telegram_api::wallPaperSettings::BACKGROUND_COLOR_MASK) != 0) { top_color_ = settings->background_color_; - if (!is_valid_color(top_color_)) { + if (!validate_alpha_color(top_color_)) { LOG(ERROR) << "Receive " << to_string(*settings); - top_color_ = 0; } } if ((flags & telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK) != 0 || (flags & telegram_api::wallPaperSettings::THIRD_BACKGROUND_COLOR_MASK) != 0) { bottom_color_ = settings->second_background_color_; - if (!is_valid_color(bottom_color_)) { + if (!validate_alpha_color(bottom_color_)) { LOG(ERROR) << "Receive " << to_string(*settings); - bottom_color_ = 0; } third_color_ = settings->third_background_color_; - if (!is_valid_color(third_color_)) { + if (!validate_alpha_color(third_color_)) { LOG(ERROR) << "Receive " << to_string(*settings); - third_color_ = 0; } if ((flags & telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK) != 0) { fourth_color_ = settings->fourth_background_color_; - if (!is_valid_color(fourth_color_)) { + if (!validate_alpha_color(fourth_color_)) { LOG(ERROR) << "Receive " << to_string(*settings); - fourth_color_ = 0; } } } else if ((flags & telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK) != 0) { bottom_color_ = settings->second_background_color_; - if (!is_valid_color(bottom_color_)) { + if (!validate_alpha_color(bottom_color_)) { LOG(ERROR) << "Receive " << to_string(*settings); - bottom_color_ = 0; } rotation_angle_ = settings->rotation_; @@ -203,10 +211,6 @@ string BackgroundFill::get_link(bool is_first) const { } } -static bool is_valid_intensity(int32 intensity) { - return -100 <= intensity && intensity <= 100; -} - bool BackgroundFill::is_dark() const { switch (get_type()) { case Type::Solid: @@ -254,7 +258,7 @@ void BackgroundType::apply_parameters_from_link(Slice name) { if (!intensity_arg.empty()) { intensity_ = to_integer(intensity_arg); } - if (!is_valid_intensity(intensity_)) { + if (!is_valid_intensity(intensity_, true)) { intensity_ = 50; } @@ -341,10 +345,11 @@ Result BackgroundType::get_background_type(const td_api::Backgro case td_api::backgroundTypePattern::ID: { auto pattern_type = static_cast(background_type); TRY_RESULT(background_fill, BackgroundFill::get_background_fill(pattern_type->fill_.get())); - if (!is_valid_intensity(pattern_type->intensity_)) { + if (!is_valid_intensity(pattern_type->intensity_, false)) { return Status::Error(400, "Wrong intensity value"); } - return BackgroundType(pattern_type->is_moving_, std::move(background_fill), pattern_type->intensity_); + auto intensity = pattern_type->is_inverted_ ? -max(pattern_type->intensity_, 1) : pattern_type->intensity_; + return BackgroundType(pattern_type->is_moving_, std::move(background_fill), intensity); } case td_api::backgroundTypeFill::ID: { auto fill_type = static_cast(background_type); @@ -375,7 +380,7 @@ BackgroundType::BackgroundType(bool is_fill, bool is_pattern, is_moving_ = (settings->flags_ & telegram_api::wallPaperSettings::MOTION_MASK) != 0; if ((settings->flags_ & telegram_api::wallPaperSettings::INTENSITY_MASK) != 0) { intensity_ = settings->intensity_; - if (!is_valid_intensity(intensity_)) { + if (!is_valid_intensity(intensity_, true)) { LOG(ERROR) << "Receive " << to_string(settings); intensity_ = 50; } @@ -414,8 +419,8 @@ td_api::object_ptr BackgroundType::get_background_type_o case Type::Wallpaper: return td_api::make_object(is_blurred_, is_moving_); case Type::Pattern: - return td_api::make_object(fill_.get_background_fill_object(), intensity_, - is_moving_); + return td_api::make_object( + fill_.get_background_fill_object(), intensity_ < 0 ? -intensity_ : intensity_, intensity_ < 0, is_moving_); case Type::Fill: return td_api::make_object(fill_.get_background_fill_object()); default: diff --git a/td/telegram/Document.hpp b/td/telegram/Document.hpp index ffd560598..d3c5ed4dd 100644 --- a/td/telegram/Document.hpp +++ b/td/telegram/Document.hpp @@ -49,7 +49,7 @@ void store(const Document &document, StorerT &storer) { td->documents_manager_->store_document(document.file_id, storer); break; case Document::Type::Sticker: - td->stickers_manager_->store_sticker(document.file_id, false, storer); + td->stickers_manager_->store_sticker(document.file_id, false, storer, "Document"); break; case Document::Type::Video: td->videos_manager_->store_video(document.file_id, storer); diff --git a/td/telegram/Global.h b/td/telegram/Global.h index 3848bc42f..751610eb0 100644 --- a/td/telegram/Global.h +++ b/td/telegram/Global.h @@ -496,8 +496,7 @@ class Global final : public ActorContext { inline Global *G_impl(const char *file, int line) { ActorContext *context = Scheduler::context(); - CHECK(context); - LOG_CHECK(context->get_id() == Global::ID) << "In " << file << " at " << line; + LOG_CHECK(context != nullptr && context->get_id() == Global::ID) << "In " << file << " at " << line; return static_cast(context); } diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index de53f4c40..d83625577 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -803,7 +803,7 @@ static void store(const MessageContent *content, StorerT &storer) { } case MessageContentType::Sticker: { auto m = static_cast(content); - td->stickers_manager_->store_sticker(m->file_id, false, storer); + td->stickers_manager_->store_sticker(m->file_id, false, storer, "MessageSticker"); break; } case MessageContentType::Text: { diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 23115b88e..5c3e1a7f3 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -5708,7 +5708,11 @@ MessagesManager::Dialog::~Dialog() { } } -MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +MessagesManager::MessagesManager(Td *td, ActorShared<> parent) + : recently_found_dialogs_{td, "recently_found", MAX_RECENT_DIALOGS} + , recently_opened_dialogs_{td, "recently_opened", MAX_RECENT_DIALOGS} + , td_(td) + , parent_(std::move(parent)) { upload_media_callback_ = std::make_shared(); upload_thumbnail_callback_ = std::make_shared(); upload_dialog_photo_callback_ = std::make_shared(); @@ -11172,9 +11176,8 @@ void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise &&prom d->is_empty = false; d->need_restore_reply_markup = true; } - if (remove_recently_found_dialog_internal(dialog_id)) { - save_recently_found_dialogs(); - } + recently_found_dialogs_.remove_dialog(dialog_id); + recently_opened_dialogs_.remove_dialog(dialog_id); if (dialog_id.get_type() == DialogType::Channel) { G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); } @@ -13865,6 +13868,14 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f if (need_update_dialog_pos && d != nullptr) { send_update_chat_last_message(d, "on_get_message"); } + if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) { + CHECK(d != nullptr); + if (!old_message_id.is_valid() || !message_id.is_valid() || old_message_id <= message_id) { + LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << message_id + << " from " << source << ": " << debug_add_message_to_dialog_fail_reason_; + } + send_update_delete_messages(dialog_id, {message_id.get()}, true, false); + } return FullMessageId(); } @@ -16559,17 +16570,7 @@ std::pair> MessagesManager::search_dialogs(const string return {}; } if (query.empty()) { - if (!load_recently_found_dialogs(promise)) { - return {}; - } - - promise.set_value(Unit()); - - update_recently_found_dialogs(); - - size_t result_size = min(static_cast(limit), recently_found_dialog_ids_.size()); - return {narrow_cast(recently_found_dialog_ids_.size()), - vector(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.begin() + result_size)}; + return recently_found_dialogs_.get_dialogs(limit, std::move(promise)); } auto result = dialogs_hints_.search(query, limit); @@ -16583,6 +16584,10 @@ std::pair> MessagesManager::search_dialogs(const string return {narrow_cast(result.first), std::move(dialog_ids)}; } +std::pair> MessagesManager::get_recently_opened_dialogs(int32 limit, Promise &&promise) { + return recently_opened_dialogs_.get_dialogs(limit, std::move(promise)); +} + vector MessagesManager::sort_dialogs_by_order(const vector &dialog_ids, int32 limit) const { CHECK(!td_->auth_manager_->is_bot()); int64 fake_order = static_cast(dialog_ids.size()) + 1; @@ -19246,10 +19251,13 @@ Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_pt dialog_id = DialogId(sender_user_id); } if (dialog_id == get_my_dialog_id()) { - return Status::Error(5, is_blocked ? Slice("Can't block self") : Slice("Can't unblock self")); + return Status::Error(400, is_blocked ? Slice("Can't block self") : Slice("Can't unblock self")); } Dialog *d = get_dialog_force(dialog_id, "toggle_message_sender_is_blocked"); + if (!have_input_peer(dialog_id, AccessRights::Know)) { + return Status::Error(400, "Message sender isn't accessible"); + } if (d != nullptr) { if (is_blocked == d->is_blocked) { return Status::OK(); @@ -19295,11 +19303,6 @@ uint64 MessagesManager::save_toggle_dialog_is_blocked_on_server_log_event(Dialog } void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id) { - if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) { - // don't even create new binlog events - return; - } - if (log_event_id == 0 && G()->parameters().use_message_db) { log_event_id = save_toggle_dialog_is_blocked_on_server_log_event(dialog_id, is_blocked); } @@ -19875,7 +19878,11 @@ void MessagesManager::read_message_contents_on_server(DialogId dialog_id, vector void MessagesManager::open_dialog(Dialog *d) { DialogId dialog_id = d->dialog_id; - if (d->is_opened || !have_input_peer(dialog_id, AccessRights::Read)) { + if (!have_input_peer(dialog_id, AccessRights::Read)) { + return; + } + recently_opened_dialogs_.add_dialog(dialog_id); + if (d->is_opened) { return; } d->is_opened = true; @@ -21767,8 +21774,7 @@ void MessagesManager::delete_bot_command_message_id(DialogId dialog_id, MessageI if (it == dialog_bot_command_message_ids_.end()) { return; } - it->second.message_ids.erase(message_id); - if (it->second.message_ids.empty()) { + if (it->second.message_ids.erase(message_id) && it->second.message_ids.empty()) { dialog_bot_command_message_ids_.erase(it); } } @@ -25125,6 +25131,10 @@ bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const { ContactsManager::ChannelType::Broadcast; } +bool MessagesManager::is_deleted_secret_chat(DialogId dialog_id) const { + return is_deleted_secret_chat(get_dialog(dialog_id)); +} + bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const { if (d == nullptr) { return true; @@ -29217,25 +29227,27 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI sent_message->have_previous = true; sent_message->have_next = true; + send_update_message_send_succeeded(d, old_message_id, sent_message.get()); + bool need_update = true; Message *m = add_message_to_dialog(d, std::move(sent_message), true, &need_update, &need_update_dialog_pos, source); - if (m == nullptr) { - if (old_message_id.is_valid() && new_message_id < old_message_id) { - // the message ID has decreased. This could happen if some messages were lost. - // In this case the failure is possible - return {}; - } - LOG(FATAL) << td_->contacts_manager_->get_my_id() << " " << dialog_id << " " << old_message_id << " " - << new_message_id << " " << d->last_clear_history_message_id << " " << d->max_unavailable_message_id - << " " << d->last_message_id << " " << d->last_new_message_id << " " << d->last_assigned_message_id - << " " << have_input_peer(dialog_id, AccessRights::Read) << " " - << debug_add_message_to_dialog_fail_reason_ << " " << source; - } - - send_update_message_send_succeeded(d, old_message_id, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "on_send_message_success"); } + + if (m == nullptr) { + if (!(old_message_id.is_valid() && new_message_id < old_message_id) && + !(ttl_period > 0 && date + ttl_period <= G()->server_time())) { + // if message ID has decreased, which could happen if some messages were lost, + // or the message has already been deleted after TTL period, then the error is expected + LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << new_message_id + << " from " << source << ": " << debug_add_message_to_dialog_fail_reason_; + } + send_update_delete_messages(dialog_id, {new_message_id.get()}, true, false); + being_readded_message_id_ = FullMessageId(); + return {}; + } + try_add_active_live_location(dialog_id, m); update_reply_count_by_message(d, +1, m); update_forward_count(dialog_id, m); @@ -30445,7 +30457,9 @@ void MessagesManager::set_dialog_has_bots(Dialog *d, bool has_bots) { auto it = dialog_bot_command_message_ids_.find(d->dialog_id); if (it != dialog_bot_command_message_ids_.end()) { for (auto message_id : it->second.message_ids) { - send_update_message_content_impl(d->dialog_id, get_message(d, message_id), "set_dialog_has_bots"); + auto m = get_message(d, message_id); + LOG_CHECK(m != nullptr) << d->dialog_id << ' ' << message_id; + send_update_message_content_impl(d->dialog_id, m, "set_dialog_has_bots"); } } } @@ -37630,8 +37644,8 @@ void MessagesManager::on_binlog_events(vector &&events) { log_event_parse(log_event, event.data_).ensure(); auto dialog_id = log_event.dialog_id_; - Dialog *d = get_dialog_force(dialog_id, "ToggleDialogIsBlockedOnServerLogEvent"); - if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { + if (dialog_id.get_type() == DialogType::SecretChat || !have_dialog_info_force(dialog_id) || + !have_input_peer(dialog_id, AccessRights::Know)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37794,213 +37808,24 @@ void MessagesManager::on_binlog_events(vector &&events) { } } -void MessagesManager::save_recently_found_dialogs() { - if (recently_found_dialogs_loaded_ < 2) { - return; - } - - string value; - for (auto &dialog_id : recently_found_dialog_ids_) { - if (!value.empty()) { - value += ','; - } - if (!G()->parameters().use_message_db) { - // if there is no dialog database, prefer to save dialogs by username - auto username = get_dialog_username(dialog_id); - if (dialog_id.get_type() != DialogType::SecretChat && !username.empty()) { - value += '@'; - value += username; - continue; - } - } - value += to_string(dialog_id.get()); - } - LOG(DEBUG) << "Save recently found chats " << value; - G()->td_db()->get_binlog_pmc()->set("recently_found_dialog_usernames_and_ids", value); -} - -bool MessagesManager::load_recently_found_dialogs(Promise &promise) { - CHECK(!td_->auth_manager_->is_bot()); - if (recently_found_dialogs_loaded_ >= 2) { - return true; - } - - string found_dialogs_str = G()->td_db()->get_binlog_pmc()->get("recently_found_dialog_usernames_and_ids"); - if (found_dialogs_str.empty()) { - recently_found_dialogs_loaded_ = 2; - if (!recently_found_dialog_ids_.empty()) { - save_recently_found_dialogs(); - } - return true; - } - - LOG(DEBUG) << "Loaded recently found chats " << found_dialogs_str; - auto found_dialogs = full_split(found_dialogs_str, ','); - if (recently_found_dialogs_loaded_ == 1 && resolve_recently_found_dialogs_multipromise_.promise_count() == 0) { - // queries was sent and have already been finished - auto newly_found_dialogs = std::move(recently_found_dialog_ids_); - recently_found_dialog_ids_.clear(); - - for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) { - if ((*it)[0] == '@') { - auto dialog_id = resolve_dialog_username(it->substr(1)); - if (dialog_id.is_valid() && have_input_peer(dialog_id, AccessRights::Read)) { - force_create_dialog(dialog_id, "recently found resolved dialog"); - add_recently_found_dialog_internal(dialog_id); - } - } else { - auto dialog_id = DialogId(to_integer(*it)); - CHECK(dialog_id.is_valid()); - if (have_input_peer(dialog_id, AccessRights::Read)) { - force_create_dialog(dialog_id, "recently found dialog"); - add_recently_found_dialog_internal(dialog_id); - } - } - } - for (auto it = newly_found_dialogs.rbegin(); it != newly_found_dialogs.rend(); ++it) { - add_recently_found_dialog_internal(*it); - } - recently_found_dialogs_loaded_ = 2; - if (!newly_found_dialogs.empty()) { - save_recently_found_dialogs(); - } - return true; - } - - resolve_recently_found_dialogs_multipromise_.add_promise(std::move(promise)); - if (recently_found_dialogs_loaded_ == 0) { - recently_found_dialogs_loaded_ = 1; - - resolve_recently_found_dialogs_multipromise_.set_ignore_errors(true); - auto lock = resolve_recently_found_dialogs_multipromise_.get_promise(); - - for (auto &found_dialog : found_dialogs) { - if (found_dialog[0] == '@') { - search_public_dialog(found_dialog, false, resolve_recently_found_dialogs_multipromise_.get_promise()); - } - } - if (G()->parameters().use_message_db) { - for (auto &found_dialog : found_dialogs) { - if (found_dialog[0] != '@') { - auto dialog_id = DialogId(to_integer(found_dialog)); - CHECK(dialog_id.is_valid()); - // TODO use asynchronous load - // get_dialog(dialog_id, resolve_recently_found_dialogs_multipromise_.get_promise()); - get_dialog_force(dialog_id, "load_recently_found_dialogs"); - } - } - } else { - get_dialogs_from_list(DialogListId(FolderId::main()), MAX_GET_DIALOGS + 2, - PromiseCreator::lambda( - [promise = resolve_recently_found_dialogs_multipromise_.get_promise()]( - td_api::object_ptr &&chats) mutable { promise.set_value(Unit()); })); - td_->contacts_manager_->search_contacts("", 1, resolve_recently_found_dialogs_multipromise_.get_promise()); - } - - lock.set_value(Unit()); - } - return false; -} - Status MessagesManager::add_recently_found_dialog(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "add_recently_found_dialog")) { - return Status::Error(5, "Chat not found"); + return Status::Error(400, "Chat not found"); } - if (add_recently_found_dialog_internal(dialog_id)) { - save_recently_found_dialogs(); - } - + recently_found_dialogs_.add_dialog(dialog_id); return Status::OK(); } Status MessagesManager::remove_recently_found_dialog(DialogId dialog_id) { if (!have_dialog_force(dialog_id, "remove_recently_found_dialog")) { - return Status::Error(5, "Chat not found"); + return Status::Error(400, "Chat not found"); } - if (remove_recently_found_dialog_internal(dialog_id)) { - save_recently_found_dialogs(); - } - + recently_found_dialogs_.remove_dialog(dialog_id); return Status::OK(); } void MessagesManager::clear_recently_found_dialogs() { - recently_found_dialogs_loaded_ = 2; - if (recently_found_dialog_ids_.empty()) { - return; - } - - recently_found_dialog_ids_.clear(); - save_recently_found_dialogs(); -} - -bool MessagesManager::add_recently_found_dialog_internal(DialogId dialog_id) { - CHECK(have_dialog(dialog_id)); - - if (!recently_found_dialog_ids_.empty() && recently_found_dialog_ids_[0] == dialog_id) { - return false; - } - - // TODO create function - auto it = std::find(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.end(), dialog_id); - if (it == recently_found_dialog_ids_.end()) { - if (narrow_cast(recently_found_dialog_ids_.size()) == MAX_RECENTLY_FOUND_DIALOGS) { - CHECK(!recently_found_dialog_ids_.empty()); - recently_found_dialog_ids_.back() = dialog_id; - } else { - recently_found_dialog_ids_.push_back(dialog_id); - } - it = recently_found_dialog_ids_.end() - 1; - } - std::rotate(recently_found_dialog_ids_.begin(), it, it + 1); - return true; -} - -bool MessagesManager::remove_recently_found_dialog_internal(DialogId dialog_id) { - CHECK(have_dialog(dialog_id)); - return td::remove(recently_found_dialog_ids_, dialog_id); -} - -void MessagesManager::update_recently_found_dialogs() { - vector dialog_ids; - for (auto dialog_id : recently_found_dialog_ids_) { - const Dialog *d = get_dialog(dialog_id); - if (d == nullptr) { - continue; - } - switch (dialog_id.get_type()) { - case DialogType::User: - // always keep - break; - case DialogType::Chat: { - auto channel_id = td_->contacts_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id()); - if (channel_id.is_valid() && get_dialog(DialogId(channel_id)) != nullptr) { - dialog_id = DialogId(channel_id); - } - break; - } - case DialogType::Channel: - // always keep - break; - case DialogType::SecretChat: - if (is_deleted_secret_chat(d)) { - dialog_id = DialogId(); - } - break; - case DialogType::None: - default: - UNREACHABLE(); - break; - } - if (dialog_id.is_valid()) { - dialog_ids.push_back(dialog_id); - } - } - - if (dialog_ids != recently_found_dialog_ids_) { - recently_found_dialog_ids_ = std::move(dialog_ids); - save_recently_found_dialogs(); - } + recently_found_dialogs_.clear_dialogs(); } void MessagesManager::suffix_load_loop(Dialog *d) { diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 4d76eba41..8f9209a5f 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -42,6 +42,7 @@ #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationId.h" #include "td/telegram/NotificationSettings.h" +#include "td/telegram/RecentDialogList.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ReportReason.h" #include "td/telegram/RestrictionReason.h" @@ -374,6 +375,8 @@ class MessagesManager final : public Actor { void clear_recently_found_dialogs(); + std::pair> get_recently_opened_dialogs(int32 limit, Promise &&promise); + DialogId resolve_dialog_username(const string &username) const; DialogId search_public_dialog(const string &username_to_search, bool force, Promise &&promise); @@ -581,6 +584,8 @@ class MessagesManager final : public Actor { bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds); + bool is_deleted_secret_chat(DialogId dialog_id) const; + Result> get_message_link(FullMessageId full_message_id, int32 media_timestamp, bool for_group, bool for_comment); @@ -1670,7 +1675,7 @@ class MessagesManager final : public Actor { static constexpr int32 MIN_CHANNEL_DIFFERENCE = 1; static constexpr int32 MAX_CHANNEL_DIFFERENCE = 100; static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000; // server side limit - static constexpr int32 MAX_RECENTLY_FOUND_DIALOGS = 50; // some reasonable value + static constexpr int32 MAX_RECENT_DIALOGS = 50; // some reasonable value static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description static constexpr size_t MAX_DIALOG_FILTER_TITLE_LENGTH = 12; // server side limit for dialog filter title @@ -2987,16 +2992,6 @@ class MessagesManager final : public Actor { static MessageId get_next_yet_unsent_scheduled_message_id(Dialog *d, int32 date); - bool add_recently_found_dialog_internal(DialogId dialog_id); - - bool remove_recently_found_dialog_internal(DialogId dialog_id); - - void update_recently_found_dialogs(); - - void save_recently_found_dialogs(); - - bool load_recently_found_dialogs(Promise &promise); - void reget_message_from_server_if_needed(DialogId dialog_id, const Message *m); void speculatively_update_active_group_call_id(Dialog *d, const Message *m); @@ -3091,10 +3086,8 @@ class MessagesManager final : public Actor { static DialogId get_message_original_sender(const Message *m); - int32 recently_found_dialogs_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded - MultiPromiseActor resolve_recently_found_dialogs_multipromise_{"ResolveRecentlyFoundDialogsMultiPromiseActor"}; - - vector recently_found_dialog_ids_; + RecentDialogList recently_found_dialogs_; + RecentDialogList recently_opened_dialogs_; class UploadMediaCallback; class UploadThumbnailCallback; diff --git a/td/telegram/RecentDialogList.cpp b/td/telegram/RecentDialogList.cpp new file mode 100644 index 000000000..05ceccd23 --- /dev/null +++ b/td/telegram/RecentDialogList.cpp @@ -0,0 +1,255 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/RecentDialogList.h" + +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/TdParameters.h" + +#include "td/utils/algorithm.h" +#include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" + +namespace td { + +RecentDialogList::RecentDialogList(Td *td, const char *name, size_t max_size) + : td_(td), name_(name), max_size_(max_size) { + register_actor(PSLICE() << name << "_chats", this).release(); +} + +string RecentDialogList::get_binlog_key() const { + return PSTRING() << name_ << "_dialog_usernames_and_ids"; +} + +void RecentDialogList::save_dialogs() const { + if (!is_loaded_) { + return; + } + CHECK(removed_dialog_ids_.empty()); + + SliceBuilder sb; + for (auto &dialog_id : dialog_ids_) { + sb << ','; + if (!G()->parameters().use_message_db) { + // if there is no dialog database, prefer to save dialogs by username + string username; + switch (dialog_id.get_type()) { + case DialogType::User: + username = td_->contacts_manager_->get_user_username(dialog_id.get_user_id()); + break; + case DialogType::Chat: + break; + case DialogType::Channel: + username = td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()); + break; + case DialogType::SecretChat: + break; + case DialogType::None: + default: + UNREACHABLE(); + } + if (!username.empty() && username.find(',') == string::npos) { + sb << '@' << username; + continue; + } + } + sb << dialog_id.get(); + } + auto result = sb.as_cslice(); + if (!result.empty()) { + result.remove_prefix(1); + } + G()->td_db()->get_binlog_pmc()->set(get_binlog_key(), result.str()); +} + +void RecentDialogList::load_dialogs(Promise &&promise) { + if (is_loaded_) { + return promise.set_value(Unit()); + } + + load_list_queries_.push_back(std::move(promise)); + if (load_list_queries_.size() != 1) { + return; + } + + auto found_dialogs = full_split(G()->td_db()->get_binlog_pmc()->get(get_binlog_key()), ','); + MultiPromiseActorSafe mpas{"LoadRecentDialogListMultiPromiseActor"}; + mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), found_dialogs](Unit) mutable { + send_closure(actor_id, &RecentDialogList::on_load_dialogs, std::move(found_dialogs)); + })); + mpas.set_ignore_errors(true); + auto lock = mpas.get_promise(); + + vector dialog_ids; + for (auto &found_dialog : found_dialogs) { + if (found_dialog[0] == '@') { + td_->messages_manager_->search_public_dialog(found_dialog, false, mpas.get_promise()); + } else { + dialog_ids.push_back(DialogId(to_integer(found_dialog))); + } + } + if (!dialog_ids.empty()) { + if (G()->parameters().use_message_db) { + td_->messages_manager_->load_dialogs(std::move(dialog_ids), mpas.get_promise()); + } else { + td_->messages_manager_->get_dialogs_from_list( + DialogListId(FolderId::main()), 102, + PromiseCreator::lambda([promise = mpas.get_promise()](td_api::object_ptr &&chats) mutable { + promise.set_value(Unit()); + })); + td_->contacts_manager_->search_contacts("", 1, mpas.get_promise()); + } + } + + lock.set_value(Unit()); +} + +void RecentDialogList::on_load_dialogs(vector &&found_dialogs) { + auto promises = std::move(load_list_queries_); + CHECK(!promises.empty()); + + auto newly_found_dialogs = std::move(dialog_ids_); + reset_to_empty(dialog_ids_); + + for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) { + DialogId dialog_id; + if ((*it)[0] == '@') { + dialog_id = td_->messages_manager_->resolve_dialog_username(it->substr(1)); + } else { + dialog_id = DialogId(to_integer(*it)); + } + if (dialog_id.is_valid() && removed_dialog_ids_.count(dialog_id) == 0 && + td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->messages_manager_->force_create_dialog(dialog_id, "recent dialog"); + do_add_dialog(dialog_id); + } + } + for (auto it = newly_found_dialogs.rbegin(); it != newly_found_dialogs.rend(); ++it) { + do_add_dialog(*it); + } + is_loaded_ = true; + removed_dialog_ids_.clear(); + if (!newly_found_dialogs.empty()) { + save_dialogs(); + } + + for (auto &promise : promises) { + promise.set_value(Unit()); + } +} + +void RecentDialogList::add_dialog(DialogId dialog_id) { + if (!is_loaded_) { + load_dialogs(Promise()); + } + if (do_add_dialog(dialog_id)) { + save_dialogs(); + } +} + +bool RecentDialogList::do_add_dialog(DialogId dialog_id) { + if (!dialog_ids_.empty() && dialog_ids_[0] == dialog_id) { + return false; + } + + // TODO create function + auto it = std::find(dialog_ids_.begin(), dialog_ids_.end(), dialog_id); + if (it == dialog_ids_.end()) { + if (dialog_ids_.size() == max_size_) { + CHECK(!dialog_ids_.empty()); + dialog_ids_.back() = dialog_id; + } else { + dialog_ids_.push_back(dialog_id); + } + it = dialog_ids_.end() - 1; + } + std::rotate(dialog_ids_.begin(), it, it + 1); + removed_dialog_ids_.erase(dialog_id); + return true; +} + +void RecentDialogList::remove_dialog(DialogId dialog_id) { + if (!is_loaded_) { + load_dialogs(Promise()); + } + if (td::remove(dialog_ids_, dialog_id)) { + save_dialogs(); + } else if (!is_loaded_) { + removed_dialog_ids_.insert(dialog_id); + } +} + +void RecentDialogList::update_dialogs() { + CHECK(is_loaded_); + vector dialog_ids; + for (auto dialog_id : dialog_ids_) { + if (!td_->messages_manager_->have_dialog(dialog_id)) { + continue; + } + switch (dialog_id.get_type()) { + case DialogType::User: + // always keep + break; + case DialogType::Chat: { + auto channel_id = td_->contacts_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id()); + if (channel_id.is_valid() && td_->messages_manager_->have_dialog(DialogId(channel_id))) { + dialog_id = DialogId(channel_id); + } + break; + } + case DialogType::Channel: + // always keep + break; + case DialogType::SecretChat: + if (td_->messages_manager_->is_deleted_secret_chat(dialog_id)) { + dialog_id = DialogId(); + } + break; + case DialogType::None: + default: + UNREACHABLE(); + break; + } + if (dialog_id.is_valid()) { + dialog_ids.push_back(dialog_id); + } + } + + if (dialog_ids != dialog_ids_) { + dialog_ids_ = std::move(dialog_ids); + save_dialogs(); + } +} + +std::pair> RecentDialogList::get_dialogs(int32 limit, Promise &&promise) { + load_dialogs(std::move(promise)); + if (!is_loaded_) { + return {}; + } + + update_dialogs(); + + CHECK(limit >= 0); + int32 total_count = narrow_cast(dialog_ids_.size()); + return {total_count, vector(dialog_ids_.begin(), dialog_ids_.begin() + min(limit, total_count))}; +} + +void RecentDialogList::clear_dialogs() { + if (dialog_ids_.empty() && is_loaded_) { + return; + } + + is_loaded_ = true; + dialog_ids_.clear(); + removed_dialog_ids_.clear(); + save_dialogs(); +} + +} // namespace td diff --git a/td/telegram/RecentDialogList.h b/td/telegram/RecentDialogList.h new file mode 100644 index 000000000..73d5e4239 --- /dev/null +++ b/td/telegram/RecentDialogList.h @@ -0,0 +1,59 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/DialogId.h" + +#include "td/actor/actor.h" +#include "td/actor/PromiseFuture.h" + +#include "td/utils/common.h" + +#include +#include + +namespace td { + +class Td; + +// stores list of Dialog identifiers of a limited size +class RecentDialogList : public Actor { + public: + RecentDialogList(Td *td, const char *name, size_t max_size); + + void add_dialog(DialogId dialog_id); + + void remove_dialog(DialogId dialog_id); + + std::pair> get_dialogs(int32 limit, Promise &&promise); + + void clear_dialogs(); + + private: + Td *td_; + const char *name_; + size_t max_size_; + vector dialog_ids_; + std::unordered_set removed_dialog_ids_; + + bool is_loaded_ = false; + vector> load_list_queries_; + + void load_dialogs(Promise &&promise); + + void on_load_dialogs(vector &&found_dialogs); + + bool do_add_dialog(DialogId dialog_id); + + string get_binlog_key() const; + + void update_dialogs(); + + void save_dialogs() const; +}; + +} // namespace td diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index e74f7dcb6..cc950f2c4 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -1132,7 +1132,7 @@ class StickersManager::StickerListLogEvent { StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); td::store(narrow_cast(sticker_ids.size()), storer); for (auto sticker_id : sticker_ids) { - stickers_manager->store_sticker(sticker_id, false, storer); + stickers_manager->store_sticker(sticker_id, false, storer, "StickerListLogEvent"); } } diff --git a/td/telegram/StickersManager.h b/td/telegram/StickersManager.h index 46b7ba7c9..037b88760 100644 --- a/td/telegram/StickersManager.h +++ b/td/telegram/StickersManager.h @@ -270,7 +270,7 @@ class StickersManager final : public Actor { void merge_stickers(FileId new_id, FileId old_id, bool can_delete_old); template - void store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer) const; + void store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer, const char *source) const; template FileId parse_sticker(bool in_sticker_set, ParserT &parser); diff --git a/td/telegram/StickersManager.hpp b/td/telegram/StickersManager.hpp index 6c2a3782e..82b5ac68a 100644 --- a/td/telegram/StickersManager.hpp +++ b/td/telegram/StickersManager.hpp @@ -21,7 +21,7 @@ namespace td { template -void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer) const { +void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer, const char *source) const { auto it = stickers_.find(file_id); if (it == stickers_.end() || it->second == nullptr) { return; @@ -171,7 +171,7 @@ void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with store(stored_sticker_count, storer); for (uint32 i = 0; i < stored_sticker_count; i++) { auto sticker_id = sticker_set->sticker_ids[i]; - store_sticker(sticker_id, true, storer); + store_sticker(sticker_id, true, storer, "store_sticker_set"); if (was_loaded) { auto it = sticker_set->sticker_emojis_map_.find(sticker_id); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 4ee8ae9c7..d4399f451 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -964,6 +964,25 @@ class GetInactiveSupergroupChatsRequest final : public RequestActor<> { } }; +class GetRecentlyOpenedChatsRequest final : public RequestActor<> { + int32 limit_; + + std::pair> dialog_ids_; + + void do_run(Promise &&promise) final { + dialog_ids_ = td->messages_manager_->get_recently_opened_dialogs(limit_, std::move(promise)); + } + + void do_send_result() final { + send_result(MessagesManager::get_chats_object(dialog_ids_)); + } + + public: + GetRecentlyOpenedChatsRequest(ActorShared td, uint64 request_id, int32 limit) + : RequestActor(std::move(td), request_id), limit_(limit) { + } +}; + class GetMessageRequest final : public RequestOnceActor { FullMessageId full_message_id_; @@ -4369,6 +4388,7 @@ void Td::send_update(tl_object_ptr &&object) { } switch (object_id) { + case td_api::updateChatThemes::ID: case td_api::updateFavoriteStickers::ID: case td_api::updateInstalledStickerSets::ID: case td_api::updateRecentStickers::ID: @@ -5435,6 +5455,11 @@ void Td::on_request(uint64 id, const td_api::clearRecentlyFoundChats &request) { send_closure(actor_id(this), &Td::send_result, id, make_tl_object()); } +void Td::on_request(uint64 id, const td_api::getRecentlyOpenedChats &request) { + CHECK_IS_USER(); + CREATE_REQUEST(GetRecentlyOpenedChatsRequest, request.limit_); +} + void Td::on_request(uint64 id, const td_api::openChat &request) { CHECK_IS_USER(); answer_ok_query(id, messages_manager_->open_dialog(DialogId(request.chat_id_))); @@ -8135,12 +8160,6 @@ void Td::on_request(uint64 id, const td_api::resetBackgrounds &request) { background_manager_->reset_backgrounds(std::move(promise)); } -void Td::on_request(uint64 id, const td_api::getChatThemes &request) { - CHECK_IS_USER(); - CREATE_REQUEST_PROMISE(); - theme_manager_->get_chat_themes(std::move(promise)); -} - void Td::on_request(uint64 id, td_api::getRecentlyVisitedTMeUrls &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.referrer_); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index a5db5e26b..f13407019 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -595,6 +595,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::clearRecentlyFoundChats &request); + void on_request(uint64 id, const td_api::getRecentlyOpenedChats &request); + void on_request(uint64 id, const td_api::getGroupsInCommon &request); void on_request(uint64 id, td_api::checkChatUsername &request); @@ -1195,8 +1197,6 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::resetBackgrounds &request); - void on_request(uint64 id, const td_api::getChatThemes &request); - void on_request(uint64 id, td_api::getRecentlyVisitedTMeUrls &request); void on_request(uint64 id, td_api::setBotUpdatesStatus &request); diff --git a/td/telegram/ThemeManager.cpp b/td/telegram/ThemeManager.cpp index e2de4ec03..1f7ef974f 100644 --- a/td/telegram/ThemeManager.cpp +++ b/td/telegram/ThemeManager.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/ThemeManager.h" +#include "td/telegram/AuthManager.h" #include "td/telegram/BackgroundManager.h" #include "td/telegram/Global.h" #include "td/telegram/net/NetQueryCreator.h" @@ -14,6 +15,7 @@ #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" +#include "td/utils/Random.h" #include "td/utils/Time.h" namespace td { @@ -44,33 +46,73 @@ class GetChatThemesQuery final : public Td::ResultHandler { } }; +bool operator==(const ThemeManager::ThemeSettings &lhs, const ThemeManager::ThemeSettings &rhs) { + return lhs.accent_color == rhs.accent_color && lhs.background_id == rhs.background_id && + lhs.background_type == rhs.background_type && lhs.base_theme == rhs.base_theme && + lhs.message_colors == rhs.message_colors && lhs.animate_message_colors == rhs.animate_message_colors; +} + +bool operator!=(const ThemeManager::ThemeSettings &lhs, const ThemeManager::ThemeSettings &rhs) { + return !(lhs == rhs); +} + ThemeManager::ThemeManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { - chat_themes_.next_reload_time = Time::now(); +} + +void ThemeManager::start_up() { + init(); +} + +void ThemeManager::init() { + if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { + return; + } + + chat_themes_.next_reload_time = Time::now(); // TODO load chat themes from binlog + loop(); } void ThemeManager::tear_down() { parent_.reset(); } -void ThemeManager::get_chat_themes(Promise> &&promise) { +void ThemeManager::loop() { + if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { + return; + } + if (Time::now() < chat_themes_.next_reload_time) { - return promise.set_value(get_chat_themes_object()); + return set_timeout_at(chat_themes_.next_reload_time); } - if (!chat_themes_.themes.empty()) { - promise.set_value(get_chat_themes_object()); - pending_get_chat_themes_queries_.push_back(Promise>()); - } else { - pending_get_chat_themes_queries_.push_back(std::move(promise)); - } - if (pending_get_chat_themes_queries_.size() == 1) { - auto request_promise = PromiseCreator::lambda( - [actor_id = actor_id(this)](Result> result) { - send_closure(actor_id, &ThemeManager::on_get_chat_themes, std::move(result)); - }); + auto request_promise = PromiseCreator::lambda( + [actor_id = actor_id(this)](Result> result) { + send_closure(actor_id, &ThemeManager::on_get_chat_themes, std::move(result)); + }); - td_->create_handler(std::move(request_promise))->send(chat_themes_.hash); + td_->create_handler(std::move(request_promise))->send(chat_themes_.hash); +} + +void ThemeManager::on_update_theme(telegram_api::object_ptr &&theme, Promise &&promise) { + CHECK(theme != nullptr); + bool is_changed = false; + for (auto &chat_theme : chat_themes_.themes) { + if (chat_theme.light_id == theme->id_ || chat_theme.dark_id == theme->id_) { + auto theme_settings = get_chat_theme_settings(std::move(theme->settings_)); + if (chat_theme.light_id == theme->id_ && chat_theme.light_theme != theme_settings) { + chat_theme.light_theme = theme_settings; + is_changed = true; + } + if (chat_theme.dark_id == theme->id_ && chat_theme.dark_theme != theme_settings) { + chat_theme.dark_theme = theme_settings; + is_changed = true; + } + } } + if (is_changed) { + send_update_chat_themes(); + } + promise.set_value(Unit()); } td_api::object_ptr ThemeManager::get_theme_settings_object(const ThemeSettings &settings) const { @@ -97,54 +139,52 @@ td_api::object_ptr ThemeManager::get_chat_theme_object(const get_theme_settings_object(theme.dark_theme)); } -td_api::object_ptr ThemeManager::get_chat_themes_object() const { - return td_api::make_object( +td_api::object_ptr ThemeManager::get_update_chat_themes_object() const { + return td_api::make_object( transform(chat_themes_.themes, [this](const ChatTheme &theme) { return get_chat_theme_object(theme); })); } +void ThemeManager::send_update_chat_themes() const { + send_closure(G()->td(), &Td::send_update, get_update_chat_themes_object()); +} + void ThemeManager::on_get_chat_themes(Result> result) { - auto promises = std::move(pending_get_chat_themes_queries_); - CHECK(!promises.empty()); - reset_to_empty(pending_get_chat_themes_queries_); - if (result.is_error()) { - // do not clear chat_themes_ - - auto error = result.move_as_error(); - for (auto &promise : promises) { - promise.set_error(error.clone()); - } + set_timeout_in(Random::fast(40, 60)); return; } chat_themes_.next_reload_time = Time::now() + THEME_CACHE_TIME; + set_timeout_at(chat_themes_.next_reload_time); auto chat_themes_ptr = result.move_as_ok(); LOG(DEBUG) << "Receive " << to_string(chat_themes_ptr); - if (chat_themes_ptr->get_id() != telegram_api::account_chatThemesNotModified::ID) { - CHECK(chat_themes_ptr->get_id() == telegram_api::account_chatThemes::ID); - auto chat_themes = telegram_api::move_object_as(chat_themes_ptr); - chat_themes_.hash = chat_themes->hash_; - chat_themes_.themes.clear(); - for (auto &chat_theme : chat_themes->themes_) { - if (chat_theme->emoticon_.empty()) { - LOG(ERROR) << "Receive " << to_string(chat_theme); - continue; - } - - ChatTheme theme; - theme.emoji = std::move(chat_theme->emoticon_); - theme.light_theme = get_chat_theme_settings(std::move(chat_theme->theme_->settings_)); - theme.dark_theme = get_chat_theme_settings(std::move(chat_theme->dark_theme_->settings_)); - chat_themes_.themes.push_back(std::move(theme)); + if (chat_themes_ptr->get_id() == telegram_api::account_chatThemesNotModified::ID) { + return; + } + CHECK(chat_themes_ptr->get_id() == telegram_api::account_chatThemes::ID); + auto chat_themes = telegram_api::move_object_as(chat_themes_ptr); + chat_themes_.hash = chat_themes->hash_; + chat_themes_.themes.clear(); + for (auto &chat_theme : chat_themes->themes_) { + if (chat_theme->emoticon_.empty()) { + LOG(ERROR) << "Receive " << to_string(chat_theme); + continue; } + + ChatTheme theme; + theme.emoji = std::move(chat_theme->emoticon_); + theme.light_id = chat_theme->theme_->id_; + theme.dark_id = chat_theme->dark_theme_->id_; + theme.light_theme = get_chat_theme_settings(std::move(chat_theme->theme_->settings_)); + theme.dark_theme = get_chat_theme_settings(std::move(chat_theme->dark_theme_->settings_)); + if (theme.light_theme.message_colors.empty() || theme.dark_theme.message_colors.empty()) { + continue; + } + chat_themes_.themes.push_back(std::move(theme)); } - for (auto &promise : promises) { - if (promise) { - promise.set_value(get_chat_themes_object()); - } - } + send_update_chat_themes(); } ThemeManager::BaseTheme ThemeManager::get_base_theme( @@ -184,4 +224,12 @@ ThemeManager::ThemeSettings ThemeManager::get_chat_theme_settings( return result; } +void ThemeManager::get_current_state(vector> &updates) const { + if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || chat_themes_.themes.empty()) { + return; + } + + updates.push_back(get_update_chat_themes_object()); +} + } // namespace td diff --git a/td/telegram/ThemeManager.h b/td/telegram/ThemeManager.h index f3a4dbc3a..dc8fcb93d 100644 --- a/td/telegram/ThemeManager.h +++ b/td/telegram/ThemeManager.h @@ -25,7 +25,11 @@ class ThemeManager final : public Actor { public: ThemeManager(Td *td, ActorShared<> parent); - void get_chat_themes(Promise> &&promise); + void init(); + + void on_update_theme(telegram_api::object_ptr &&theme, Promise &&promise); + + void get_current_state(vector> &updates) const; private: enum class BaseTheme : int32 { Classic, Day, Night, Tinted, Arctic }; @@ -41,8 +45,14 @@ class ThemeManager final : public Actor { bool animate_message_colors = false; }; + friend bool operator==(const ThemeSettings &lhs, const ThemeSettings &rhs); + + friend bool operator!=(const ThemeSettings &lhs, const ThemeSettings &rhs); + struct ChatTheme { string emoji; + int64 light_id = 0; + int64 dark_id = 0; ThemeSettings light_theme; ThemeSettings dark_theme; }; @@ -53,6 +63,10 @@ class ThemeManager final : public Actor { vector themes; }; + void start_up() final; + + void loop() final; + void tear_down() final; void on_get_chat_themes(Result> result); @@ -61,14 +75,14 @@ class ThemeManager final : public Actor { td_api::object_ptr get_chat_theme_object(const ChatTheme &theme) const; - td_api::object_ptr get_chat_themes_object() const; + td_api::object_ptr get_update_chat_themes_object() const; + + void send_update_chat_themes() const; static BaseTheme get_base_theme(const telegram_api::object_ptr &base_theme); ThemeSettings get_chat_theme_settings(telegram_api::object_ptr settings); - vector>> pending_get_chat_themes_queries_; - ChatThemes chat_themes_; Td *td_; diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 0739e4219..0ba36bbe7 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -47,6 +47,7 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/ThemeManager.h" #include "td/telegram/WebPagesManager.h" #include "td/actor/MultiPromise.h" @@ -3206,10 +3207,10 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - promise.set_value(Unit()); + td_->theme_manager_->on_update_theme(std::move(update->theme_), std::move(promise)); } +// unsupported updates + } // namespace td diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index 86df374c7..7a3ef1312 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -486,9 +486,9 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); - // unsupported updates - void on_update(tl_object_ptr update, Promise &&promise); + + // unsupported updates }; } // namespace td diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 318edc1f2..d96c78a1d 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -1535,20 +1535,22 @@ class CliClient final : public Actor { static td_api::object_ptr get_solid_pattern_background(int32 color, int32 intensity, bool is_moving) { - return get_gradient_pattern_background(color, color, intensity, is_moving); + return get_gradient_pattern_background(color, color, intensity, false, is_moving); } static td_api::object_ptr get_gradient_pattern_background(int32 top_color, int32 bottom_color, - int32 intensity, bool is_moving) { + int32 intensity, bool is_inverted, + bool is_moving) { return td_api::make_object(get_background_fill(top_color, bottom_color), intensity, - is_moving); + is_inverted, is_moving); } static td_api::object_ptr get_freeform_gradient_pattern_background(vector colors, int32 intensity, + bool is_inverted, bool is_moving) { return td_api::make_object(get_background_fill(std::move(colors)), intensity, - is_moving); + is_inverted, is_moving); } static td_api::object_ptr get_solid_background(int32 color) { @@ -2229,14 +2231,16 @@ class CliClient final : public Actor { send_get_background_url(get_solid_pattern_background(0, 0, false)); send_get_background_url(get_solid_pattern_background(0xFFFFFF, 100, true)); send_get_background_url(get_solid_pattern_background(0xABCDEF, 49, true)); - send_get_background_url(get_gradient_pattern_background(0, 0, 0, false)); - send_get_background_url(get_gradient_pattern_background(0xFFFFFF, 0, 100, true)); - send_get_background_url(get_gradient_pattern_background(0xABCDEF, 0xFEDCBA, 49, true)); - send_get_background_url(get_gradient_pattern_background(0, 0x1000000, 49, true)); - send_get_background_url(get_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA}, 49, true)); - send_get_background_url(get_freeform_gradient_pattern_background({0xABCDEF, 0x111111, 0x222222}, 49, true)); + send_get_background_url(get_gradient_pattern_background(0, 0, 0, false, false)); + send_get_background_url(get_gradient_pattern_background(0, 0, 0, true, false)); + send_get_background_url(get_gradient_pattern_background(0xFFFFFF, 0, 100, false, true)); + send_get_background_url(get_gradient_pattern_background(0xFFFFFF, 0, 100, true, true)); + send_get_background_url(get_gradient_pattern_background(0xABCDEF, 0xFEDCBA, 49, false, true)); + send_get_background_url(get_gradient_pattern_background(0, 0x1000000, 49, false, true)); + send_get_background_url(get_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA}, 49, false, true)); + send_get_background_url(get_freeform_gradient_pattern_background({0xABCDEF, 0x111111, 0x222222}, 49, true, true)); send_get_background_url( - get_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA, 0x111111, 0x222222}, 49, true)); + get_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA, 0x111111, 0x222222}, 49, false, true)); send_get_background_url(get_solid_background(-1)); send_get_background_url(get_solid_background(0xABCDEF)); send_get_background_url(get_solid_background(0x1000000)); @@ -2261,7 +2265,7 @@ class CliClient final : public Actor { } else if (op == "sbggp" || op == "sbggpd") { send_request(td_api::make_object( td_api::make_object(as_input_file(args)), - get_gradient_pattern_background(0xABCDEF, 0xFE, 51, false), op == "sbggpd")); + get_gradient_pattern_background(0xABCDEF, 0xFE, 51, op == "sbggpd", false), op == "sbggpd")); } else if (op == "sbgs" || op == "sbgsd") { int32 color; get_args(args, color); @@ -2296,8 +2300,6 @@ class CliClient final : public Actor { send_request(td_api::make_object(to_integer(args))); } else if (op == "rbgs") { send_request(td_api::make_object()); - } else if (op == "gcts") { - send_request(td_api::make_object()); } else if (op == "gcos") { send_request(td_api::make_object()); } else if (op == "gcoc") { @@ -4050,6 +4052,8 @@ class CliClient final : public Actor { send_request(td_api::make_object(as_chat_id(args))); } else if (op == "crfcs") { send_request(td_api::make_object()); + } else if (op == "groc") { + send_request(td_api::make_object(as_limit(args))); } else if (op == "gwpp") { send_request(td_api::make_object(as_caption(args))); } else if (op == "gwpiv") { diff --git a/td/telegram/net/DcOptions.h b/td/telegram/net/DcOptions.h index 848ec6084..eee4fd10e 100644 --- a/td/telegram/net/DcOptions.h +++ b/td/telegram/net/DcOptions.h @@ -172,7 +172,7 @@ class DcOption { friend bool operator==(const DcOption &lhs, const DcOption &rhs); - friend StringBuilder &operator<<(StringBuilder &sb, const DcOption::PrintFlags &flags); + friend StringBuilder &operator<<(StringBuilder &sb, const PrintFlags &flags); friend StringBuilder &operator<<(StringBuilder &sb, const DcOption &dc_option); diff --git a/td/tl/tl_json.h b/td/tl/tl_json.h index 2c77c8a9b..9b5b6f282 100644 --- a/td/tl/tl_json.h +++ b/td/tl/tl_json.h @@ -17,6 +17,7 @@ #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/tl_storers.h" +#include "td/utils/TlDowncastHelper.h" #include @@ -149,21 +150,6 @@ Status from_json(std::vector &to, JsonValue from) { return Status::OK(); } -template -class DowncastHelper final : public T { - public: - explicit DowncastHelper(int32 constructor) : constructor_(constructor) { - } - int32 get_id() const final { - return constructor_; - } - void store(TlStorerToString &s, const char *field_name) const final { - } - - private: - int32 constructor_{0}; -}; - template std::enable_if_t::value, Status> from_json(tl_object_ptr &to, JsonValue from) { if (from.type() != JsonValue::Type::Object) { @@ -185,7 +171,7 @@ std::enable_if_t::value, Status> from_json(tl_object_p return Status::Error(PSLICE() << "Expected String or Integer, got " << constructor_value.type()); } - DowncastHelper helper(constructor); + TlDowncastHelper helper(constructor); Status status; bool ok = downcast_call(static_cast(helper), [&](auto &dummy) { auto result = make_tl_object>(); diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index cf4c9c4ee..528c8eb19 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -256,10 +256,10 @@ set(TDUTILS_SOURCE td/utils/Time.h td/utils/TimedStat.h td/utils/Timer.h - td/utils/TsFileLog.h td/utils/tl_helpers.h td/utils/tl_parsers.h td/utils/tl_storers.h + td/utils/TlDowncastHelper.h td/utils/translit.h td/utils/TsCerr.h td/utils/TsFileLog.h diff --git a/tdutils/td/utils/TlDowncastHelper.h b/tdutils/td/utils/TlDowncastHelper.h new file mode 100644 index 000000000..c573683af --- /dev/null +++ b/tdutils/td/utils/TlDowncastHelper.h @@ -0,0 +1,26 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +namespace td { + +template +class TlDowncastHelper final : public T { + public: + explicit TlDowncastHelper(int32 constructor) : constructor_(constructor) { + } + int32 get_id() const final { + return constructor_; + } + void store(TlStorerToString &s, const char *field_name) const final { + } + + private: + int32 constructor_{0}; +}; + +} // namespace td diff --git a/tdutils/td/utils/port/wstring_convert.cpp b/tdutils/td/utils/port/wstring_convert.cpp index 45ad32065..c81d0695f 100644 --- a/tdutils/td/utils/port/wstring_convert.cpp +++ b/tdutils/td/utils/port/wstring_convert.cpp @@ -10,6 +10,8 @@ char disable_linker_warning_about_empty_file_wstring_convert_cpp TD_UNUSED; #if TD_PORT_WINDOWS +#include "td/utils/base64.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/utf8.h" #include @@ -18,7 +20,7 @@ namespace td { Result to_wstring(CSlice slice) { if (!check_utf8(slice)) { - return Status::Error("Wrong string encoding"); + return Status::Error(PSLICE() << "String was expected to be encoded in UTF-8: " << base64_encode(slice)); } size_t wstring_len = utf8_utf16_length(slice);