diff --git a/CMakeLists.txt b/CMakeLists.txt index ec99c1405..1beff40de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.8.5 LANGUAGES CXX C) +project(TDLib VERSION 1.8.6 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -293,7 +293,6 @@ set(TDLIB_SOURCE td/telegram/AudiosManager.cpp td/telegram/AuthManager.cpp td/telegram/AutoDownloadSettings.cpp - td/telegram/AvailableReaction.cpp td/telegram/BackgroundManager.cpp td/telegram/BackgroundType.cpp td/telegram/BotCommand.cpp @@ -305,6 +304,7 @@ set(TDLIB_SOURCE td/telegram/CallManager.cpp td/telegram/CallbackQueriesManager.cpp td/telegram/ChannelParticipantFilter.cpp + td/telegram/ChatReactions.cpp td/telegram/ClientActor.cpp td/telegram/ConfigManager.cpp td/telegram/ConnectionState.cpp @@ -333,6 +333,8 @@ set(TDLIB_SOURCE td/telegram/DownloadManager.cpp td/telegram/DownloadManagerCallback.cpp td/telegram/DraftMessage.cpp + td/telegram/EmailVerification.cpp + td/telegram/EmojiStatus.cpp td/telegram/FileReferenceManager.cpp td/telegram/files/FileBitmask.cpp td/telegram/files/FileDb.cpp @@ -431,6 +433,7 @@ set(TDLIB_SOURCE td/telegram/SecureStorage.cpp td/telegram/SecureValue.cpp td/telegram/SendCodeHelper.cpp + td/telegram/SentEmailCode.cpp td/telegram/SequenceDispatcher.cpp td/telegram/SpecialStickerSetType.cpp td/telegram/SponsoredMessageManager.cpp @@ -494,7 +497,6 @@ set(TDLIB_SOURCE td/telegram/AudiosManager.h td/telegram/AuthManager.h td/telegram/AutoDownloadSettings.h - td/telegram/AvailableReaction.h td/telegram/BackgroundId.h td/telegram/BackgroundManager.h td/telegram/BackgroundType.h @@ -510,6 +512,7 @@ set(TDLIB_SOURCE td/telegram/ChannelParticipantFilter.h td/telegram/ChannelType.h td/telegram/ChatId.h + td/telegram/ChatReactions.h td/telegram/ClientActor.h td/telegram/ConfigManager.h td/telegram/ConnectionState.h @@ -542,6 +545,8 @@ set(TDLIB_SOURCE td/telegram/DownloadManager.h td/telegram/DownloadManagerCallback.h td/telegram/DraftMessage.h + td/telegram/EmailVerification.h + td/telegram/EmojiStatus.h td/telegram/EncryptedFile.h td/telegram/FileReferenceManager.h td/telegram/files/FileBitmask.h @@ -677,6 +682,7 @@ set(TDLIB_SOURCE td/telegram/SecureStorage.h td/telegram/SecureValue.h td/telegram/SendCodeHelper.h + td/telegram/SentEmailCode.h td/telegram/SequenceDispatcher.h td/telegram/ServerMessageId.h td/telegram/SetWithPosition.h diff --git a/README.md b/README.md index 1f402cbbd..c8cab1657 100644 --- a/README.md +++ b/README.md @@ -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.5 REQUIRED) +find_package(Td 1.8.6 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlight-team/tdlight/blob/master/example/cpp/CMakeLists.txt). diff --git a/benchmark/bench_actor.cpp b/benchmark/bench_actor.cpp index b3a04f592..afe94ca3c 100644 --- a/benchmark/bench_actor.cpp +++ b/benchmark/bench_actor.cpp @@ -46,10 +46,9 @@ class ActorTraits { } // namespace td class CreateActorBench final : public td::Benchmark { - td::ConcurrentScheduler scheduler_; + td::ConcurrentScheduler scheduler_{0, 0}; void start_up() final { - scheduler_.init(0); scheduler_.start(); } @@ -140,8 +139,7 @@ class RingBench final : public td::Benchmark { } void start_up() final { - scheduler_ = new td::ConcurrentScheduler(); - scheduler_->init(thread_n_); + scheduler_ = new td::ConcurrentScheduler(thread_n_, 0); actor_array_ = td::vector>(actor_n_); for (int i = 0; i < actor_n_; i++) { @@ -293,8 +291,7 @@ class QueryBench final : public td::Benchmark { }; void start_up() final { - scheduler_ = new td::ConcurrentScheduler(); - scheduler_->init(0); + scheduler_ = new td::ConcurrentScheduler(0, 0); server_ = scheduler_->create_actor_unsafe(0, "Server"); scheduler_->start(); diff --git a/benchmark/bench_crypto.cpp b/benchmark/bench_crypto.cpp index fc9cdb5a5..40d7602d2 100644 --- a/benchmark/bench_crypto.cpp +++ b/benchmark/bench_crypto.cpp @@ -52,6 +52,106 @@ class SHA1Bench final : public td::Benchmark { }; #endif +class SHA1ShortBench final : public td::Benchmark { + public: + alignas(64) unsigned char data[SHORT_DATA_SIZE]; + + std::string get_description() const final { + return PSTRING() << "SHA1 [" << SHORT_DATA_SIZE << "B]"; + } + + void start_up() final { + std::fill(std::begin(data), std::end(data), static_cast(123)); + } + + void run(int n) final { + unsigned char md[20]; + for (int i = 0; i < n; i++) { + td::sha1(td::Slice(data, SHORT_DATA_SIZE), md); + } + } +}; + +class SHA256ShortBench final : public td::Benchmark { + public: + alignas(64) unsigned char data[SHORT_DATA_SIZE]; + + std::string get_description() const final { + return PSTRING() << "SHA256 [" << SHORT_DATA_SIZE << "B]"; + } + + void start_up() final { + std::fill(std::begin(data), std::end(data), static_cast(123)); + } + + void run(int n) final { + unsigned char md[32]; + for (int i = 0; i < n; i++) { + td::sha256(td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32)); + } + } +}; + +class SHA512ShortBench final : public td::Benchmark { + public: + alignas(64) unsigned char data[SHORT_DATA_SIZE]; + + std::string get_description() const final { + return PSTRING() << "SHA512 [" << SHORT_DATA_SIZE << "B]"; + } + + void start_up() final { + std::fill(std::begin(data), std::end(data), static_cast(123)); + } + + void run(int n) final { + unsigned char md[64]; + for (int i = 0; i < n; i++) { + td::sha512(td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 64)); + } + } +}; + +class HmacSha256ShortBench final : public td::Benchmark { + public: + alignas(64) unsigned char data[SHORT_DATA_SIZE]; + + std::string get_description() const final { + return PSTRING() << "HMAC-SHA256 [" << SHORT_DATA_SIZE << "B]"; + } + + void start_up() final { + std::fill(std::begin(data), std::end(data), static_cast(123)); + } + + void run(int n) final { + unsigned char md[32]; + for (int i = 0; i < n; i++) { + td::hmac_sha256(td::Slice(data, SHORT_DATA_SIZE), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32)); + } + } +}; + +class HmacSha512ShortBench final : public td::Benchmark { + public: + alignas(64) unsigned char data[SHORT_DATA_SIZE]; + + std::string get_description() const final { + return PSTRING() << "HMAC-SHA512 [" << SHORT_DATA_SIZE << "B]"; + } + + void start_up() final { + std::fill(std::begin(data), std::end(data), static_cast(123)); + } + + void run(int n) final { + unsigned char md[32]; + for (int i = 0; i < n; i++) { + td::hmac_sha256(td::Slice(data, SHORT_DATA_SIZE), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32)); + } + } +}; + class AesEcbBench final : public td::Benchmark { public: alignas(64) unsigned char data[DATA_SIZE]; @@ -415,6 +515,11 @@ int main() { #if OPENSSL_VERSION_NUMBER <= 0x10100000L td::bench(SHA1Bench()); #endif + td::bench(SHA1ShortBench()); + td::bench(SHA256ShortBench()); + td::bench(SHA512ShortBench()); + td::bench(HmacSha256ShortBench()); + td::bench(HmacSha512ShortBench()); td::bench(Crc32Bench()); td::bench(Crc64Bench()); } diff --git a/benchmark/bench_db.cpp b/benchmark/bench_db.cpp index b257951c5..822a91593 100644 --- a/benchmark/bench_db.cpp +++ b/benchmark/bench_db.cpp @@ -29,7 +29,7 @@ template class TdKvBench final : public td::Benchmark { - td::ConcurrentScheduler sched; + td::ConcurrentScheduler sched{1, 0}; td::string name_; public: @@ -72,7 +72,6 @@ class TdKvBench final : public td::Benchmark { }; void start_up_n(int n) final { - sched.init(1); sched.create_actor_unsafe
(1, "Main", n).release(); } @@ -179,8 +178,7 @@ class SqliteKeyValueAsyncBench final : public td::Benchmark { td::unique_ptr sqlite_kv_async_; td::Status do_start_up() { - scheduler_ = td::make_unique(); - scheduler_->init(1); + scheduler_ = td::make_unique(1, 0); auto guard = scheduler_->get_main_guard(); diff --git a/benchmark/bench_http.cpp b/benchmark/bench_http.cpp index fe90d32a2..70fdb909c 100644 --- a/benchmark/bench_http.cpp +++ b/benchmark/bench_http.cpp @@ -66,8 +66,7 @@ class HttpClient final : public td::HttpOutboundConnection::Callback { int main() { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); - auto scheduler = td::make_unique(); - scheduler->init(0); + auto scheduler = td::make_unique(0, 0); scheduler->create_actor_unsafe(0, "Client1").release(); scheduler->create_actor_unsafe(0, "Client2").release(); scheduler->start(); diff --git a/benchmark/bench_http_server.cpp b/benchmark/bench_http_server.cpp index ded5cfa81..33cde9ef8 100644 --- a/benchmark/bench_http_server.cpp +++ b/benchmark/bench_http_server.cpp @@ -74,8 +74,7 @@ class Server final : public td::TcpListener::Callback { int main() { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); - auto scheduler = td::make_unique(); - scheduler->init(N); + auto scheduler = td::make_unique(N, 0); scheduler->create_actor_unsafe(0, "Server").release(); scheduler->start(); while (scheduler->run_main(10)) { diff --git a/benchmark/bench_http_server_cheat.cpp b/benchmark/bench_http_server_cheat.cpp index fe145f326..8bbd768b4 100644 --- a/benchmark/bench_http_server_cheat.cpp +++ b/benchmark/bench_http_server_cheat.cpp @@ -121,8 +121,7 @@ class Server final : public td::TcpListener::Callback { int main() { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); - auto scheduler = td::make_unique(); - scheduler->init(N); + auto scheduler = td::make_unique(N, 0); scheduler->create_actor_unsafe(0, "Server").release(); scheduler->start(); while (scheduler->run_main(10)) { diff --git a/benchmark/bench_http_server_fast.cpp b/benchmark/bench_http_server_fast.cpp index d46273cab..4b140422f 100644 --- a/benchmark/bench_http_server_fast.cpp +++ b/benchmark/bench_http_server_fast.cpp @@ -106,8 +106,7 @@ class Server final : public td::TcpListener::Callback { int main() { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); - auto scheduler = td::make_unique(); - scheduler->init(N); + auto scheduler = td::make_unique(N, 0); scheduler->create_actor_unsafe(0, "Server").release(); scheduler->start(); while (scheduler->run_main(10)) { diff --git a/benchmark/bench_tddb.cpp b/benchmark/bench_tddb.cpp index eb6880359..b41033c6e 100644 --- a/benchmark/bench_tddb.cpp +++ b/benchmark/bench_tddb.cpp @@ -87,8 +87,7 @@ class MessagesDbBench final : public td::Benchmark { std::shared_ptr messages_db_async_; td::Status do_start_up() { - scheduler_ = td::make_unique(); - scheduler_->init(1); + scheduler_ = td::make_unique(1, 0); auto guard = scheduler_->get_main_guard(); diff --git a/benchmark/wget.cpp b/benchmark/wget.cpp index 1fdfc2af1..5145f317f 100644 --- a/benchmark/wget.cpp +++ b/benchmark/wget.cpp @@ -23,8 +23,7 @@ int main(int argc, char *argv[]) { auto timeout = 10; auto ttl = 3; auto prefer_ipv6 = (argc > 2 && td::string(argv[2]) == "-6"); - auto scheduler = td::make_unique(); - scheduler->init(0); + auto scheduler = td::make_unique(0, 0); scheduler ->create_actor_unsafe(0, "Client", td::PromiseCreator::lambda([](td::Result> res) { diff --git a/build.html b/build.html index 10b489f5e..e2d8728de 100644 --- a/build.html +++ b/build.html @@ -746,7 +746,7 @@ function onOptionsChanged() { pre_text.push('Note that building requires a lot of memory, so you may need to increase allowed per-process memory usage in /etc/login.conf or build from root.'); } if (os_netbsd) { - pre_text.push('Note that the following instruction is for NetBSD 8.0 and default SH shell.'); + pre_text.push('Note that the following instruction is for NetBSD 8+ and default SH shell.'); } if (os_mac) { pre_text.push('Note that the following instruction will build TDLight only for ' + os_mac_host_name + '.'); @@ -917,8 +917,8 @@ function onOptionsChanged() { if (!use_root) { commands.push('su -'); } - commands.push('export PKG_PATH=ftp://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/i386/8.0_2019Q2/All'); - var packages = 'git gperf php-7.3.6 cmake openssl gcc5-libs'; + commands.push('export PKG_PATH=http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r)/All'); + var packages = 'git gperf pcre2 php-8* cmake openssl gcc12-libs mozilla-rootcerts-openssl'; commands.push('pkg_add ' + packages); if (!use_root) { commands.push('exit'); diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index 1356ac7f3..a9548d67c 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.8.5 REQUIRED) +find_package(Td 1.8.6 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/cpp/td_example.cpp b/example/cpp/td_example.cpp index 773a7c443..3b6cf42ed 100644 --- a/example/cpp/td_example.cpp +++ b/example/cpp/td_example.cpp @@ -238,82 +238,85 @@ class TdExample { void on_authorization_state_update() { authentication_query_id_++; - td_api::downcast_call( - *authorization_state_, - overloaded( - [this](td_api::authorizationStateReady &) { - are_authorized_ = true; - std::cout << "Got authorization" << std::endl; - }, - [this](td_api::authorizationStateLoggingOut &) { - are_authorized_ = false; - std::cout << "Logging out" << std::endl; - }, - [this](td_api::authorizationStateClosing &) { std::cout << "Closing" << std::endl; }, - [this](td_api::authorizationStateClosed &) { - are_authorized_ = false; - need_restart_ = true; - std::cout << "Terminated" << std::endl; - }, - [this](td_api::authorizationStateWaitCode &) { - std::cout << "Enter authentication code: " << std::flush; - std::string code; - std::cin >> code; - send_query(td_api::make_object(code), - create_authentication_query_handler()); - }, - [this](td_api::authorizationStateWaitRegistration &) { - std::string first_name; - std::string last_name; - std::cout << "Enter your first name: " << std::flush; - std::cin >> first_name; - std::cout << "Enter your last name: " << std::flush; - std::cin >> last_name; - send_query(td_api::make_object(first_name, last_name), - create_authentication_query_handler()); - }, - [this](td_api::authorizationStateWaitPassword &) { - std::cout << "Enter authentication password: " << std::flush; - std::string password; - std::getline(std::cin, password); - send_query(td_api::make_object(password), - create_authentication_query_handler()); - }, - [this](td_api::authorizationStateWaitOtherDeviceConfirmation &state) { - std::cout << "Confirm this login link on another device: " << state.link_ << std::endl; - }, - [this](td_api::authorizationStateWaitPhoneNumber &) { - std::cout << "Enter phone number: " << std::flush; - std::string phone_number; - std::cin >> phone_number; - send_query(td_api::make_object(phone_number, nullptr), - create_authentication_query_handler()); - }, - [this](td_api::authorizationStateWaitEncryptionKey &) { - std::cout << "Enter encryption key or DESTROY: " << std::flush; - std::string key; - std::getline(std::cin, key); - if (key == "DESTROY") { - send_query(td_api::make_object(), create_authentication_query_handler()); - } else { - send_query(td_api::make_object(std::move(key)), - create_authentication_query_handler()); - } - }, - [this](td_api::authorizationStateWaitTdlibParameters &) { - auto parameters = td_api::make_object(); - parameters->database_directory_ = "tdlib"; - parameters->use_message_database_ = true; - parameters->use_secret_chats_ = true; - parameters->api_id_ = 94575; - parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2"; - parameters->system_language_code_ = "en"; - parameters->device_model_ = "Desktop"; - parameters->application_version_ = "1.0"; - parameters->enable_storage_optimizer_ = true; - send_query(td_api::make_object(std::move(parameters)), - create_authentication_query_handler()); - })); + td_api::downcast_call(*authorization_state_, + overloaded( + [this](td_api::authorizationStateReady &) { + are_authorized_ = true; + std::cout << "Got authorization" << std::endl; + }, + [this](td_api::authorizationStateLoggingOut &) { + are_authorized_ = false; + std::cout << "Logging out" << std::endl; + }, + [this](td_api::authorizationStateClosing &) { std::cout << "Closing" << std::endl; }, + [this](td_api::authorizationStateClosed &) { + are_authorized_ = false; + need_restart_ = true; + std::cout << "Terminated" << std::endl; + }, + [this](td_api::authorizationStateWaitPhoneNumber &) { + std::cout << "Enter phone number: " << std::flush; + std::string phone_number; + std::cin >> phone_number; + send_query( + td_api::make_object(phone_number, nullptr), + create_authentication_query_handler()); + }, + [this](td_api::authorizationStateWaitEmailAddress &) { + std::cout << "Enter email address: " << std::flush; + std::string email_address; + std::cin >> email_address; + send_query(td_api::make_object(email_address), + create_authentication_query_handler()); + }, + [this](td_api::authorizationStateWaitEmailCode &) { + std::cout << "Enter email authentication code: " << std::flush; + std::string code; + std::cin >> code; + send_query(td_api::make_object( + td_api::make_object(code)), + create_authentication_query_handler()); + }, + [this](td_api::authorizationStateWaitCode &) { + std::cout << "Enter authentication code: " << std::flush; + std::string code; + std::cin >> code; + send_query(td_api::make_object(code), + create_authentication_query_handler()); + }, + [this](td_api::authorizationStateWaitRegistration &) { + std::string first_name; + std::string last_name; + std::cout << "Enter your first name: " << std::flush; + std::cin >> first_name; + std::cout << "Enter your last name: " << std::flush; + std::cin >> last_name; + send_query(td_api::make_object(first_name, last_name), + create_authentication_query_handler()); + }, + [this](td_api::authorizationStateWaitPassword &) { + std::cout << "Enter authentication password: " << std::flush; + std::string password; + std::getline(std::cin, password); + send_query(td_api::make_object(password), + create_authentication_query_handler()); + }, + [this](td_api::authorizationStateWaitOtherDeviceConfirmation &state) { + std::cout << "Confirm this login link on another device: " << state.link_ << std::endl; + }, + [this](td_api::authorizationStateWaitTdlibParameters &) { + auto request = td_api::make_object(); + request->database_directory_ = "tdlib"; + request->use_message_database_ = true; + request->use_secret_chats_ = true; + request->api_id_ = 94575; + request->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2"; + request->system_language_code_ = "en"; + request->device_model_ = "Desktop"; + request->application_version_ = "1.0"; + request->enable_storage_optimizer_ = true; + send_query(std::move(request), create_authentication_query_handler()); + })); } void check_authentication_error(Object object) { diff --git a/example/csharp/TdExample.cs b/example/csharp/TdExample.cs index 405def5e1..0c07ee7c7 100644 --- a/example/csharp/TdExample.cs +++ b/example/csharp/TdExample.cs @@ -67,28 +67,34 @@ namespace TdExample } if (_authorizationState is TdApi.AuthorizationStateWaitTdlibParameters) { - TdApi.TdlibParameters parameters = new TdApi.TdlibParameters(); - parameters.DatabaseDirectory = "tdlib"; - parameters.UseMessageDatabase = true; - parameters.UseSecretChats = true; - parameters.ApiId = 94575; - parameters.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; - parameters.SystemLanguageCode = "en"; - parameters.DeviceModel = "Desktop"; - parameters.ApplicationVersion = "1.0"; - parameters.EnableStorageOptimizer = true; + TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters(); + request.DatabaseDirectory = "tdlib"; + request.UseMessageDatabase = true; + request.UseSecretChats = true; + request.ApiId = 94575; + request.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; + request.SystemLanguageCode = "en"; + request.DeviceModel = "Desktop"; + request.ApplicationVersion = "1.0"; + request.EnableStorageOptimizer = true; - _client.Send(new TdApi.SetTdlibParameters(parameters), new AuthorizationRequestHandler()); - } - else if (_authorizationState is TdApi.AuthorizationStateWaitEncryptionKey) - { - _client.Send(new TdApi.CheckDatabaseEncryptionKey(), new AuthorizationRequestHandler()); + _client.Send(request, new AuthorizationRequestHandler()); } else if (_authorizationState is TdApi.AuthorizationStateWaitPhoneNumber) { string phoneNumber = ReadLine("Please enter phone number: "); _client.Send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler()); } + else if (_authorizationState is TdApi.AuthorizationStateWaitEmailAddress) + { + string emailAddress = ReadLine("Please enter email address: "); + _client.Send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler()); + } + else if (_authorizationState is TdApi.AuthorizationStateWaitEmailCode) + { + string code = ReadLine("Please enter email authentication code: "); + _client.Send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler()); + } else if (_authorizationState is TdApi.AuthorizationStateWaitOtherDeviceConfirmation state) { Console.WriteLine("Please confirm this login link on another device: " + state.Link); diff --git a/example/java/org/drinkless/tdlib/Client.java b/example/java/org/drinkless/tdlib/Client.java index e9cf5e180..0a58bb45a 100644 --- a/example/java/org/drinkless/tdlib/Client.java +++ b/example/java/org/drinkless/tdlib/Client.java @@ -13,6 +13,14 @@ import java.util.concurrent.atomic.AtomicLong; * Main class for interaction with the TDLib. */ public final class Client { + static { + try { + System.loadLibrary("tdjni"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } + } + /** * Interface for handler for results of queries to TDLib and incoming updates from TDLib. */ diff --git a/example/java/org/drinkless/tdlib/example/Example.java b/example/java/org/drinkless/tdlib/example/Example.java index 486470f6e..7e76b4227 100644 --- a/example/java/org/drinkless/tdlib/example/Example.java +++ b/example/java/org/drinkless/tdlib/example/Example.java @@ -55,14 +55,6 @@ public final class Example { private static final String commandsLine = "Enter command (gcs - GetChats, gc - GetChat, me - GetMe, sm - SendMessage, lo - LogOut, q - Quit): "; private static volatile String currentPrompt = null; - static { - try { - System.loadLibrary("tdjni"); - } catch (UnsatisfiedLinkError e) { - e.printStackTrace(); - } - } - private static void print(String str) { if (currentPrompt != null) { System.out.println(""); @@ -101,21 +93,18 @@ public final class Example { } switch (Example.authorizationState.getConstructor()) { case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR: - TdApi.TdlibParameters parameters = new TdApi.TdlibParameters(); - parameters.databaseDirectory = "tdlib"; - parameters.useMessageDatabase = true; - parameters.useSecretChats = true; - parameters.apiId = 94575; - parameters.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; - parameters.systemLanguageCode = "en"; - parameters.deviceModel = "Desktop"; - parameters.applicationVersion = "1.0"; - parameters.enableStorageOptimizer = true; + TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters(); + request.databaseDirectory = "tdlib"; + request.useMessageDatabase = true; + request.useSecretChats = true; + request.apiId = 94575; + request.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; + request.systemLanguageCode = "en"; + request.deviceModel = "Desktop"; + request.applicationVersion = "1.0"; + request.enableStorageOptimizer = true; - client.send(new TdApi.SetTdlibParameters(parameters), new AuthorizationRequestHandler()); - break; - case TdApi.AuthorizationStateWaitEncryptionKey.CONSTRUCTOR: - client.send(new TdApi.CheckDatabaseEncryptionKey(), new AuthorizationRequestHandler()); + client.send(request, new AuthorizationRequestHandler()); break; case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: { String phoneNumber = promptString("Please enter phone number: "); @@ -127,6 +116,16 @@ public final class Example { System.out.println("Please confirm this login link on another device: " + link); break; } + case TdApi.AuthorizationStateWaitEmailAddress.CONSTRUCTOR: { + String emailAddress = promptString("Please enter email address: "); + client.send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler()); + break; + } + case TdApi.AuthorizationStateWaitEmailCode.CONSTRUCTOR: { + String code = promptString("Please enter email authentication code: "); + client.send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler()); + break; + } case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: { String code = promptString("Please enter authentication code: "); client.send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler()); diff --git a/example/python/tdjson_example.py b/example/python/tdjson_example.py index 17efd1f42..ee8a92a15 100644 --- a/example/python/tdjson_example.py +++ b/example/python/tdjson_example.py @@ -94,26 +94,33 @@ while True: # you MUST obtain your own api_id and api_hash at https://my.telegram.org # and use them in the setTdlibParameters call if auth_state['@type'] == 'authorizationStateWaitTdlibParameters': - td_send({'@type': 'setTdlibParameters', 'parameters': { - 'database_directory': 'tdlib', - 'use_message_database': True, - 'use_secret_chats': True, - 'api_id': 94575, - 'api_hash': 'a3406de8d171bb422bb6ddf3bbd800e2', - 'system_language_code': 'en', - 'device_model': 'Desktop', - 'application_version': '1.0', - 'enable_storage_optimizer': True}}) - - # set an encryption key for database to let know TDLib how to open the database - if auth_state['@type'] == 'authorizationStateWaitEncryptionKey': - td_send({'@type': 'checkDatabaseEncryptionKey', 'encryption_key': ''}) + td_send({'@type': 'setTdlibParameters', + 'database_directory': 'tdlib', + 'use_message_database': True, + 'use_secret_chats': True, + 'api_id': 94575, + 'api_hash': 'a3406de8d171bb422bb6ddf3bbd800e2', + 'system_language_code': 'en', + 'device_model': 'Desktop', + 'application_version': '1.0', + 'enable_storage_optimizer': True}) # enter phone number to log in if auth_state['@type'] == 'authorizationStateWaitPhoneNumber': phone_number = input('Please enter your phone number: ') td_send({'@type': 'setAuthenticationPhoneNumber', 'phone_number': phone_number}) + # enter email address to log in + if auth_state['@type'] == 'authorizationStateWaitEmailAddress': + email_address = input('Please enter your email address: ') + td_send({'@type': 'setAuthenticationEmailAddress', 'email_address': email_address}) + + # wait for email authorization code + if auth_state['@type'] == 'authorizationStateWaitEmailCode': + code = input('Please enter the email authentication code you received: ') + td_send({'@type': 'checkAuthenticationEmailCode', + 'code': {'@type': 'emailAddressAuthenticationCode', 'code' : 'code'}}) + # wait for authorization code if auth_state['@type'] == 'authorizationStateWaitCode': code = input('Please enter the authentication code you received: ') diff --git a/example/swift/src/main.swift b/example/swift/src/main.swift index c0c6c088a..9e3513395 100644 --- a/example/swift/src/main.swift +++ b/example/swift/src/main.swift @@ -107,27 +107,33 @@ func updateAuthorizationState(authorizationState: Dictionary) { case "authorizationStateWaitTdlibParameters": client.queryAsync(query:[ "@type":"setTdlibParameters", - "parameters":[ - "database_directory":"tdlib", - "use_message_database":true, - "use_secret_chats":true, - "api_id":94575, - "api_hash":"a3406de8d171bb422bb6ddf3bbd800e2", - "system_language_code":"en", - "device_model":"Desktop", - "application_version":"1.0", - "enable_storage_optimizer":true - ] + "database_directory":"tdlib", + "use_message_database":true, + "use_secret_chats":true, + "api_id":94575, + "api_hash":"a3406de8d171bb422bb6ddf3bbd800e2", + "system_language_code":"en", + "device_model":"Desktop", + "application_version":"1.0", + "enable_storage_optimizer":true ]); - case "authorizationStateWaitEncryptionKey": - client.queryAsync(query: ["@type":"checkDatabaseEncryptionKey", "encryption_key":""]) - case "authorizationStateWaitPhoneNumber": print("Enter your phone number: ") let phone_number = myReadLine() client.queryAsync(query:["@type":"setAuthenticationPhoneNumber", "phone_number":phone_number], f:checkAuthenticationError) + case "authorizationStateWaitEmailAddress": + print("Enter your email address: ") + let email_address = myReadLine() + client.queryAsync(query:["@type":"setAuthenticationEmailAddress", "email_address":email_address], f:checkAuthenticationError) + + case "authorizationStateWaitEmailCode": + var code: String = "" + print("Enter email code: ") + code = myReadLine() + client.queryAsync(query:["@type":"checkAuthenticationEmailCode", "code":["@type":"emailAddressAuthenticationCode", "code":code]], f:checkAuthenticationError) + case "authorizationStateWaitCode": var code: String = "" print("Enter (SMS) code: ") diff --git a/example/uwp/app/MainPage.xaml.cs b/example/uwp/app/MainPage.xaml.cs index 068bf8a46..4c42e2767 100644 --- a/example/uwp/app/MainPage.xaml.cs +++ b/example/uwp/app/MainPage.xaml.cs @@ -36,17 +36,16 @@ namespace TdApp }); _client = Td.Client.Create(_handler); - var parameters = new TdApi.TdlibParameters(); - parameters.DatabaseDirectory = Windows.Storage.ApplicationData.Current.LocalFolder.Path; - parameters.UseSecretChats = true; - parameters.UseMessageDatabase = true; - parameters.ApiId = 94575; - parameters.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; - parameters.SystemLanguageCode = "en"; - parameters.DeviceModel = "Desktop"; - parameters.ApplicationVersion = "1.0.0"; - _client.Send(new TdApi.SetTdlibParameters(parameters), null); - _client.Send(new TdApi.CheckDatabaseEncryptionKey(), null); + var request = new TdApi.SetTdlibParameters(); + request.DatabaseDirectory = Windows.Storage.ApplicationData.Current.LocalFolder.Path; + request.UseSecretChats = true; + request.UseMessageDatabase = true; + request.ApiId = 94575; + request.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; + request.SystemLanguageCode = "en"; + request.DeviceModel = "Desktop"; + request.ApplicationVersion = "1.0.0"; + _client.Send(request, null); } public void Print(String str) @@ -97,6 +96,18 @@ namespace TdApp AcceptCommand(command); _client.Send(new TdApi.SetAuthenticationPhoneNumber(args[1], null), _handler); } + else if (command.StartsWith("sae")) + { + var args = command.Split(" ".ToCharArray(), 2); + AcceptCommand(command); + _client.Send(new TdApi.SetAuthenticationEmailAddress(args[1]), _handler); + } + else if (command.StartsWith("caec")) + { + var args = command.Split(" ".ToCharArray(), 2); + AcceptCommand(command); + _client.Send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(args[1])), _handler); + } else if (command.StartsWith("cac")) { var args = command.Split(" ".ToCharArray(), 2); diff --git a/example/uwp/extension.vsixmanifest b/example/uwp/extension.vsixmanifest index ff7fe7adf..e5deb134e 100644 --- a/example/uwp/extension.vsixmanifest +++ b/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/example/web/tdweb/src/worker.js b/example/web/tdweb/src/worker.js index abc087c44..dff184512 100644 --- a/example/web/tdweb/src/worker.js +++ b/example/web/tdweb/src/worker.js @@ -734,14 +734,14 @@ class TdClient { prepareQuery(query) { if (query['@type'] === 'setTdlibParameters') { - query.parameters.database_directory = this.tdfs.dbFileSystem.root; - query.parameters.files_directory = this.tdfs.inboundFileSystem.root; + query.database_directory = this.tdfs.dbFileSystem.root; + query.files_directory = this.tdfs.inboundFileSystem.root; const useDb = this.useDatabase; - query.parameters.use_file_database = useDb; - query.parameters.use_chat_info_database = useDb; - query.parameters.use_message_database = useDb; - query.parameters.use_secret_chats = useDb; + query.use_file_database = useDb; + query.use_chat_info_database = useDb; + query.use_message_database = useDb; + query.use_secret_chats = useDb; } if (query['@type'] === 'getLanguagePackString') { query.language_pack_database_path = diff --git a/td/generate/JavadocTlDocumentationGenerator.php b/td/generate/JavadocTlDocumentationGenerator.php index 922cf2151..e9cd2d3e6 100644 --- a/td/generate/JavadocTlDocumentationGenerator.php +++ b/td/generate/JavadocTlDocumentationGenerator.php @@ -141,18 +141,29 @@ EOT * This class is a base class for all TDLib interface classes. */ EOT +); + + $this->addDocumentation(" public Object() {", <<addDocumentation(' public abstract int getConstructor();', <<addDocumentation(' public native String toString();', <<addDocumentation(" public Function() {", <<addDocumentation(' public static final int CONSTRUCTOR', <<addDocumentation(" public $class_name() {", << = Files; + //@class InputFile @description Points to a file @@ -480,14 +484,20 @@ chatPermissions can_send_messages:Bool can_send_media_messages:Bool can_send_pol chatAdministratorRights can_manage_chat:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_promote_members:Bool can_manage_video_chats:Bool is_anonymous:Bool = ChatAdministratorRights; -//@description Describes an option for gifting Telegram Premium to a user +//@description Describes an option for buying Telegram Premium to a user //@currency ISO 4217 currency code for Telegram Premium subscription payment //@amount The amount to pay, in the smallest units of the currency -//@discount_percentage The discount associated with this gift option, as a percentage +//@discount_percentage The discount associated with this option, as a percentage //@month_count Number of month the Telegram Premium subscription will be active //@store_product_id Identifier of the store product associated with the option -//@payment_link An internal link to be opened for gifting Telegram Premium to the user if store payment isn't possible; may be null if direct payment isn't available -premiumGiftOption currency:string amount:int53 discount_percentage:int32 month_count:int32 store_product_id:string payment_link:InternalLinkType = PremiumGiftOption; +//@payment_link An internal link to be opened for buying Telegram Premium to the user if store payment isn't possible; may be null if direct payment isn't available +premiumPaymentOption currency:string amount:int53 discount_percentage:int32 month_count:int32 store_product_id:string payment_link:InternalLinkType = PremiumPaymentOption; + +//@description Describes a custom emoji to be shown instead of the Telegram Premium badge @custom_emoji_id Identifier of the custom emoji in stickerFormatTgs format. If the custom emoji belongs to the sticker set GetOption("themed_emoji_statuses_sticker_set_id"), then it's color must be changed to the color of the Telegram Premium badge +emojiStatus custom_emoji_id:int64 = EmojiStatus; + +//@description Contains a list of emoji statuses @emoji_statuses The list of emoji statuses +emojiStatuses emoji_statuses:vector = EmojiStatuses; //@description Represents a user @@ -498,6 +508,7 @@ premiumGiftOption currency:string amount:int53 discount_percentage:int32 month_c //@phone_number Phone number of the user //@status Current online status of the user //@profile_photo Profile photo of the user; may be null +//@emoji_status Emoji status to be shown instead of the default Telegram Premium badge; may be null. For Telegram Premium users only //@is_contact The user is a contact of the current user //@is_mutual_contact The user is a contact of the current user and the current user is a contact of the user //@is_verified True, if the user is verified @@ -510,7 +521,7 @@ premiumGiftOption currency:string amount:int53 discount_percentage:int32 month_c //@type Type of the user //@language_code IETF language tag of the user's language; only available to bots //@added_to_attachment_menu True, if the user added the current bot to attachment menu; only available to bots -user id:int53 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; +user id:int53 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; //@description Contains information about a bot @@ -537,7 +548,7 @@ botInfo share_text:string description:string photo:photo animation:animation men //@premium_gift_options The list of available options for gifting Telegram Premium to the user //@group_in_common_count Number of group chats where both the other user and the current user are a member; 0 for the current user //@bot_info For bots, information about the bot; may be null -userFullInfo photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool need_phone_number_privacy_exception:Bool bio:formattedText premium_gift_options:vector group_in_common_count:int32 bot_info:botInfo = UserFullInfo; +userFullInfo photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool need_phone_number_privacy_exception:Bool bio:formattedText premium_gift_options:vector group_in_common_count:int32 bot_info:botInfo = UserFullInfo; //@description Represents a list of users @total_count Approximate total number of users found @user_ids A list of user identifiers users total_count:int32 user_ids:vector = Users; @@ -821,6 +832,15 @@ messageForwardOriginChannel chat_id:int53 message_id:int53 author_signature:stri messageForwardOriginMessageImport sender_name:string = MessageForwardOrigin; +//@class ReactionType @description Describes type of message reaction + +//@description A reaction with an emoji @emoji Text representation of the reaction +reactionTypeEmoji emoji:string = ReactionType; + +//@description A reaction with a custom emoji @custom_emoji_id Unique identifier of the custom emoji +reactionTypeCustomEmoji custom_emoji_id:int64 = ReactionType; + + //@description Contains information about a forwarded message //@origin Origin of a forwarded message //@date Point in time (Unix timestamp) when the message was originally sent @@ -838,11 +858,11 @@ messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announc messageReplyInfo reply_count:int32 recent_replier_ids:vector 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 +//@type Type 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, basic group and supergroup chats -messageReaction reaction:string total_count:int32 is_chosen:Bool recent_sender_ids:vector = MessageReaction; +messageReaction type:ReactionType total_count:int32 is_chosen:Bool recent_sender_ids:vector = MessageReaction; //@description Contains information about interactions with a message //@view_count Number of times the message was viewed @@ -852,10 +872,10 @@ messageReaction reaction:string total_count:int32 is_chosen:Bool recent_sender_i messageInteractionInfo view_count:int32 forward_count:int32 reply_info:messageReplyInfo reactions:vector = MessageInteractionInfo; //@description Contains information about an unread reaction to a message -//@reaction Text representation of the reaction +//@type Type 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; +unreadReaction type:ReactionType sender_id:MessageSender is_big:Bool = UnreadReaction; //@class MessageSendingState @description Contains information about the sending state of the message @@ -888,6 +908,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool n //@can_get_message_thread True, if information about the message thread is available through getMessageThread and getMessageThreadHistory //@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 through getMessageLink +//@can_report_reactions True, if reactions on the message can be reported through reportMessageReactions //@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 @@ -907,7 +928,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_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 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 can_report_reactions: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 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 number of messages found @messages List of messages; messages may be null messages total_count:int32 messages:vector = Messages; @@ -1072,6 +1093,15 @@ chatSourcePublicServiceAnnouncement type:string text:string = ChatSource; chatPosition list:ChatList order:int64 is_pinned:Bool source:ChatSource = ChatPosition; +//@class ChatAvailableReactions @description Describes reactions available in the chat + +//@description All reactions are available in the chat +chatAvailableReactionsAll = ChatAvailableReactions; + +//@description Only specific reactions are available in the chat @reactions The list of reactions +chatAvailableReactionsSome reactions:vector = ChatAvailableReactions; + + //@description Describes a video chat //@group_call_id Group call identifier of an active video chat; 0 if none. Full information about the video chat can be received through the method getGroupCall //@has_participants True, if the video chat has participants @@ -1102,7 +1132,7 @@ videoChat 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 //@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 +//@available_reactions Types of reaction, 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 @@ -1111,7 +1141,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 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 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 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:ChatAvailableReactions 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 number of chats found @chat_ids List of chat identifiers chats total_count:int32 chat_ids:vector = Chats; @@ -2135,8 +2165,9 @@ messageSchedulingStateSendWhenOnline = MessageSchedulingState; //@disable_notification Pass true to disable notification for the message //@from_background Pass true if the message is sent from the background //@protect_content Pass true if the content of the message must be protected from forwarding and saving; for bots only +//@update_order_of_installed_sticker_sets Pass true if the user explicitly chosen a sticker or a custom emoji from an installed sticker set; applicable only to sendMessage and sendMessageAlbum //@scheduling_state Message scheduling state; pass null to send message immediately. Messages sent to a secret chat, live location messages and self-destructing messages can't be scheduled -messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool scheduling_state:MessageSchedulingState = MessageSendOptions; +messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState = MessageSendOptions; //@description Options to be used when a message content is copied without reference to the original sender. Service messages and messageInvoice can't be copied //@send_copy True, if content of the message needs to be copied without reference to the original sender. Always true if the message is forwarded to a secret chat or is local @@ -2560,24 +2591,26 @@ 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 = 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 reaction applied to a message @type Type of the reaction @sender_id Identifier of the chat member, applied the reaction +addedReaction type:ReactionType sender_id:MessageSender = AddedReaction; //@description Represents a list of reactions added to a message @total_count The total number 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 next_offset:string = AddedReactions; -//@description Represents an available reaction @reaction Text representation of the reaction @needs_premium True, if Telegram Premium is needed to send the reaction -availableReaction reaction:string needs_premium:Bool = AvailableReaction; +//@description Represents an available reaction @type Type of the reaction @needs_premium True, if Telegram Premium is needed to send the reaction +availableReaction type:ReactionType needs_premium:Bool = AvailableReaction; -//@description Represents a list of available reactions @reactions List of reactions -availableReactions reactions:vector = AvailableReactions; +//@description Represents a list of reactions that can be added to a message +//@top_reactions List of reactions to be shown at the top +//@recent_reactions List of recently used reactions +//@popular_reactions List of popular reactions +//@allow_custom_emoji True, if custom emoji reactions could be added by Telegram Premium subscribers +availableReactions top_reactions:vector recent_reactions:vector popular_reactions:vector allow_custom_emoji:Bool = AvailableReactions; - -//@description Contains stickers which must be used for reaction animation rendering -//@reaction Text representation of the reaction +//@description Contains information about a emoji reaction +//@emoji Text representation of the reaction //@title Reaction title //@is_active True, if the reaction can be added to new messages and enabled in chats -//@is_premium True, if the reaction is available only for Premium users //@static_icon Static icon for the reaction //@appear_animation Appear animation for the reaction //@select_animation Select animation for the reaction @@ -2585,7 +2618,7 @@ availableReactions reactions:vector = AvailableReactions; //@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 is_premium:Bool static_icon:sticker appear_animation:sticker select_animation:sticker activate_animation:sticker effect_animation:sticker around_animation:sticker center_animation:sticker = Reaction; +emojiReaction emoji: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 = EmojiReaction; //@description Represents a list of animations @animations List of animations @@ -2839,7 +2872,7 @@ chatEventMemberPromoted user_id:int53 old_status:ChatMemberStatus new_status:Cha chatEventMemberRestricted member_id:MessageSender old_status:ChatMemberStatus new_status:ChatMemberStatus = 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 new_available_reactions:vector = ChatEventAction; +chatEventAvailableReactionsChanged old_available_reactions:ChatAvailableReactions new_available_reactions:ChatAvailableReactions = 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; @@ -3028,6 +3061,9 @@ premiumFeatureAdvancedChatManagement = PremiumFeature; //@description A badge in the user's profile premiumFeatureProfileBadge = PremiumFeature; +//@description A emoji status shown along with the user's name +premiumFeatureEmojiStatus = PremiumFeature; + //@description Profile photo animation on message and chat screens premiumFeatureAnimatedProfilePhoto = PremiumFeature; @@ -3063,10 +3099,9 @@ premiumFeaturePromotionAnimation feature:PremiumFeature animation:animation = Pr //@description Contains state of Telegram Premium subscription and promotion videos for Premium features //@state Text description of the state of the current Premium subscription; may be empty if the current user has no Telegram Premium subscription -//@currency ISO 4217 currency code for Telegram Premium subscription payment -//@monthly_amount Monthly subscription payment for Telegram Premium subscription, in the smallest units of the currency +//@payment_options The list of available options for buying Telegram Premium //@animations The list of available promotion animations for Premium features -premiumState state:formattedText currency:string monthly_amount:int53 animations:vector = PremiumState; +premiumState state:formattedText payment_options:vector animations:vector = PremiumState; //@class StorePaymentPurpose @description Describes a purpose of an in-store payment @@ -3704,6 +3739,9 @@ internalLinkTypeFilterSettings = InternalLinkType; //@bot_username Username of the bot that owns the game @game_short_name Short name of the game internalLinkTypeGame bot_username:string game_short_name:string = InternalLinkType; +//@description The link must be opened in an Instant View. Call getWebPageInstantView with the given URL to process the link @url URL to be passed to getWebPageInstantView +internalLinkTypeInstantView url:string = InternalLinkType; + //@description The link is a link to an invoice. Call getPaymentForm with the given invoice name to process the link @invoice_name Name of the invoice internalLinkTypeInvoice invoice_name:string = InternalLinkType; @@ -4259,8 +4297,8 @@ 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 = Update; +//@description The chat available reactions were changed @chat_id Chat identifier @available_reactions The new reactions, available in the chat +updateChatAvailableReactions chat_id:int53 available_reactions:ChatAvailableReactions = 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 = Update; @@ -4484,8 +4522,11 @@ updateAttachmentMenuBots bots:vector = Update; //@description A message was sent by an opened Web App, so the Web App needs to be closed @web_app_launch_id Identifier of Web App launch updateWebAppMessageSent web_app_launch_id:int64 = Update; -//@description The list of supported reactions has changed @reactions The new list of supported reactions -updateReactions reactions:vector = Update; +//@description The list of active emoji reactions has changed @emojis The new list of active emoji reactions +updateActiveEmojiReactions emojis:vector = Update; + +//@description The type of default reaction has changed @reaction_type The new type of the default reaction +updateDefaultReactionType reaction_type:ReactionType = Update; //@description The list of supported dice emojis has changed @emojis The new list of supported dice emojis updateDiceEmojis emojis:vector = Update; @@ -4596,20 +4637,39 @@ testVectorStringObject value:vector = TestVectorStringObject; getAuthorizationState = AuthorizationState; -//@description Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters @parameters Parameters for TDLib initialization -setTdlibParameters parameters:tdlibParameters = Ok; - -//@description Checks the database encryption key for correctness. Works only when the current authorization state is authorizationStateWaitEncryptionKey @encryption_key Encryption key to check or set up -checkDatabaseEncryptionKey encryption_key:bytes = Ok; +//@description Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters +//@use_test_dc Pass true to use Telegram test environment instead of the production environment +//@database_directory The path to the directory for the persistent database; if empty, the current working directory will be used +//@files_directory The path to the directory for storing files; if empty, database_directory will be used +//@database_encryption_key Encryption key for the database +//@use_file_database Pass true to keep information about downloaded and uploaded files between application restarts +//@use_chat_info_database Pass true to keep cache of users, basic groups, supergroups, channels and secret chats between restarts. Implies use_file_database +//@use_message_database Pass true to keep cache of chats and messages between restarts. Implies use_chat_info_database +//@use_secret_chats Pass true to enable support for secret chats +//@api_id Application identifier for Telegram API access, which can be obtained at https://my.telegram.org +//@api_hash Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org +//@system_language_code IETF language tag of the user's operating system language; must be non-empty +//@device_model Model of the device the application is being run on; must be non-empty +//@system_version Version of the operating system the application is being run on. If empty, the version is automatically detected by TDLib +//@application_version Application version; must be non-empty +//@enable_storage_optimizer Pass true to automatically delete old files in background +//@ignore_file_names Pass true to ignore original file names for downloaded files. Otherwise, downloaded files are saved under names as close as possible to the original name +setTdlibParameters use_test_dc:Bool database_directory:string files_directory:string database_encryption_key:bytes use_file_database:Bool use_chat_info_database:Bool use_message_database:Bool use_secret_chats:Bool api_id:int32 api_hash:string system_language_code:string device_model:string system_version:string application_version:string enable_storage_optimizer:Bool ignore_file_names:Bool = Ok; //@description Sets the phone number of the user and sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitPhoneNumber, //-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword //@phone_number The phone number of the user, in international format @settings Settings for the authentication of the user's phone number; pass null to use default settings setAuthenticationPhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = Ok; -//@description Re-sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitCode, the next_code_type of the result is not null and the server-specified timeout has passed +//@description Sets the email address of the user and sends an authentication code to the email address. Works only when the current authorization state is authorizationStateWaitEmailAddress @email_address The email address of the user +setAuthenticationEmailAddress email_address:string = Ok; + +//@description Resends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitCode, the next_code_type of the result is not null and the server-specified timeout has passed, or when the current authorization state is authorizationStateWaitEmailCode resendAuthenticationCode = Ok; +//@description Checks the authentication of a email address. Works only when the current authorization state is authorizationStateWaitEmailCode @code Email address authentication to check +checkAuthenticationEmailCode code:EmailAddressAuthentication = Ok; + //@description Checks the authentication code. Works only when the current authorization state is authorizationStateWaitCode @code Authentication code to check checkAuthenticationCode code:string = Ok; @@ -4667,6 +4727,15 @@ getPasswordState = PasswordState; //@old_password Previous 2-step verification password of the user @new_password New 2-step verification password of the user; may be empty to remove the password @new_hint New password hint; may be empty @set_recovery_email_address Pass true to change also the recovery email address @new_recovery_email_address New recovery email address; may be empty setPassword old_password:string new_password:string new_hint:string set_recovery_email_address:Bool new_recovery_email_address:string = PasswordState; +//@description Changes the login email address of the user. The change will not be applied until the new login email address is confirmed with `checkLoginEmailAddressCode`. To use Apple ID/Google ID instead of a email address, call `checkLoginEmailAddressCode` directly @new_login_email_address New login email address +setLoginEmailAddress new_login_email_address:string = EmailAddressAuthenticationCodeInfo; + +//@description Resends the login email address verification code +resendLoginEmailAddressCode = EmailAddressAuthenticationCodeInfo; + +//@description Checks the login email address authentication @code Email address authentication to check +checkLoginEmailAddressCode code:EmailAddressAuthentication = Ok; + //@description Returns a 2-step verification recovery email address that was previously set up. This method can be used to verify a password provided by the user @password The 2-step verification password for the current user getRecoveryEmailAddress password:string = RecoveryEmailAddress; @@ -5128,25 +5197,45 @@ 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 Returns information about a emoji reaction. Returns a 404 error if the reaction is not found @emoji Text representation of the reaction +getEmojiReaction emoji:string = EmojiReaction; -//@description Changes chosen reaction for a message +//@description Returns TGS files with generic animations for custom emoji reactions +getCustomEmojiReactionAnimations = Files; + +//@description Returns reactions, which can be added to a message. The list can change after updateActiveEmojiReactions, 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 -//@reaction Text representation of the new chosen reaction. Can be an empty string or the currently chosen non-big reaction to remove the reaction +//@row_size Number of reaction per row, 5-25 +getMessageAvailableReactions chat_id:int53 message_id:int53 row_size:int32 = AvailableReactions; + +//@description Clears the list of recently used reactions +clearRecentReactions = Ok; + +//@description Adds a reaction to a message. Use getMessageAvailableReactions to receive the list of available reactions for the message +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +//@reaction_type Type of the reaction to add //@is_big Pass true if the reaction is added with a big animation -setMessageReaction chat_id:int53 message_id:int53 reaction:string is_big:Bool = Ok; +//@update_recent_reactions Pass true if the reaction needs to be added to recent reactions +addMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType is_big:Bool update_recent_reactions:Bool = Ok; + +//@description Removes a reaction from a message. A chosen reaction can always be removed +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +//@reaction_type Type of the reaction to remove +removeMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType = 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 +//@reaction_type Type of the reactions to return; pass null to return all added reactions //@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; +getMessageAddedReactions chat_id:int53 message_id:int53 reaction_type:ReactionType offset:string limit:int32 = AddedReactions; + +//@description Changes type of default reaction for the current user @reaction_type New type of the default reaction +setDefaultReactionType reaction_type:ReactionType = Ok; //@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 @@ -5244,7 +5333,8 @@ answerInlineQuery inline_query_id:int64 is_personal:Bool results:vector = 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 Reactions available in the chat. All emoji reactions must be active +setChatAvailableReactions chat_id:int53 available_reactions:ChatAvailableReactions = 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; @@ -5567,6 +5658,19 @@ getAttachmentMenuBot bot_user_id:int53 = AttachmentMenuBot; toggleBotIsAddedToAttachmentMenu bot_user_id:int53 is_added:Bool = Ok; +//@description Returns up to 8 themed emoji statuses, which color must be changed to the color of the Telegram Premium badge +getThemedEmojiStatuses = EmojiStatuses; + +//@description Returns recent emoji statuses +getRecentEmojiStatuses = EmojiStatuses; + +//@description Returns default emoji statuses +getDefaultEmojiStatuses = EmojiStatuses; + +//@description Clears the list of recently used emoji statuses +clearRecentEmojiStatuses = Ok; + + //@description Downloads a file from the cloud. Download progress and completion of the download will be notified through updateFile updates //@file_id Identifier of the file to download //@priority Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile/addFileToDownloads was called will be downloaded first @@ -6073,6 +6177,11 @@ setBio bio:string = Ok; //@description Changes the username of the current user @username The new value of the username. Use an empty string to remove the username setUsername username:string = Ok; +//@description Changes the emoji status of the current user; for Telegram Premium users only +//@emoji_status New emoji status; pass null to switch to the default badge +//@duration Duration of the status, in seconds; pass 0 to keep the status active until it will be changed manually +setEmojiStatus emoji_status:emojiStatus duration:int32 = Ok; + //@description Changes the location of the current user. Needs to be called if GetOption("is_location_visible") is true and location changes for more than 1 kilometer @location The new location of the user setLocation location:location = Ok; @@ -6080,7 +6189,7 @@ setLocation location:location = Ok; //@phone_number The new phone number of the user in international format @settings Settings for the authentication of the user's phone number; pass null to use default settings changePhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo; -//@description Re-sends the authentication code sent to confirm a new phone number for the current user. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed +//@description Resends the authentication code sent to confirm a new phone number for the current user. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed resendChangePhoneNumberCode = AuthenticationCodeInfo; //@description Checks the authentication code sent to confirm a new phone number of the user @code Authentication code to check @@ -6325,6 +6434,11 @@ reportChat chat_id:int53 message_ids:vector reason:ChatReportReason text: //@chat_id Chat identifier @file_id Identifier of the photo to report. Only full photos from chatPhoto can be reported @reason The reason for reporting the chat photo @text Additional report details; 0-1024 characters reportChatPhoto chat_id:int53 file_id:int32 reason:ChatReportReason text:string = Ok; +//@description Reports reactions set on a message to the Telegram moderators. Reactions on a message can be reported only if message.can_report_reactions +//@chat_id Chat identifier @message_id Message identifier @sender_id Identifier of the sender, which added the reaction +reportMessageReactions chat_id:int53 message_id:int53 sender_id:MessageSender = Ok; + + //@description Returns detailed statistics about a chat. Currently, this method can be used only for supergroups and channels. Can be used only if supergroupFullInfo.can_get_statistics == true @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the application getChatStatistics chat_id:int53 is_dark:Bool = ChatStatistics; @@ -6409,7 +6523,7 @@ getPreferredCountryLanguage country_code:string = Text; //@phone_number The phone number of the user, in international format @settings Settings for the authentication of the user's phone number; pass null to use default settings sendPhoneNumberVerificationCode phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo; -//@description Re-sends the code to verify a phone number to be added to a user's Telegram Passport +//@description Resends the code to verify a phone number to be added to a user's Telegram Passport resendPhoneNumberVerificationCode = AuthenticationCodeInfo; //@description Checks the phone number verification code for Telegram Passport @code Verification code to check @@ -6419,7 +6533,7 @@ checkPhoneNumberVerificationCode code:string = Ok; //@description Sends a code to verify an email address to be added to a user's Telegram Passport @email_address Email address sendEmailAddressVerificationCode email_address:string = EmailAddressAuthenticationCodeInfo; -//@description Re-sends the code to verify an email address to be added to a user's Telegram Passport +//@description Resends the code to verify an email address to be added to a user's Telegram Passport resendEmailAddressVerificationCode = EmailAddressAuthenticationCodeInfo; //@description Checks the email address verification code for Telegram Passport @code Verification code to check diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index 68ad20578..4f17c4aa8 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -101,7 +101,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#5d99adee flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -119,8 +119,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?Vector = ChatFull; -channelFull#ea68a619 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?Vector = ChatFull; +chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; +channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -315,7 +315,7 @@ updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update; updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Update; -updateStickerSets#43ae3dec = Update; +updateStickerSets#31c24808 flags:# masks:flags.0?true emojis:flags.1?true = Update; updateSavedGifs#9375341e = Update; updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update; updateBotInlineSend#12f12a07 flags:# user_id:long query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; @@ -384,6 +384,10 @@ updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; updateSavedRingtones#74d8be99 = Update; updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update; updateReadFeaturedEmojiStickers#fb4c496c = Update; +updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update; +updateRecentEmojiStatuses#30f443db = Update; +updateRecentReactions#6f7863f4 = Update; +updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -410,7 +414,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; +config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -548,7 +552,7 @@ authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true pa account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations; -account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password; +account.password#957b50fb flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int login_email_pattern:flags.6?string = account.Password; account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings; @@ -572,6 +576,8 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet; +inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; +inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; @@ -707,6 +713,8 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType; +auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType; +auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -933,7 +941,7 @@ channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction; channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; -channelAdminLogEventActionChangeAvailableReactions#9cf7f76a prev_value:Vector new_value:Vector = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1318,7 +1326,7 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Ve auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut; -reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount; +reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount; messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactions:flags.1?Vector = MessageReactions; @@ -1332,7 +1340,7 @@ messages.availableReactions#768e3aad hash:int reactions:Vector video_sections:Vector videos:Vector currency:string monthly_amount:long users:Vector = help.PremiumPromo; +help.premiumPromo#5334759c status_text:string status_entities:Vector video_sections:Vector videos:Vector period_options:Vector users:Vector = help.PremiumPromo; inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose; inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; @@ -1394,6 +1402,37 @@ premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_ur paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod; +emojiStatusEmpty#2de11aae = EmojiStatus; +emojiStatus#929b619d document_id:long = EmojiStatus; +emojiStatusUntil#fa30a8c7 document_id:long until:int = EmojiStatus; + +account.emojiStatusesNotModified#d08ce645 = account.EmojiStatuses; +account.emojiStatuses#90c467d1 hash:long statuses:Vector = account.EmojiStatuses; + +reactionEmpty#79f5d419 = Reaction; +reactionEmoji#1b2286b8 emoticon:string = Reaction; +reactionCustomEmoji#8935fc73 document_id:long = Reaction; + +chatReactionsNone#eafc32bc = ChatReactions; +chatReactionsAll#52928bca flags:# allow_custom:flags.0?true = ChatReactions; +chatReactionsSome#661d4037 reactions:Vector = ChatReactions; + +messages.reactionsNotModified#b06fdbdf = messages.Reactions; +messages.reactions#eafdf716 hash:long reactions:Vector = messages.Reactions; + +emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose; +emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose; +emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose; + +emailVerificationCode#922e55a9 code:string = EmailVerification; +emailVerificationGoogle#db909ec2 token:string = EmailVerification; +emailVerificationApple#96d074fd token:string = EmailVerification; + +account.emailVerified#2b96cd1b email:string = account.EmailVerified; +account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified; + +premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1406,7 +1445,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; -auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; +auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string phone_code:flags.0?string email_verification:flags.1?EmailVerification = auth.Authorization; auth.logOut#3e72ba19 = auth.LoggedOut; auth.resetAuthorizations#9fab0d1a = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; @@ -1462,8 +1501,8 @@ account.getAuthorizationForm#a929597a bot_id:long scope:string public_key:string account.acceptAuthorization#f3ed4c73 bot_id:long scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode; account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; -account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; -account.verifyEmail#ecba39db email:string code:string = Bool; +account.sendVerifyEmailCode#98e037bb purpose:EmailVerifyPurpose email:string = account.SentEmailCode; +account.verifyEmail#32da4cf purpose:EmailVerifyPurpose verification:EmailVerification = account.EmailVerified; account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout; account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool; account.confirmPasswordEmail#8fdf1920 code:string = Bool; @@ -1500,6 +1539,10 @@ account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_request account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones; account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone; account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document; +account.updateEmojiStatus#fbd3de6b emoji_status:EmojiStatus = Bool; +account.getDefaultEmojiStatuses#d6753386 hash:long = account.EmojiStatuses; +account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses; +account.clearRecentEmojiStatuses#18201aae = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1536,8 +1579,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; @@ -1617,7 +1660,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -1676,12 +1719,12 @@ 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:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates; +messages.sendReaction#d30d78d4 flags:# big:flags.1?true add_to_recent:flags.2?true peer:InputPeer msg_id:int reaction:flags.0?Vector = Updates; messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = 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 = Updates; +messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList; +messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates; messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; -messages.setDefaultReaction#d960c4d4 reaction:string = Bool; +messages.setDefaultReaction#4f47a016 reaction:Reaction = 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; @@ -1689,9 +1732,9 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool; -messages.requestWebView#91b15831 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult; +messages.requestWebView#fc87a53c flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult; messages.prolongWebView#ea5fbcce flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = Bool; -messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult; +messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; @@ -1699,6 +1742,10 @@ messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_i messages.getCustomEmojiDocuments#d9ab0f54 document_id:Vector = Vector; messages.getEmojiStickers#fbfca18f hash:long = messages.AllStickers; messages.getFeaturedEmojiStickers#ecf6736 hash:long = messages.FeaturedStickers; +messages.reportReaction#3f64c076 peer:InputPeer id:int reaction_peer:InputPeer = Bool; +messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions; +messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions; +messages.clearRecentReactions#9dfeefb4 = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1805,7 +1852,6 @@ payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoi payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates; payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; -payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates; 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 software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/td/generate/tl_writer_java.cpp b/td/generate/tl_writer_java.cpp index e2dba8abb..f20c6a04b 100644 --- a/td/generate/tl_writer_java.cpp +++ b/td/generate/tl_writer_java.cpp @@ -194,7 +194,15 @@ std::string TD_TL_writer_java::gen_output_begin() const { return "package " + package_name + ";\n\n" "public class " + - tl_name + " {\n"; + tl_name + + " {\n" + " static {\n" + " try {\n" + " System.loadLibrary(\"tdjni\");\n" + " } catch (UnsatisfiedLinkError e) {\n" + " e.printStackTrace();\n" + + " }\n" + " }\n\n"; } std::string TD_TL_writer_java::gen_output_end() const { @@ -226,8 +234,11 @@ std::string TD_TL_writer_java::gen_class_begin(const std::string &class_name, co full_class_name += "<" + fetched_type + ">"; } std::string result = " public " + std::string(is_proxy ? "abstract " : "") + full_class_name + " {\n"; + if (is_proxy) { + result += " public " + class_name + "() {\n }\n"; + } if (class_name == gen_base_tl_class_name() || class_name == gen_base_function_class_name()) { - result += " public native String toString();\n"; + result += "\n public native String toString();\n"; } return result; diff --git a/td/mtproto/RawConnection.cpp b/td/mtproto/RawConnection.cpp index a663e6af3..d69211e7b 100644 --- a/td/mtproto/RawConnection.cpp +++ b/td/mtproto/RawConnection.cpp @@ -49,11 +49,13 @@ class RawConnectionDefault final : public RawConnection { bool can_send() const final { return transport_->can_write(); } + TransportType get_transport_type() const final { return transport_->get_type(); } - void send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key, - uint64 quick_ack_token) final { + + size_t send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key, + uint64 quick_ack_token) final { PacketInfo info; info.version = 2; info.no_crypto_flag = false; @@ -76,7 +78,9 @@ class RawConnectionDefault final : public RawConnection { } } + auto packet_size = packet.size(); transport_->write(std::move(packet), use_quick_ack); + return packet_size; } uint64 send_no_crypto(const Storer &storer) final { @@ -250,7 +254,7 @@ class RawConnectionDefault final : public RawConnection { sync_with_poll(socket_fd_); // read/write - // EINVAL may be returned in linux kernel < 2.6.28. And on some new kernels too. + // EINVAL can be returned in Linux kernel < 2.6.28. And on some new kernels too. // just close connection and hope that read or write will not return this error too. TRY_STATUS(socket_fd_.get_pending_error()); @@ -280,11 +284,13 @@ class RawConnectionHttp final : public RawConnection { bool can_send() const final { return mode_ == Send; } + TransportType get_transport_type() const final { return mtproto::TransportType{mtproto::TransportType::Http, 0, mtproto::ProxySecret()}; } - void send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key, - uint64 quick_ack_token) final { + + size_t send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key, + uint64 quick_ack_token) final { PacketInfo info; info.version = 2; info.no_crypto_flag = false; @@ -295,7 +301,9 @@ class RawConnectionHttp final : public RawConnection { auto packet = BufferWriter{Transport::write(storer, auth_key, &info), 0, 0}; Transport::write(storer, auth_key, &info, packet.as_slice()); + auto packet_size = packet.size(); send_packet(packet.as_buffer_slice()); + return packet_size; } uint64 send_no_crypto(const Storer &storer) final { diff --git a/td/mtproto/RawConnection.h b/td/mtproto/RawConnection.h index be63533dd..6582d46e7 100644 --- a/td/mtproto/RawConnection.h +++ b/td/mtproto/RawConnection.h @@ -48,8 +48,8 @@ class RawConnection { virtual bool can_send() const = 0; virtual TransportType get_transport_type() const = 0; - virtual void send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key, - uint64 quick_ack_token) = 0; + virtual size_t send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key, + uint64 quick_ack_token) = 0; virtual uint64 send_no_crypto(const Storer &storer) = 0; virtual PollableFdInfo &get_poll_info() = 0; diff --git a/td/mtproto/SessionConnection.cpp b/td/mtproto/SessionConnection.cpp index d6722f264..bb7b6670c 100644 --- a/td/mtproto/SessionConnection.cpp +++ b/td/mtproto/SessionConnection.cpp @@ -717,14 +717,17 @@ Status SessionConnection::on_quick_ack(uint64 quick_ack_token) { void SessionConnection::on_read(size_t size) { last_read_at_ = Time::now_cached(); + last_read_size_ += size; } SessionConnection::SessionConnection(Mode mode, unique_ptr raw_connection, AuthData *auth_data) - : raw_connection_(std::move(raw_connection)), auth_data_(auth_data) { + : random_delay_(Random::fast(0, 5000000) * 1e-6) + , state_(Init) + , mode_(mode) + , created_at_(Time::now()) + , raw_connection_(std::move(raw_connection)) + , auth_data_(auth_data) { CHECK(raw_connection_); - state_ = Init; - mode_ = mode; - created_at_ = Time::now(); } PollableFdInfo &SessionConnection::get_poll_info() { @@ -765,8 +768,9 @@ void SessionConnection::do_close(Status status) { void SessionConnection::send_crypto(const Storer &storer, uint64 quick_ack_token) { CHECK(state_ != Closed); - raw_connection_->send_crypto(storer, auth_data_->get_session_id(), auth_data_->get_server_salt(Time::now_cached()), - auth_data_->get_auth_key(), quick_ack_token); + last_write_size_ += raw_connection_->send_crypto(storer, auth_data_->get_session_id(), + auth_data_->get_server_salt(Time::now_cached()), + auth_data_->get_auth_key(), quick_ack_token); } Result SessionConnection::send_query(BufferSlice buffer, bool gzip_flag, int64 message_id, @@ -969,10 +973,11 @@ void SessionConnection::flush_packet() { { // LOG(ERROR) << (auth_data_->get_header().empty() ? '-' : '+'); uint64 parent_message_id = 0; - auto storer = PacketStorer( - queries, auth_data_->get_header(), std::move(to_ack), ping_id, ping_disconnect_delay() + 2, max_delay, - max_after, max_wait, future_salt_n, to_get_state_info, to_resend_answer, to_cancel_answer, destroy_auth_key, - auth_data_, &container_id, &get_state_info_id, &resend_answer_id, &ping_message_id, &parent_message_id); + auto storer = PacketStorer(queries, auth_data_->get_header(), std::move(to_ack), ping_id, + static_cast(ping_disconnect_delay() + 2.0), max_delay, max_after, + max_wait, future_salt_n, to_get_state_info, to_resend_answer, + to_cancel_answer, destroy_auth_key, auth_data_, &container_id, + &get_state_info_id, &resend_answer_id, &ping_message_id, &parent_message_id); auto quick_ack_token = use_quick_ack ? parent_message_id : 0; send_crypto(storer, quick_ack_token); @@ -1032,7 +1037,18 @@ Status SessionConnection::do_flush() { return Status::Error("No auth key"); } - TRY_STATUS(raw_connection_->flush(auth_data_->get_auth_key(), *this)); + last_read_size_ = 0; + last_write_size_ = 0; + auto start_time = Time::now(); + auto result = raw_connection_->flush(auth_data_->get_auth_key(), *this); + auto elapsed_time = Time::now() - start_time; + if (elapsed_time >= 0.01) { + LOG(ERROR) << "RawConnection::flush took " << elapsed_time << " seconds, written " << last_write_size_ + << " bytes, read " << last_read_size_ << " bytes and returned " << result; + } + if (result.is_error()) { + return result; + } if (last_pong_at_ + ping_disconnect_delay() < Time::now_cached()) { auto stats_callback = raw_connection_->stats_callback(); diff --git a/td/mtproto/SessionConnection.h b/td/mtproto/SessionConnection.h index 0b2e75555..ead20e1b1 100644 --- a/td/mtproto/SessionConnection.h +++ b/td/mtproto/SessionConnection.h @@ -139,32 +139,31 @@ class SessionConnection final bool is_main_ = false; bool was_moved_ = false; - int rtt() const { - return max(2, static_cast(raw_connection_->extra().rtt * 1.5 + 1)); + double rtt() const { + return max(2.0, raw_connection_->extra().rtt * 1.5 + 1); } - int32 read_disconnect_delay() const { - return online_flag_ ? rtt() * 7 / 2 : 135; + double read_disconnect_delay() const { + return online_flag_ ? rtt() * 3.5 : 135 + random_delay_; } - int32 ping_disconnect_delay() const { - return (online_flag_ && is_main_) ? rtt() * 5 / 2 : 135; + double ping_disconnect_delay() const { + return online_flag_ && is_main_ ? rtt() * 2.5 : 135 + random_delay_; } - int32 ping_may_delay() const { - return online_flag_ ? rtt() / 2 : 30; + double ping_may_delay() const { + return online_flag_ ? rtt() * 0.5 : 30 + random_delay_; } - int32 ping_must_delay() const { - return online_flag_ ? rtt() : 60; + double ping_must_delay() const { + return online_flag_ ? rtt() : 60 + random_delay_; } double http_max_wait() const { return 25.0; // 25s. Longer could be closed by proxy } - static constexpr int HTTP_MAX_AFTER = 10; // 0.01s - static constexpr int HTTP_MAX_DELAY = 30; // 0.03s - static constexpr int TEMP_KEY_TIMEOUT = 60 * 60 * 24; // one day + static constexpr int HTTP_MAX_AFTER = 10; // 0.01s + static constexpr int HTTP_MAX_DELAY = 30; // 0.03s vector to_send_; vector to_ack_; @@ -182,6 +181,7 @@ class SessionConnection final // nobody cleans up this map. But it should be really small. FlatHashMap> container_to_service_msg_; + double random_delay_ = 0; double last_read_at_ = 0; double last_ping_at_ = 0; double last_pong_at_ = 0; @@ -189,6 +189,9 @@ class SessionConnection final uint64 last_ping_message_id_ = 0; uint64 last_ping_container_id_ = 0; + uint64 last_read_size_ = 0; + uint64 last_write_size_ = 0; + bool need_destroy_auth_key_ = false; bool sent_destroy_auth_key_ = false; diff --git a/td/telegram/Account.cpp b/td/telegram/Account.cpp index 2ccccb973..94921c845 100644 --- a/td/telegram/Account.cpp +++ b/td/telegram/Account.cpp @@ -118,7 +118,7 @@ class SetAccountTtlQuery final : public Td::ResultHandler { void send(int32 account_ttl) { send_query(G()->net_query_creator().create( - telegram_api::account_setAccountTTL(make_tl_object(account_ttl)))); + telegram_api::account_setAccountTTL(make_tl_object(account_ttl)), {{"me"}})); } void on_result(BufferSlice packet) final { @@ -315,7 +315,8 @@ class ChangeAuthorizationSettingsQuery final : public Td::ResultHandler { flags |= telegram_api::account_changeAuthorizationSettings::CALL_REQUESTS_DISABLED_MASK; } send_query(G()->net_query_creator().create(telegram_api::account_changeAuthorizationSettings( - flags, hash, encrypted_requests_disabled, call_requests_disabled))); + flags, hash, encrypted_requests_disabled, call_requests_disabled), + {{"me"}})); } void on_result(BufferSlice packet) final { @@ -342,7 +343,8 @@ class SetAuthorizationTtlQuery final : public Td::ResultHandler { } void send(int32 authorization_ttl_days) { - send_query(G()->net_query_creator().create(telegram_api::account_setAuthorizationTTL(authorization_ttl_days))); + send_query( + G()->net_query_creator().create(telegram_api::account_setAuthorizationTTL(authorization_ttl_days), {{"me"}})); } void on_result(BufferSlice packet) final { @@ -472,7 +474,7 @@ class SetBotGroupDefaultAdminRightsQuery final : public Td::ResultHandler { void send(AdministratorRights administrator_rights) { send_query(G()->net_query_creator().create( - telegram_api::bots_setBotGroupDefaultAdminRights(administrator_rights.get_chat_admin_rights()))); + telegram_api::bots_setBotGroupDefaultAdminRights(administrator_rights.get_chat_admin_rights()), {{"me"}})); } void on_result(BufferSlice packet) final { @@ -505,7 +507,7 @@ class SetBotBroadcastDefaultAdminRightsQuery final : public Td::ResultHandler { void send(AdministratorRights administrator_rights) { send_query(G()->net_query_creator().create( - telegram_api::bots_setBotBroadcastDefaultAdminRights(administrator_rights.get_chat_admin_rights()))); + telegram_api::bots_setBotBroadcastDefaultAdminRights(administrator_rights.get_chat_admin_rights()), {{"me"}})); } void on_result(BufferSlice packet) final { diff --git a/td/telegram/AttachMenuManager.cpp b/td/telegram/AttachMenuManager.cpp index 224bd23da..c52ca42c5 100644 --- a/td/telegram/AttachMenuManager.cpp +++ b/td/telegram/AttachMenuManager.cpp @@ -49,8 +49,8 @@ class RequestWebViewQuery final : public Td::ResultHandler { } void send(DialogId dialog_id, UserId bot_user_id, tl_object_ptr &&input_user, string &&url, - td_api::object_ptr &&theme, MessageId reply_to_message_id, bool silent, - DialogId as_dialog_id) { + td_api::object_ptr &&theme, string &&platform, MessageId reply_to_message_id, + bool silent, DialogId as_dialog_id) { dialog_id_ = dialog_id; bot_user_id_ = bot_user_id; reply_to_message_id_ = reply_to_message_id; @@ -104,7 +104,8 @@ class RequestWebViewQuery final : public Td::ResultHandler { send_query(G()->net_query_creator().create(telegram_api::messages_requestWebView( flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(input_user), url, start_parameter, - std::move(theme_parameters), reply_to_message_id.get_server_message_id().get(), std::move(as_input_peer)))); + std::move(theme_parameters), platform, reply_to_message_id.get_server_message_id().get(), + std::move(as_input_peer)))); } void on_result(BufferSlice packet) final { @@ -600,7 +601,7 @@ void AttachMenuManager::schedule_ping_web_view() { void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, MessageId reply_to_message_id, string &&url, td_api::object_ptr &&theme, - Promise> &&promise) { + string &&platform, Promise> &&promise) { TRY_STATUS_PROMISE(promise, td_->contacts_manager_->get_bot_data(bot_user_id)); TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); @@ -634,8 +635,8 @@ void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, DialogId as_dialog_id = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id); td_->create_handler(std::move(promise)) - ->send(dialog_id, bot_user_id, std::move(input_user), std::move(url), std::move(theme), reply_to_message_id, - silent, as_dialog_id); + ->send(dialog_id, bot_user_id, std::move(input_user), std::move(url), std::move(theme), std::move(platform), + reply_to_message_id, silent, as_dialog_id); } void AttachMenuManager::open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, diff --git a/td/telegram/AttachMenuManager.h b/td/telegram/AttachMenuManager.h index 18e0822af..3110aaaa6 100644 --- a/td/telegram/AttachMenuManager.h +++ b/td/telegram/AttachMenuManager.h @@ -33,7 +33,7 @@ class AttachMenuManager final : public Actor { void init(); void request_web_view(DialogId dialog_id, UserId bot_user_id, MessageId reply_to_message_id, string &&url, - td_api::object_ptr &&theme, + td_api::object_ptr &&theme, string &&platform, Promise> &&promise); void open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, MessageId reply_to_message_id, diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index 010a68583..1a80475af 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -107,6 +107,12 @@ tl_object_ptr AuthManager::get_authorization_state_o switch (authorization_state) { case State::WaitPhoneNumber: return make_tl_object(); + case State::WaitEmailAddress: + return make_tl_object(allow_apple_id_, allow_google_id_); + case State::WaitEmailCode: + return make_tl_object( + allow_apple_id_, allow_google_id_, email_code_info_.get_email_address_authentication_code_info_object(), + next_phone_number_login_date_); case State::WaitCode: return send_code_helper_.get_authorization_state_wait_code(); case State::WaitQrCodeConfirmation: @@ -174,7 +180,8 @@ void AuthManager::check_bot_token(uint64 query_id, string bot_token) { void AuthManager::request_qr_code_authentication(uint64 query_id, vector other_user_ids) { if (state_ != State::WaitPhoneNumber) { - if ((state_ == State::WaitCode || state_ == State::WaitPassword || state_ == State::WaitRegistration) && + if ((state_ == State::WaitEmailAddress || state_ == State::WaitEmailCode || state_ == State::WaitCode || + state_ == State::WaitPassword || state_ == State::WaitRegistration) && net_query_id_ == 0) { // ok } else { @@ -239,7 +246,8 @@ void AuthManager::on_update_login_token() { void AuthManager::set_phone_number(uint64 query_id, string phone_number, td_api::object_ptr settings) { if (state_ != State::WaitPhoneNumber) { - if ((state_ == State::WaitCode || state_ == State::WaitPassword || state_ == State::WaitRegistration) && + if ((state_ == State::WaitEmailAddress || state_ == State::WaitEmailCode || state_ == State::WaitCode || + state_ == State::WaitPassword || state_ == State::WaitRegistration) && net_query_id_ == 0) { // ok } else { @@ -251,12 +259,20 @@ void AuthManager::set_phone_number(uint64 query_id, string phone_number, query_id, Status::Error(400, "Cannot set phone number after bot token was entered. You need to log out first")); } if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(400, "Phone number can't be empty")); + return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty")); } other_user_ids_.clear(); was_qr_code_request_ = false; + allow_apple_id_ = false; + allow_google_id_ = false; + email_address_ = {}; + email_code_info_ = {}; + next_phone_number_login_date_ = 0; + code_ = string(); + email_code_ = {}; + if (send_code_helper_.phone_number() != phone_number) { send_code_helper_ = SendCodeHelper(); terms_of_service_ = TermsOfService(); @@ -268,8 +284,35 @@ void AuthManager::set_phone_number(uint64 query_id, string phone_number, std::move(phone_number), settings, api_id_, api_hash_))); } +void AuthManager::set_email_address(uint64 query_id, string email_address) { + if (state_ != State::WaitEmailAddress) { + if (state_ == State::WaitEmailCode && net_query_id_ == 0) { + // ok + } else { + return on_query_error(query_id, Status::Error(400, "Call to setAuthenticationEmailAddress unexpected")); + } + } + if (email_address.empty()) { + return on_query_error(query_id, Status::Error(400, "Email address must be non-empty")); + } + + email_address_ = std::move(email_address); + + on_new_query(query_id); + + start_net_query(NetQueryType::SendEmailCode, + G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_))); +} + void AuthManager::resend_authentication_code(uint64 query_id) { if (state_ != State::WaitCode) { + if (state_ == State::WaitEmailCode) { + on_new_query(query_id); + start_net_query(NetQueryType::SendEmailCode, + G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_))); + return; + } + return on_query_error(query_id, Status::Error(400, "Call to resendAuthenticationCode unexpected")); } @@ -283,16 +326,48 @@ void AuthManager::resend_authentication_code(uint64 query_id) { start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(r_resend_code.move_as_ok())); } +void AuthManager::send_auth_sign_in_query() { + bool is_email = !email_code_.is_empty(); + int32 flags = + is_email ? telegram_api::auth_signIn::EMAIL_VERIFICATION_MASK : telegram_api::auth_signIn::PHONE_CODE_MASK; + start_net_query(NetQueryType::SignIn, + G()->net_query_creator().create_unauth(telegram_api::auth_signIn( + flags, send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code_, + is_email ? email_code_.get_input_email_verification() : nullptr))); +} + +void AuthManager::check_email_code(uint64 query_id, EmailVerification &&code) { + if (code.is_empty()) { + return on_query_error(query_id, Status::Error(400, "Code must be non-empty")); + } + if (state_ != State::WaitEmailCode && !(state_ == State::WaitEmailAddress && code.is_email_code())) { + return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationEmailCode unexpected")); + } + + code_ = string(); + email_code_ = std::move(code); + + on_new_query(query_id); + if (email_address_.empty()) { + send_auth_sign_in_query(); + } else { + start_net_query( + NetQueryType::VerifyEmailAddress, + G()->net_query_creator().create_unauth(telegram_api::account_verifyEmail( + send_code_helper_.get_email_verify_purpose_login_setup(), email_code_.get_input_email_verification()))); + } +} + void AuthManager::check_code(uint64 query_id, string code) { if (state_ != State::WaitCode) { return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationCode unexpected")); } code_ = std::move(code); + email_code_ = {}; + on_new_query(query_id); - start_net_query(NetQueryType::SignIn, - G()->net_query_creator().create_unauth(telegram_api::auth_signIn( - send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code_))); + send_auth_sign_in_query(); } void AuthManager::register_user(uint64 query_id, string first_name, string last_name) { @@ -303,7 +378,7 @@ void AuthManager::register_user(uint64 query_id, string first_name, string last_ on_new_query(query_id); first_name = clean_name(first_name, MAX_NAME_LENGTH); if (first_name.empty()) { - return on_query_error(Status::Error(400, "First name can't be empty")); + return on_query_error(Status::Error(400, "First name must be non-empty")); } last_name = clean_name(last_name, MAX_NAME_LENGTH); @@ -389,7 +464,9 @@ void AuthManager::log_out(uint64 query_id) { } void AuthManager::send_log_out_query() { - auto query = G()->net_query_creator().create(telegram_api::auth_logOut()); + // we can lose authorization while logging out, but still may need to resend the request, + // so we pretend that it doesn't require authorization + auto query = G()->net_query_creator().create_unauth(telegram_api::auth_logOut()); query->set_priority(1); start_net_query(NetQueryType::LogOut, std::move(query)); } @@ -478,6 +555,33 @@ void AuthManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_q G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this)); } +void AuthManager::on_sent_code(telegram_api::object_ptr &&sent_code) { + auto code_type_id = sent_code->type_->get_id(); + if (code_type_id == telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID) { + auto code_type = move_tl_object_as(std::move(sent_code->type_)); + send_code_helper_.on_phone_code_hash(std::move(sent_code->phone_code_hash_)); + allow_apple_id_ = code_type->apple_signin_allowed_; + allow_google_id_ = code_type->google_signin_allowed_; + update_state(State::WaitEmailAddress, true); + } else if (code_type_id == telegram_api::auth_sentCodeTypeEmailCode::ID) { + auto code_type = move_tl_object_as(std::move(sent_code->type_)); + send_code_helper_.on_phone_code_hash(std::move(sent_code->phone_code_hash_)); + allow_apple_id_ = code_type->apple_signin_allowed_; + allow_google_id_ = code_type->google_signin_allowed_; + email_address_.clear(); + email_code_info_ = SentEmailCode(std::move(code_type->email_pattern_), code_type->length_); + next_phone_number_login_date_ = td::max(static_cast(0), code_type->next_phone_login_date_); + if (email_code_info_.is_empty()) { + email_code_info_ = SentEmailCode("", code_type->length_); + CHECK(!email_code_info_.is_empty()); + } + update_state(State::WaitEmailCode, true); + } else { + send_code_helper_.on_sent_code(std::move(sent_code)); + update_state(State::WaitCode, true); + } +} + void AuthManager::on_send_code_result(NetQueryPtr &result) { auto r_sent_code = fetch_result(result->ok()); if (r_sent_code.is_error()) { @@ -486,10 +590,43 @@ void AuthManager::on_send_code_result(NetQueryPtr &result) { auto sent_code = r_sent_code.move_as_ok(); LOG(INFO) << "Receive " << to_string(sent_code); + on_sent_code(std::move(sent_code)); + on_query_ok(); +} - send_code_helper_.on_sent_code(std::move(sent_code)); +void AuthManager::on_send_email_code_result(NetQueryPtr &result) { + auto r_sent_code = fetch_result(result->ok()); + if (r_sent_code.is_error()) { + return on_query_error(r_sent_code.move_as_error()); + } + auto sent_code = r_sent_code.move_as_ok(); - update_state(State::WaitCode, true); + LOG(INFO) << "Receive " << to_string(sent_code); + + email_code_info_ = SentEmailCode(std::move(sent_code)); + if (email_code_info_.is_empty()) { + return on_query_error(Status::Error(500, "Receive invalid response")); + } + next_phone_number_login_date_ = 0; + + update_state(State::WaitEmailCode, true); + on_query_ok(); +} + +void AuthManager::on_verify_email_address_result(NetQueryPtr &result) { + auto r_email_verified = fetch_result(result->ok()); + if (r_email_verified.is_error()) { + return on_query_error(r_email_verified.move_as_error()); + } + auto email_verified = r_email_verified.move_as_ok(); + + LOG(INFO) << "Receive " << to_string(email_verified); + if (email_verified->get_id() != telegram_api::account_emailVerifiedLogin::ID) { + return on_query_error(Status::Error(500, "Receive invalid response")); + } + + auto verified_login = telegram_api::move_object_as(email_verified); + on_sent_code(std::move(verified_login->sent_code_)); on_query_ok(); } @@ -615,9 +752,7 @@ void AuthManager::on_get_password_result(NetQueryPtr &result) { set_login_token_expires_at(Time::now() + login_code_retry_delay_); return; } else { - start_net_query(NetQueryType::SignIn, - G()->net_query_creator().create_unauth(telegram_api::auth_signIn( - send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code_))); + send_auth_sign_in_query(); return; } @@ -857,8 +992,9 @@ void AuthManager::on_result(NetQueryPtr result) { type = net_query_type_; net_query_type_ = NetQueryType::None; if (result->is_error()) { - if ((type == NetQueryType::SendCode || type == NetQueryType::SignIn || type == NetQueryType::RequestQrCode || - type == NetQueryType::ImportQrCode) && + if ((type == NetQueryType::SendCode || type == NetQueryType::SendEmailCode || + type == NetQueryType::VerifyEmailAddress || type == NetQueryType::SignIn || + type == NetQueryType::RequestQrCode || type == NetQueryType::ImportQrCode) && result->error().code() == 401 && result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) { auto dc_id = DcId::main(); if (type == NetQueryType::ImportQrCode) { @@ -913,6 +1049,12 @@ void AuthManager::on_result(NetQueryPtr result) { case NetQueryType::SendCode: on_send_code_result(result); break; + case NetQueryType::SendEmailCode: + on_send_email_code_result(result); + break; + case NetQueryType::VerifyEmailAddress: + on_verify_email_address_result(result); + break; case NetQueryType::RequestQrCode: on_request_qr_code_result(result, false); break; @@ -988,6 +1130,8 @@ bool AuthManager::load_state() { case State::WaitPassword: case State::WaitRegistration: return 86400; + case State::WaitEmailAddress: + case State::WaitEmailCode: case State::WaitCode: case State::WaitQrCodeConfirmation: return 5 * 60; @@ -1003,7 +1147,18 @@ bool AuthManager::load_state() { } LOG(INFO) << "Load auth_state from database: " << tag("state", static_cast(db_state.state_)); - if (db_state.state_ == State::WaitCode) { + if (db_state.state_ == State::WaitEmailAddress) { + allow_apple_id_ = db_state.allow_apple_id_; + allow_google_id_ = db_state.allow_google_id_; + send_code_helper_ = std::move(db_state.send_code_helper_); + } else if (db_state.state_ == State::WaitEmailCode) { + allow_apple_id_ = db_state.allow_apple_id_; + allow_google_id_ = db_state.allow_google_id_; + email_address_ = std::move(db_state.email_address_); + email_code_info_ = std::move(db_state.email_code_info_); + next_phone_number_login_date_ = db_state.next_phone_number_login_date_; + send_code_helper_ = std::move(db_state.send_code_helper_); + } else if (db_state.state_ == State::WaitCode) { send_code_helper_ = std::move(db_state.send_code_helper_); } else if (db_state.state_ == State::WaitQrCodeConfirmation) { other_user_ids_ = std::move(db_state.other_user_ids_); @@ -1022,8 +1177,8 @@ bool AuthManager::load_state() { } void AuthManager::save_state() { - if (state_ != State::WaitCode && state_ != State::WaitQrCodeConfirmation && state_ != State::WaitPassword && - state_ != State::WaitRegistration) { + if (state_ != State::WaitEmailAddress && state_ != State::WaitEmailCode && state_ != State::WaitCode && + state_ != State::WaitQrCodeConfirmation && state_ != State::WaitPassword && state_ != State::WaitRegistration) { if (state_ != State::Closing) { G()->td_db()->get_binlog_pmc()->erase("auth_state"); } @@ -1031,7 +1186,12 @@ void AuthManager::save_state() { } DbState db_state = [&] { - if (state_ == State::WaitCode) { + if (state_ == State::WaitEmailAddress) { + return DbState::wait_email_address(api_id_, api_hash_, allow_apple_id_, allow_google_id_, send_code_helper_); + } else if (state_ == State::WaitEmailCode) { + return DbState::wait_email_code(api_id_, api_hash_, allow_apple_id_, allow_google_id_, email_address_, + email_code_info_, next_phone_number_login_date_, send_code_helper_); + } else if (state_ == State::WaitCode) { return DbState::wait_code(api_id_, api_hash_, send_code_helper_); } else if (state_ == State::WaitQrCodeConfirmation) { return DbState::wait_qr_code_confirmation(api_id_, api_hash_, other_user_ids_, login_token_, diff --git a/td/telegram/AuthManager.h b/td/telegram/AuthManager.h index 377925f8a..d6aeef3c3 100644 --- a/td/telegram/AuthManager.h +++ b/td/telegram/AuthManager.h @@ -6,9 +6,11 @@ // #pragma once +#include "td/telegram/EmailVerification.h" #include "td/telegram/net/NetActor.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/SendCodeHelper.h" +#include "td/telegram/SentEmailCode.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/TermsOfService.h" @@ -35,7 +37,9 @@ class AuthManager final : public NetActor { void set_phone_number(uint64 query_id, string phone_number, td_api::object_ptr settings); + void set_email_address(uint64 query_id, string email_address); void resend_authentication_code(uint64 query_id); + void check_email_code(uint64 query_id, EmailVerification &&code); void check_code(uint64 query_id, string code); void register_user(uint64 query_id, string first_name, string last_name); void request_qr_code_authentication(uint64 query_id, vector other_user_ids); @@ -65,6 +69,8 @@ class AuthManager final : public NetActor { WaitQrCodeConfirmation, WaitPassword, WaitRegistration, + WaitEmailAddress, + WaitEmailCode, Ok, LoggingOut, DestroyingKeys, @@ -75,6 +81,8 @@ class AuthManager final : public NetActor { SignIn, SignUp, SendCode, + SendEmailCode, + VerifyEmailAddress, RequestQrCode, ImportQrCode, GetPassword, @@ -111,7 +119,16 @@ class AuthManager final : public NetActor { string api_hash_; Timestamp state_timestamp_; - // WaitCode + // WaitEmailAddress and WaitEmailCode + bool allow_apple_id_ = false; + bool allow_google_id_ = false; + + // WaitEmailCode + string email_address_; + SentEmailCode email_code_info_; + int32 next_phone_number_login_date_ = 0; + + // WaitEmailAddress, WaitEmailCode, WaitCode and WaitRegistration SendCodeHelper send_code_helper_; // WaitQrCodeConfirmation @@ -127,6 +144,28 @@ class AuthManager final : public NetActor { DbState() = default; + static DbState wait_email_address(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, + SendCodeHelper send_code_helper) { + DbState state(State::WaitEmailAddress, api_id, std::move(api_hash)); + state.send_code_helper_ = std::move(send_code_helper); + state.allow_apple_id_ = allow_apple_id; + state.allow_google_id_ = allow_google_id; + return state; + } + + static DbState wait_email_code(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, + string email_address, SentEmailCode email_code_info, + int32 next_phone_number_login_date, SendCodeHelper send_code_helper) { + DbState state(State::WaitEmailCode, api_id, std::move(api_hash)); + state.send_code_helper_ = std::move(send_code_helper); + state.allow_apple_id_ = allow_apple_id; + state.allow_google_id_ = allow_google_id; + state.email_address_ = std::move(email_address); + state.email_code_info_ = std::move(email_code_info); + state.next_phone_number_login_date_ = next_phone_number_login_date; + return state; + } + static DbState wait_code(int32 api_id, string api_hash, SendCodeHelper send_code_helper) { DbState state(State::WaitCode, api_id, std::move(api_hash)); state.send_code_helper_ = std::move(send_code_helper); @@ -177,6 +216,16 @@ class AuthManager final : public NetActor { int32 api_id_; string api_hash_; + // State::WaitEmailAddress + bool allow_apple_id_ = false; + bool allow_google_id_ = false; + + // State::WaitEmailCode + string email_address_; + SentEmailCode email_code_info_; + int32 next_phone_number_login_date_ = 0; + EmailVerification email_code_; + // State::WaitCode SendCodeHelper send_code_helper_; string code_; @@ -227,10 +276,15 @@ class AuthManager final : public NetActor { void do_delete_account(uint64 query_id, string reason, Result> r_input_password); + void send_auth_sign_in_query(); void send_log_out_query(); void destroy_auth_keys(); + void on_sent_code(telegram_api::object_ptr &&sent_code); + void on_send_code_result(NetQueryPtr &result); + void on_send_email_code_result(NetQueryPtr &result); + void on_verify_email_address_result(NetQueryPtr &result); void on_request_qr_code_result(NetQueryPtr &result, bool is_import); void on_get_password_result(NetQueryPtr &result); void on_request_password_recovery_result(NetQueryPtr &result); diff --git a/td/telegram/AuthManager.hpp b/td/telegram/AuthManager.hpp index b80df5191..65225376e 100644 --- a/td/telegram/AuthManager.hpp +++ b/td/telegram/AuthManager.hpp @@ -55,6 +55,7 @@ void AuthManager::DbState::store(StorerT &storer) const { bool is_wait_registration_supported = true; bool is_wait_registration_stores_phone_number = true; bool is_wait_qr_code_confirmation_supported = true; + bool is_time_store_supported = true; BEGIN_STORE_FLAGS(); STORE_FLAG(has_terms_of_service); STORE_FLAG(is_pbkdf2_supported); @@ -62,17 +63,27 @@ void AuthManager::DbState::store(StorerT &storer) const { STORE_FLAG(is_wait_registration_supported); STORE_FLAG(is_wait_registration_stores_phone_number); STORE_FLAG(is_wait_qr_code_confirmation_supported); + STORE_FLAG(allow_apple_id_); + STORE_FLAG(allow_google_id_); + STORE_FLAG(is_time_store_supported); END_STORE_FLAGS(); store(state_, storer); store(api_id_, storer); store(api_hash_, storer); - store(state_timestamp_, storer); + store_time(state_timestamp_.at(), storer); if (has_terms_of_service) { store(terms_of_service_, storer); } - if (state_ == State::WaitCode) { + if (state_ == State::WaitEmailAddress) { + store(send_code_helper_, storer); + } else if (state_ == State::WaitEmailCode) { + store(send_code_helper_, storer); + store(email_address_, storer); + store(email_code_info_, storer); + store(next_phone_number_login_date_, storer); + } else if (state_ == State::WaitCode) { store(send_code_helper_, storer); } else if (state_ == State::WaitQrCodeConfirmation) { store(other_user_ids_, storer); @@ -96,6 +107,7 @@ void AuthManager::DbState::parse(ParserT &parser) { bool is_wait_registration_supported = false; bool is_wait_registration_stores_phone_number = false; bool is_wait_qr_code_confirmation_supported = false; + bool is_time_store_supported = false; if (parser.version() >= static_cast(Version::AddTermsOfService)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_terms_of_service); @@ -104,26 +116,39 @@ void AuthManager::DbState::parse(ParserT &parser) { PARSE_FLAG(is_wait_registration_supported); PARSE_FLAG(is_wait_registration_stores_phone_number); PARSE_FLAG(is_wait_qr_code_confirmation_supported); + PARSE_FLAG(allow_apple_id_); + PARSE_FLAG(allow_google_id_); + PARSE_FLAG(is_time_store_supported); END_PARSE_FLAGS(); } - if (!is_wait_qr_code_confirmation_supported) { - return parser.set_error("Have no QR code confirmation support"); + if (!is_time_store_supported) { + return parser.set_error("Have no time store support"); } CHECK(is_pbkdf2_supported); CHECK(is_srp_supported); CHECK(is_wait_registration_supported); CHECK(is_wait_registration_stores_phone_number); + CHECK(is_wait_qr_code_confirmation_supported); parse(state_, parser); parse(api_id_, parser); parse(api_hash_, parser); - parse(state_timestamp_, parser); + double state_timestamp = 0.0; + parse_time(state_timestamp, parser); + state_timestamp_ = Timestamp::at(state_timestamp); if (has_terms_of_service) { parse(terms_of_service_, parser); } - if (state_ == State::WaitCode) { + if (state_ == State::WaitEmailAddress) { + parse(send_code_helper_, parser); + } else if (state_ == State::WaitEmailCode) { + parse(send_code_helper_, parser); + parse(email_address_, parser); + parse(email_code_info_, parser); + parse(next_phone_number_login_date_, parser); + } else if (state_ == State::WaitCode) { parse(send_code_helper_, parser); } else if (state_ == State::WaitQrCodeConfirmation) { parse(other_user_ids_, parser); diff --git a/td/telegram/AvailableReaction.cpp b/td/telegram/AvailableReaction.cpp deleted file mode 100644 index dfd794437..000000000 --- a/td/telegram/AvailableReaction.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// 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/AvailableReaction.h" - -#include "td/utils/algorithm.h" - -namespace td { - -AvailableReactionType get_reaction_type(const vector &available_reactions, const string &reaction) { - for (auto &available_reaction : available_reactions) { - if (available_reaction.reaction_ == reaction) { - if (available_reaction.is_premium_) { - return AvailableReactionType::NeedsPremium; - } - return AvailableReactionType::Available; - } - } - return AvailableReactionType::Unavailable; -} - -vector get_active_reactions(const vector &available_reactions, - const vector &active_reactions) { - if (available_reactions.empty()) { - // fast path - return available_reactions; - } - if (available_reactions.size() == active_reactions.size()) { - size_t i; - for (i = 0; i < available_reactions.size(); i++) { - if (available_reactions[i] != active_reactions[i].reaction_) { - break; - } - } - if (i == available_reactions.size()) { - // fast path - return available_reactions; - } - } - - vector result; - for (const auto &active_reaction : active_reactions) { - if (td::contains(available_reactions, active_reaction.reaction_)) { - result.push_back(active_reaction.reaction_); - } - } - return result; -} - -td_api::object_ptr AvailableReaction::get_available_reaction_object() const { - return td_api::make_object(reaction_, is_premium_); -} - -bool operator==(const AvailableReaction &lhs, const AvailableReaction &rhs) { - return lhs.reaction_ == rhs.reaction_ && lhs.is_premium_ == rhs.is_premium_; -} - -} // namespace td diff --git a/td/telegram/AvailableReaction.h b/td/telegram/AvailableReaction.h deleted file mode 100644 index e3196382d..000000000 --- a/td/telegram/AvailableReaction.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// 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" - -namespace td { - -struct AvailableReaction { - string reaction_; - bool is_premium_; - - AvailableReaction(const string &reaction, bool is_premium) : reaction_(reaction), is_premium_(is_premium) { - } - - td_api::object_ptr get_available_reaction_object() const; -}; - -bool operator==(const AvailableReaction &lhs, const AvailableReaction &rhs); - -inline bool operator!=(const AvailableReaction &lhs, const AvailableReaction &rhs) { - return !(lhs == rhs); -} - -enum class AvailableReactionType : int32 { Unavailable, Available, NeedsPremium }; - -AvailableReactionType get_reaction_type(const vector &reactions, const string &reaction); - -vector get_active_reactions(const vector &available_reactions, - const vector &active_reactions); - -} // namespace td diff --git a/td/telegram/BotMenuButton.cpp b/td/telegram/BotMenuButton.cpp index 32c18c418..3b997e06b 100644 --- a/td/telegram/BotMenuButton.cpp +++ b/td/telegram/BotMenuButton.cpp @@ -133,7 +133,7 @@ void set_menu_button(Td *td, UserId user_id, td_api::object_ptr(); } else if (menu_button->text_.empty()) { if (menu_button->url_ != "default") { - return promise.set_error(Status::Error(400, "Menu button text can't be empty")); + return promise.set_error(Status::Error(400, "Menu button text must be non-empty")); } input_bot_menu_button = telegram_api::make_object(); } else { diff --git a/td/telegram/CallManager.cpp b/td/telegram/CallManager.cpp index 702655775..0e4452102 100644 --- a/td/telegram/CallManager.cpp +++ b/td/telegram/CallManager.cpp @@ -66,8 +66,9 @@ void CallManager::create_call(UserId user_id, tl_object_ptr(std::move(promise), Status::Error(400, "Call not found")); send_closure(actor, &CallActor::create_call, user_id, std::move(input_user), std::move(protocol), is_video, - std::move(promise)); + std::move(safe_promise)); } void CallManager::accept_call(CallId call_id, CallProtocol &&protocol, Promise promise) { @@ -75,7 +76,8 @@ void CallManager::accept_call(CallId call_id, CallProtocol &&protocol, Promise(std::move(promise), Status::Error(400, "Call not found")); + send_closure(actor, &CallActor::accept_call, std::move(protocol), std::move(safe_promise)); } void CallManager::send_call_signaling_data(CallId call_id, string &&data, Promise promise) { @@ -83,7 +85,8 @@ void CallManager::send_call_signaling_data(CallId call_id, string &&data, Promis if (actor.empty()) { return promise.set_error(Status::Error(400, "Call not found")); } - send_closure(actor, &CallActor::send_call_signaling_data, std::move(data), std::move(promise)); + auto safe_promise = SafePromise(std::move(promise), Status::Error(400, "Call not found")); + send_closure(actor, &CallActor::send_call_signaling_data, std::move(data), std::move(safe_promise)); } void CallManager::discard_call(CallId call_id, bool is_disconnected, int32 duration, bool is_video, int64 connection_id, @@ -92,7 +95,9 @@ void CallManager::discard_call(CallId call_id, bool is_disconnected, int32 durat if (actor.empty()) { return promise.set_error(Status::Error(400, "Call not found")); } - send_closure(actor, &CallActor::discard_call, is_disconnected, duration, is_video, connection_id, std::move(promise)); + auto safe_promise = SafePromise(std::move(promise), Status::Error(400, "Call not found")); + send_closure(actor, &CallActor::discard_call, is_disconnected, duration, is_video, connection_id, + std::move(safe_promise)); } void CallManager::rate_call(CallId call_id, int32 rating, string comment, @@ -101,7 +106,8 @@ void CallManager::rate_call(CallId call_id, int32 rating, string comment, if (actor.empty()) { return promise.set_error(Status::Error(400, "Call not found")); } - send_closure(actor, &CallActor::rate_call, rating, std::move(comment), std::move(problems), std::move(promise)); + auto safe_promise = SafePromise(std::move(promise), Status::Error(400, "Call not found")); + send_closure(actor, &CallActor::rate_call, rating, std::move(comment), std::move(problems), std::move(safe_promise)); } void CallManager::send_call_debug_information(CallId call_id, string data, Promise promise) { @@ -109,7 +115,8 @@ void CallManager::send_call_debug_information(CallId call_id, string data, Promi if (actor.empty()) { return promise.set_error(Status::Error(400, "Call not found")); } - send_closure(actor, &CallActor::send_call_debug_information, std::move(data), std::move(promise)); + auto safe_promise = SafePromise(std::move(promise), Status::Error(400, "Call not found")); + send_closure(actor, &CallActor::send_call_debug_information, std::move(data), std::move(safe_promise)); } void CallManager::send_call_log(CallId call_id, td_api::object_ptr log_file, Promise promise) { @@ -117,7 +124,8 @@ void CallManager::send_call_log(CallId call_id, td_api::object_ptr(std::move(promise), Status::Error(400, "Call not found")); + send_closure(actor, &CallActor::send_call_log, std::move(log_file), std::move(safe_promise)); } CallId CallManager::create_call_actor() { diff --git a/td/telegram/ChatReactions.cpp b/td/telegram/ChatReactions.cpp new file mode 100644 index 000000000..93a0c77e1 --- /dev/null +++ b/td/telegram/ChatReactions.cpp @@ -0,0 +1,118 @@ +// +// 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/ChatReactions.h" + +#include "td/telegram/MessageReaction.h" + +#include "td/utils/algorithm.h" + +namespace td { + +ChatReactions::ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr) { + if (chat_reactions_ptr == nullptr) { + return; + } + switch (chat_reactions_ptr->get_id()) { + case telegram_api::chatReactionsNone::ID: + break; + case telegram_api::chatReactionsAll::ID: { + auto chat_reactions = move_tl_object_as(chat_reactions_ptr); + allow_all_ = true; + allow_custom_ = chat_reactions->allow_custom_; + break; + } + case telegram_api::chatReactionsSome::ID: { + auto chat_reactions = move_tl_object_as(chat_reactions_ptr); + reactions_ = + transform(chat_reactions->reactions_, [](const telegram_api::object_ptr &reaction) { + return get_message_reaction_string(reaction); + }); + break; + } + default: + UNREACHABLE(); + } +} + +ChatReactions::ChatReactions(td_api::object_ptr &&chat_reactions_ptr, + bool allow_custom) { + if (chat_reactions_ptr == nullptr) { + return; + } + switch (chat_reactions_ptr->get_id()) { + case td_api::chatAvailableReactionsAll::ID: + allow_all_ = true; + allow_custom_ = allow_custom; + break; + case td_api::chatAvailableReactionsSome::ID: { + auto chat_reactions = move_tl_object_as(chat_reactions_ptr); + reactions_ = transform(chat_reactions->reactions_, [](const td_api::object_ptr &reaction) { + return get_message_reaction_string(reaction); + }); + break; + } + default: + UNREACHABLE(); + } +} + +ChatReactions ChatReactions::get_active_reactions(const FlatHashMap &active_reaction_pos) const { + ChatReactions result = *this; + if (!reactions_.empty()) { + CHECK(!allow_all_); + CHECK(!allow_custom_); + td::remove_if(result.reactions_, + [&](const string &reaction) { return !is_active_reaction(reaction, active_reaction_pos); }); + } + return result; +} + +bool ChatReactions::is_allowed_reaction(const string &reaction) const { + CHECK(!allow_all_); + if (allow_custom_ && is_custom_reaction(reaction)) { + return true; + } + return td::contains(reactions_, reaction); +} + +td_api::object_ptr ChatReactions::get_chat_available_reactions_object() const { + if (allow_all_) { + return td_api::make_object(); + } + return td_api::make_object(transform(reactions_, get_reaction_type_object)); +} + +telegram_api::object_ptr ChatReactions::get_input_chat_reactions() const { + if (allow_all_) { + int32 flags = 0; + if (allow_custom_) { + flags |= telegram_api::chatReactionsAll::ALLOW_CUSTOM_MASK; + } + return telegram_api::make_object(flags, false /*ignored*/); + } + if (!reactions_.empty()) { + return telegram_api::make_object(transform(reactions_, get_input_reaction)); + } + return telegram_api::make_object(); +} + +bool operator==(const ChatReactions &lhs, const ChatReactions &rhs) { + // don't compare allow_custom_ + return lhs.reactions_ == rhs.reactions_ && lhs.allow_all_ == rhs.allow_all_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions) { + if (reactions.allow_all_) { + if (reactions.allow_custom_) { + return string_builder << "AllReactions"; + } + return string_builder << "AllRegularReactions"; + } + return string_builder << '[' << reactions.reactions_ << ']'; +} + +} // namespace td diff --git a/td/telegram/ChatReactions.h b/td/telegram/ChatReactions.h new file mode 100644 index 000000000..78594fd37 --- /dev/null +++ b/td/telegram/ChatReactions.h @@ -0,0 +1,83 @@ +// +// 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/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +struct ChatReactions { + vector reactions_; + bool allow_all_ = false; // implies empty reactions + bool allow_custom_ = false; // implies allow_all + + ChatReactions() = default; + + explicit ChatReactions(vector &&reactions) : reactions_(std::move(reactions)) { + } + + explicit ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr); + + ChatReactions(td_api::object_ptr &&chat_reactions_ptr, bool allow_custom); + + ChatReactions(bool allow_all, bool allow_custom) : allow_all_(allow_all), allow_custom_(allow_custom) { + } + + ChatReactions get_active_reactions(const FlatHashMap &active_reaction_pos) const; + + bool is_allowed_reaction(const string &reaction) const; + + telegram_api::object_ptr get_input_chat_reactions() const; + + td_api::object_ptr get_chat_available_reactions_object() const; + + bool empty() const { + return reactions_.empty() && !allow_all_; + } + + template + void store(StorerT &storer) const { + bool has_reactions = !reactions_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(allow_all_); + STORE_FLAG(allow_custom_); + STORE_FLAG(has_reactions); + END_STORE_FLAGS(); + if (has_reactions) { + td::store(reactions_, storer); + } + } + + template + void parse(ParserT &parser) { + bool has_reactions; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(allow_all_); + PARSE_FLAG(allow_custom_); + PARSE_FLAG(has_reactions); + END_PARSE_FLAGS(); + if (has_reactions) { + td::parse(reactions_, parser); + } + } +}; + +bool operator==(const ChatReactions &lhs, const ChatReactions &rhs); + +inline bool operator!=(const ChatReactions &lhs, const ChatReactions &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions); + +} // namespace td diff --git a/td/telegram/Client.cpp b/td/telegram/Client.cpp index 101f54c23..f9fcb5894 100644 --- a/td/telegram/Client.cpp +++ b/td/telegram/Client.cpp @@ -98,8 +98,7 @@ class ClientManager::Impl final { CHECK(concurrent_scheduler_ == nullptr); CHECK(options_.net_query_stats == nullptr); options_.net_query_stats = std::make_shared(); - concurrent_scheduler_ = make_unique(); - concurrent_scheduler_->init(0); + concurrent_scheduler_ = make_unique(0, 0); concurrent_scheduler_->start(); } tds_[client_id] = @@ -354,8 +353,7 @@ class MultiImpl { static constexpr int32 ADDITIONAL_THREAD_COUNT = 3; explicit MultiImpl(std::shared_ptr net_query_stats) { - concurrent_scheduler_ = std::make_shared(); - concurrent_scheduler_->init(ADDITIONAL_THREAD_COUNT); + concurrent_scheduler_ = std::make_shared(ADDITIONAL_THREAD_COUNT, 0); concurrent_scheduler_->start(); { @@ -423,6 +421,7 @@ class MultiImpl { static std::atomic current_id_; }; +constexpr int32 MultiImpl::ADDITIONAL_THREAD_COUNT; std::atomic MultiImpl::current_id_{1}; class MultiImplPool { diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index d64d088ac..00cd88b1d 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -12,6 +12,7 @@ #include "td/telegram/JsonValue.h" #include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/MessageReaction.h" #include "td/telegram/net/AuthDataShared.h" #include "td/telegram/net/ConnectionCreator.h" #include "td/telegram/net/DcId.h" @@ -1414,6 +1415,13 @@ void ConfigManager::process_config(tl_object_ptr config) { options.set_option_integer("notification_cloud_delay_ms", fix_timeout_ms(config->notify_cloud_delay_ms_)); options.set_option_integer("notification_default_delay_ms", fix_timeout_ms(config->notify_default_delay_ms_)); + if (is_from_main_dc && !options.have_option("default_reaction_need_sync")) { + auto reaction_str = get_message_reaction_string(config->reactions_default_); + if (!reaction_str.empty()) { + options.set_option_string("default_reaction", reaction_str); + } + } + // delete outdated options options.set_option_empty("suggested_language_code"); options.set_option_empty("chat_big_size"); @@ -1470,11 +1478,10 @@ void ConfigManager::process_app_config(tl_object_ptr &c string animation_search_emojis; vector suggested_actions; bool can_archive_and_mute_new_chats_from_unknown_users = false; - int64 chat_read_mark_expire_period = 0; - int64 chat_read_mark_size_threshold = 0; + int32 chat_read_mark_expire_period = 0; + int32 chat_read_mark_size_threshold = 0; double animated_emoji_zoom = 0.0; - string default_reaction; - int64 reactions_uniq_max = 0; + int32 reactions_uniq_max = 0; vector premium_features; auto &premium_limit_keys = get_premium_limit_keys(); string premium_bot_username; @@ -1722,10 +1729,6 @@ void ConfigManager::process_app_config(tl_object_ptr &c chat_read_mark_size_threshold = get_json_value_int(std::move(key_value->value_), key); continue; } - if (key == "reactions_default") { - default_reaction = get_json_value_string(std::move(key_value->value_), key); - continue; - } if (key == "reactions_uniq_max") { reactions_uniq_max = get_json_value_int(std::move(key_value->value_), key); continue; @@ -1798,6 +1801,16 @@ void ConfigManager::process_app_config(tl_object_ptr &c stickers_normal_by_emoji_per_premium_num = get_json_value_int(std::move(key_value->value_), key); continue; } + if (key == "default_emoji_statuses_stickerset_id") { + auto setting_value = get_json_value_long(std::move(key_value->value_), key); + G()->set_option_integer("themed_emoji_statuses_sticker_set_id", setting_value); + continue; + } + if (key == "reactions_user_max_default" || key == "reactions_user_max_premium") { + auto setting_value = get_json_value_int(std::move(key_value->value_), key); + G()->set_option_integer(key, setting_value); + continue; + } new_values.push_back(std::move(key_value)); } @@ -1814,13 +1827,15 @@ void ConfigManager::process_app_config(tl_object_ptr &c if (ignored_restriction_reasons.empty()) { options.set_option_empty("ignored_restriction_reasons"); - if (options.get_option_boolean("ignore_sensitive_content_restrictions", true)) { + if (options.get_option_boolean("ignore_sensitive_content_restrictions", true) || + options.get_option_boolean("can_ignore_sensitive_content_restrictions", true)) { get_content_settings(Auto()); } } else { options.set_option_string("ignored_restriction_reasons", ignored_restriction_reasons); - if (!options.get_option_boolean("can_ignore_sensitive_content_restrictions")) { + if (!options.get_option_boolean("can_ignore_sensitive_content_restrictions") || + !options.get_option_boolean("ignore_sensitive_content_restrictions")) { get_content_settings(Auto()); } } @@ -1872,9 +1887,6 @@ void ConfigManager::process_app_config(tl_object_ptr &c } else { options.set_option_integer("chat_read_mark_size_threshold", chat_read_mark_size_threshold); } - if (!options.have_option("default_reaction_need_sync")) { - options.set_option_string("default_reaction", default_reaction); - } if (reactions_uniq_max <= 0 || reactions_uniq_max == 11) { options.set_option_empty("reactions_uniq_max"); } else { diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index bfb339d95..8c8a4be00 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -463,8 +463,10 @@ class UploadProfilePhotoQuery final : public Td::ResultHandler { flags |= telegram_api::photos_uploadProfilePhoto::FILE_MASK; photo_input_file = std::move(input_file); } - send_query(G()->net_query_creator().create(telegram_api::photos_uploadProfilePhoto( - flags, std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp))); + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadProfilePhoto(flags, std::move(photo_input_file), std::move(video_input_file), + main_frame_timestamp), + {{"me"}})); } void on_result(BufferSlice packet) final { @@ -502,7 +504,8 @@ class UpdateProfilePhotoQuery final : public Td::ResultHandler { file_id_ = file_id; old_photo_id_ = old_photo_id; file_reference_ = FileManager::extract_file_reference(input_photo); - send_query(G()->net_query_creator().create(telegram_api::photos_updateProfilePhoto(std::move(input_photo)))); + send_query( + G()->net_query_creator().create(telegram_api::photos_updateProfilePhoto(std::move(input_photo)), {{"me"}})); } void on_result(BufferSlice packet) final { @@ -593,8 +596,8 @@ class UpdateProfileQuery final : public Td::ResultHandler { first_name_ = first_name; last_name_ = last_name; about_ = about; - send_query( - G()->net_query_creator().create(telegram_api::account_updateProfile(flags, first_name, last_name, about))); + send_query(G()->net_query_creator().create(telegram_api::account_updateProfile(flags, first_name, last_name, about), + {{"me"}})); } void on_result(BufferSlice packet) final { @@ -648,7 +651,7 @@ class UpdateUsernameQuery final : public Td::ResultHandler { } void send(const string &username) { - send_query(G()->net_query_creator().create(telegram_api::account_updateUsername(username))); + send_query(G()->net_query_creator().create(telegram_api::account_updateUsername(username), {{"me"}})); } void on_result(BufferSlice packet) final { @@ -671,6 +674,38 @@ class UpdateUsernameQuery final : public Td::ResultHandler { } }; +class UpdateEmojiStatusQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit UpdateEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(EmojiStatus emoji_status) { + send_query(G()->net_query_creator().create( + telegram_api::account_updateEmojiStatus(emoji_status.get_input_emoji_status()), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdateEmojiStatusQuery: " << result_ptr.ok(); + if (result_ptr.ok()) { + promise_.set_value(Unit()); + } else { + promise_.set_error(Status::Error(400, "Failed to change Premium badge")); + } + } + + void on_error(Status status) final { + get_recent_emoji_statuses(td_, Auto()); + promise_.set_error(std::move(status)); + } +}; + class CheckChannelUsernameQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; @@ -2554,11 +2589,9 @@ class GetInactiveChannelsQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetInactiveChannelsQuery: " << to_string(result); - // TODO use result->dates_ + // don't need to use result->dates_, because chat.last_message.date is more reliable td_->contacts_manager_->on_get_users(std::move(result->users_), "GetInactiveChannelsQuery"); - td_->contacts_manager_->on_get_inactive_channels(std::move(result->chats_)); - - promise_.set_value(Unit()); + td_->contacts_manager_->on_get_inactive_channels(std::move(result->chats_), std::move(promise_)); } void on_error(Status status) final { @@ -3382,6 +3415,9 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent user_online_timeout_.set_callback(on_user_online_timeout_callback); user_online_timeout_.set_callback_data(static_cast(this)); + user_emoji_status_timeout_.set_callback(on_user_emoji_status_timeout_callback); + user_emoji_status_timeout_.set_callback_data(static_cast(this)); + channel_unban_timeout_.set_callback(on_channel_unban_timeout_callback); channel_unban_timeout_.set_callback_data(static_cast(this)); @@ -3471,6 +3507,28 @@ void ContactsManager::on_user_online_timeout(UserId user_id) { update_user_online_member_count(u); } +void ContactsManager::on_user_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) { + if (G()->close_flag()) { + return; + } + + auto contacts_manager = static_cast(contacts_manager_ptr); + send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_emoji_status_timeout, + UserId(user_id_long)); +} + +void ContactsManager::on_user_emoji_status_timeout(UserId user_id) { + if (G()->close_flag()) { + return; + } + + auto u = get_user(user_id); + CHECK(u != nullptr); + CHECK(u->is_update_user_sent); + + update_user(u, user_id); +} + void ContactsManager::on_channel_unban_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) { if (G()->close_flag()) { return; @@ -3622,6 +3680,7 @@ void ContactsManager::User::store(StorerT &storer) const { bool has_cache_version = cache_version != 0; bool has_is_contact = true; bool has_restriction_reasons = !restriction_reasons.empty(); + bool has_emoji_status = !emoji_status.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_received); STORE_FLAG(is_verified); @@ -3650,6 +3709,7 @@ void ContactsManager::User::store(StorerT &storer) const { STORE_FLAG(can_be_added_to_attach_menu); STORE_FLAG(is_premium); // 25 STORE_FLAG(attach_menu_enabled); + STORE_FLAG(has_emoji_status); END_STORE_FLAGS(); store(first_name, storer); if (has_last_name) { @@ -3681,6 +3741,9 @@ void ContactsManager::User::store(StorerT &storer) const { if (has_cache_version) { store(cache_version, storer); } + if (has_emoji_status) { + store(emoji_status, storer); + } } template @@ -3695,6 +3758,7 @@ void ContactsManager::User::parse(ParserT &parser) { bool has_cache_version; bool has_is_contact; bool has_restriction_reasons; + bool has_emoji_status; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_received); PARSE_FLAG(is_verified); @@ -3723,6 +3787,7 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(can_be_added_to_attach_menu); PARSE_FLAG(is_premium); PARSE_FLAG(attach_menu_enabled); + PARSE_FLAG(has_emoji_status); END_PARSE_FLAGS(); parse(first_name, parser); if (has_last_name) { @@ -3774,6 +3839,9 @@ void ContactsManager::User::parse(ParserT &parser) { if (has_cache_version) { parse(cache_version, parser); } + if (has_emoji_status) { + parse(emoji_status, parser); + } if (!check_utf8(first_name)) { LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; @@ -6556,6 +6624,32 @@ void ContactsManager::set_username(const string &username, Promise &&promi td_->create_handler(std::move(promise))->send(username); } +void ContactsManager::set_emoji_status(EmojiStatus emoji_status, Promise &&promise) { + if (!td_->option_manager_->get_option_boolean("is_premium")) { + return promise.set_error(Status::Error(400, "The method is available only for Telegram Premium users")); + } + add_recent_emoji_status(td_, emoji_status); + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), emoji_status, promise = std::move(promise)](Result result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &ContactsManager::on_set_emoji_status, emoji_status, std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + }); + td_->create_handler(std::move(query_promise))->send(emoji_status); +} + +void ContactsManager::on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise) { + auto user_id = get_my_id(); + User *u = get_user(user_id); + if (u != nullptr) { + on_update_user_emoji_status(u, user_id, emoji_status); + update_user(u, user_id); + } + promise.set_value(Unit()); +} + void ContactsManager::set_chat_description(ChatId chat_id, const string &description, Promise &&promise) { auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); auto c = get_chat(chat_id); @@ -7150,7 +7244,9 @@ void ContactsManager::add_channel_participant(ChannelId channel_id, UserId user_ return promise.set_error(Status::Error(400, "Can't return to kicked from chat")); } - speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(), c->status); + if (!get_channel_join_request(c)) { + speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(), c->status); + } td_->create_handler(std::move(promise))->send(channel_id); return; } @@ -8210,26 +8306,42 @@ void ContactsManager::update_dialogs_for_discussion(DialogId dialog_id, bool is_ } vector ContactsManager::get_inactive_channels(Promise &&promise) { - if (inactive_channels_inited_) { + if (inactive_channel_ids_inited_) { promise.set_value(Unit()); - return transform(inactive_channels_, [&](ChannelId channel_id) { - DialogId dialog_id{channel_id}; - td_->messages_manager_->force_create_dialog(dialog_id, "get_inactive_channels"); - return dialog_id; - }); + return transform(inactive_channel_ids_, [&](ChannelId channel_id) { return DialogId(channel_id); }); } td_->create_handler(std::move(promise))->send(); return {}; } -void ContactsManager::on_get_inactive_channels(vector> &&chats) { - inactive_channels_inited_ = true; - inactive_channels_ = get_channel_ids(std::move(chats), "on_get_inactive_channels"); +void ContactsManager::on_get_inactive_channels(vector> &&chats, + Promise &&promise) { + auto channel_ids = get_channel_ids(std::move(chats), "on_get_inactive_channels"); + + MultiPromiseActorSafe mpas{"GetInactiveChannelsMultiPromiseActor"}; + mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), channel_ids, + promise = std::move(promise)](Unit) mutable { + send_closure(actor_id, &ContactsManager::on_create_inactive_channels, std::move(channel_ids), std::move(promise)); + })); + mpas.set_ignore_errors(true); + auto lock_promise = mpas.get_promise(); + + for (auto channel_id : channel_ids) { + td_->messages_manager_->create_dialog(DialogId(channel_id), false, mpas.get_promise()); + } + + lock_promise.set_value(Unit()); +} + +void ContactsManager::on_create_inactive_channels(vector &&channel_ids, Promise &&promise) { + inactive_channel_ids_inited_ = true; + inactive_channel_ids_ = std::move(channel_ids); + promise.set_value(Unit()); } void ContactsManager::remove_inactive_channel(ChannelId channel_id) { - if (inactive_channels_inited_ && td::remove(inactive_channels_, channel_id)) { + if (inactive_channel_ids_inited_ && td::remove(inactive_channel_ids_, channel_id)) { LOG(DEBUG) << "Remove " << channel_id << " from list of inactive channels"; } } @@ -8676,6 +8788,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_), std::move(user->username_)); } + on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_))); bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0; bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0; @@ -8724,17 +8837,16 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, << "Receive not bot " << user_id << " which has bot info version from " << source; int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1; - if (is_verified != u->is_verified || is_premium != u->is_premium || is_support != u->is_support || - is_bot != u->is_bot || can_join_groups != u->can_join_groups || - can_read_all_group_messages != u->can_read_all_group_messages || restriction_reasons != u->restriction_reasons || - is_scam != u->is_scam || is_fake != u->is_fake || is_inline_bot != u->is_inline_bot || - inline_query_placeholder != u->inline_query_placeholder || need_location_bot != u->need_location_bot || - can_be_added_to_attach_menu != u->can_be_added_to_attach_menu || attach_menu_enabled != u->attach_menu_enabled) { + if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot || + can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages || + restriction_reasons != u->restriction_reasons || is_scam != u->is_scam || is_fake != u->is_fake || + is_inline_bot != u->is_inline_bot || inline_query_placeholder != u->inline_query_placeholder || + need_location_bot != u->need_location_bot || can_be_added_to_attach_menu != u->can_be_added_to_attach_menu || + attach_menu_enabled != u->attach_menu_enabled) { LOG_IF(ERROR, is_bot != u->is_bot && !is_deleted && !u->is_deleted && u->is_received) << "User.is_bot has changed for " << user_id << "/" << u->username << " from " << source << " from " << u->is_bot << " to " << is_bot; u->is_verified = is_verified; - u->is_premium = is_premium; u->is_support = is_support; u->is_bot = is_bot; u->can_join_groups = can_join_groups; @@ -8751,6 +8863,10 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, LOG(DEBUG) << "Info has changed for " << user_id; u->is_changed = true; } + if (is_premium != u->is_premium) { + u->is_premium = is_premium; + u->is_changed = true; + } if (u->bot_info_version != bot_info_version) { u->bot_info_version = bot_info_version; @@ -9082,7 +9198,7 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id) { false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, first_name, string(), username, - phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), string()); + phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), string(), nullptr); on_get_user(std::move(user), "get_user_force"); u = get_user(user_id); CHECK(u != nullptr && u->is_received); @@ -10221,6 +10337,7 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo if (td_->option_manager_->get_option_boolean("is_premium") != u->is_premium) { td_->option_manager_->set_option_boolean("is_premium", u->is_premium); send_closure(td_->config_manager_, &ConfigManager::request_config, true); + td_->stickers_manager_->reload_top_reactions(); } } if (u->is_name_changed || u->is_username_changed || u->is_is_contact_changed) { @@ -10289,6 +10406,27 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } } + auto unix_time = G()->unix_time(); + auto effective_custom_emoji_id = u->emoji_status.get_effective_custom_emoji_id(u->is_premium, unix_time); + if (effective_custom_emoji_id != u->last_sent_emoji_status) { + u->last_sent_emoji_status = effective_custom_emoji_id; + u->is_changed = true; + } else { + u->need_save_to_database = true; + } + if (u->last_sent_emoji_status != 0) { + auto until_date = u->emoji_status.get_until_date(); + auto left_time = until_date - unix_time; + if (left_time >= 0 && left_time < 30 * 86400) { + LOG(DEBUG) << "Set emoji status timeout for " << user_id << " in " << left_time; + user_emoji_status_timeout_.set_timeout_in(user_id.get(), left_time); + } else { + user_emoji_status_timeout_.cancel_timeout(user_id.get()); + } + } else { + user_emoji_status_timeout_.cancel_timeout(user_id.get()); + } + if (u->is_deleted) { td_->inline_queries_manager_->remove_recent_inline_bot(user_id, Promise<>()); } @@ -10831,9 +10969,7 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u bool supports_video_calls = user->video_calls_available_ && !user->phone_calls_private_; bool has_private_calls = user->phone_calls_private_; bool voice_messages_forbidden = u->is_premium ? user->voice_messages_forbidden_ : false; - auto premium_gift_options = transform(std::move(user->premium_gifts_), [](auto &&premium_gift_option) { - return PremiumGiftOption(std::move(premium_gift_option)); - }); + auto premium_gift_options = get_premium_gift_options(std::move(user->premium_gifts_)); AdministratorRights group_administrator_rights(user->bot_group_admin_rights_, ChannelType::Megagroup); AdministratorRights broadcast_administrator_rights(user->bot_broadcast_admin_rights_, ChannelType::Broadcast); if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls || @@ -11691,6 +11827,29 @@ void ContactsManager::register_user_photo(User *u, UserId user_id, const Photo & } } +void ContactsManager::on_update_user_emoji_status(UserId user_id, + tl_object_ptr &&emoji_status) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id); + if (u != nullptr) { + on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(emoji_status))); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user emoji status about unknown " << user_id; + } +} + +void ContactsManager::on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status) { + if (u->emoji_status != emoji_status) { + LOG(DEBUG) << "Change emoji status of " << user_id << " from " << u->emoji_status << " to " << emoji_status; + u->emoji_status = emoji_status; + } +} + void ContactsManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact) { UserId my_id = get_my_id(); if (user_id == my_id) { @@ -16658,8 +16817,8 @@ td_api::object_ptr ContactsManager::get_user_status_object(U td_api::object_ptr ContactsManager::get_update_unknown_user_object(UserId user_id) { return td_api::make_object(td_api::make_object( - user_id.get(), "", "", "", "", td_api::make_object(), nullptr, false, false, false, - false, false, "", false, false, false, td_api::make_object(), "", false)); + user_id.get(), "", "", "", "", td_api::make_object(), nullptr, nullptr, false, false, + false, false, false, "", false, false, false, td_api::make_object(), "", false)); } int64 ContactsManager::get_user_id_object(UserId user_id, const char *source) const { @@ -16716,11 +16875,13 @@ tl_object_ptr ContactsManager::get_user_object(UserId user_id, con type = make_tl_object(); } + auto emoji_status = u->last_sent_emoji_status != 0 ? u->emoji_status.get_emoji_status_object() : nullptr; return make_tl_object( user_id.get(), u->first_name, u->last_name, u->username, u->phone_number, get_user_status_object(user_id, u), - get_profile_photo_object(td_->file_manager_.get(), u->photo), u->is_contact, u->is_mutual_contact, u->is_verified, - u->is_premium, u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, - u->is_received, std::move(type), u->language_code, u->attach_menu_enabled); + get_profile_photo_object(td_->file_manager_.get(), u->photo), std::move(emoji_status), u->is_contact, + u->is_mutual_contact, u->is_verified, u->is_premium, u->is_support, + get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->is_received, + std::move(type), u->language_code, u->attach_menu_enabled); } vector ContactsManager::get_user_ids_object(const vector &user_ids, const char *source) const { @@ -16779,18 +16940,14 @@ tl_object_ptr ContactsManager::get_user_full_info_object(U } bio_object = get_formatted_text_object(bio, true, 0); } - auto base_premium_gift_it = - std::min_element(user_full->premium_gift_options.begin(), user_full->premium_gift_options.end()); - auto premium_gift_options = transform(user_full->premium_gift_options, [&base_premium_gift_it](const auto &option) { - return option.get_premium_gift_option_object(*base_premium_gift_it); - }); auto voice_messages_forbidden = is_premium ? user_full->voice_messages_forbidden : false; - return make_tl_object( - get_chat_photo_object(td_->file_manager_.get(), user_full->photo), user_full->is_blocked, - user_full->can_be_called, user_full->supports_video_calls, user_full->has_private_calls, - !user_full->private_forward_name.empty(), voice_messages_forbidden, - user_full->need_phone_number_privacy_exception, std::move(bio_object), std::move(premium_gift_options), - user_full->common_chat_count, std::move(bot_info)); + return make_tl_object(get_chat_photo_object(td_->file_manager_.get(), user_full->photo), + user_full->is_blocked, user_full->can_be_called, + user_full->supports_video_calls, user_full->has_private_calls, + !user_full->private_forward_name.empty(), voice_messages_forbidden, + user_full->need_phone_number_privacy_exception, std::move(bio_object), + get_premium_payment_options_object(user_full->premium_gift_options), + user_full->common_chat_count, std::move(bot_info)); } td_api::object_ptr ContactsManager::get_update_unknown_basic_group_object(ChatId chat_id) { diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index ee28afdd3..23d867d63 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -19,6 +19,7 @@ #include "td/telegram/DialogLocation.h" #include "td/telegram/DialogParticipant.h" #include "td/telegram/DialogParticipantFilter.h" +#include "td/telegram/EmojiStatus.h" #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/FolderId.h" @@ -180,6 +181,7 @@ class ContactsManager final : public Actor { void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, string &&username); void on_update_user_phone_number(UserId user_id, string &&phone_number); void on_update_user_photo(UserId user_id, tl_object_ptr &&photo_ptr); + void on_update_user_emoji_status(UserId user_id, tl_object_ptr &&emoji_status); void on_update_user_online(UserId user_id, tl_object_ptr &&status); void on_update_user_local_was_online(UserId user_id, int32 local_was_online); void on_update_user_is_blocked(UserId user_id, bool is_blocked); @@ -252,7 +254,7 @@ class ContactsManager final : public Actor { void on_get_dialogs_for_discussion(vector> &&chats); - void on_get_inactive_channels(vector> &&chats); + void on_get_inactive_channels(vector> &&chats, Promise &&promise); void remove_inactive_channel(ChannelId channel_id); @@ -345,6 +347,8 @@ class ContactsManager final : public Actor { void set_username(const string &username, Promise &&promise); + void set_emoji_status(EmojiStatus emoji_status, Promise &&promise); + void set_chat_description(ChatId chat_id, const string &description, Promise &&promise); void set_channel_username(ChannelId channel_id, const string &username, Promise &&promise); @@ -643,6 +647,8 @@ class ContactsManager final : public Actor { string username; string phone_number; int64 access_hash = -1; + EmojiStatus emoji_status; + int64 last_sent_emoji_status = 0; ProfilePhoto photo; @@ -1072,6 +1078,7 @@ class ContactsManager final : public Actor { static constexpr int32 USER_FLAG_IS_ATTACH_MENU_BOT = 1 << 27; static constexpr int32 USER_FLAG_IS_PREMIUM = 1 << 28; static constexpr int32 USER_FLAG_ATTACH_MENU_ENABLED = 1 << 29; + static constexpr int32 USER_FLAG_HAS_EMOJI_STATUS = 1 << 30; static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0; static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1; @@ -1248,10 +1255,13 @@ class ContactsManager final : public Actor { static bool is_valid_username(const string &username); + void on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise); + void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name, string &&username); void on_update_user_phone_number(User *u, UserId user_id, string &&phone_number); void on_update_user_photo(User *u, UserId user_id, tl_object_ptr &&photo, const char *source); + void on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status); void on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact); void on_update_user_online(User *u, UserId user_id, tl_object_ptr &&status); void on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online); @@ -1604,6 +1614,8 @@ class ContactsManager final : public Actor { vector get_dialog_ids(vector> &&chats, const char *source); + void on_create_inactive_channels(vector &&channel_ids, Promise &&promise); + void update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable); void send_edit_chat_admin_query(ChatId chat_id, UserId user_id, bool is_administrator, Promise &&promise); @@ -1672,6 +1684,8 @@ class ContactsManager final : public Actor { static void on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); + static void on_user_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); + static void on_channel_unban_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long); static void on_user_nearby_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); @@ -1684,6 +1698,8 @@ class ContactsManager final : public Actor { void on_user_online_timeout(UserId user_id); + void on_user_emoji_status_timeout(UserId user_id); + void on_channel_unban_timeout(ChannelId channel_id); void on_user_nearby_timeout(UserId user_id); @@ -1749,8 +1765,8 @@ class ContactsManager final : public Actor { bool dialogs_for_discussion_inited_ = false; vector dialogs_for_discussion_; - bool inactive_channels_inited_ = false; - vector inactive_channels_; + bool inactive_channel_ids_inited_ = false; + vector inactive_channel_ids_; FlatHashMap>, UserIdHash> load_user_from_database_queries_; FlatHashSet loaded_from_database_users_; @@ -1857,6 +1873,7 @@ class ContactsManager final : public Actor { vector unimported_contact_invites_; // result of change_imported_contacts MultiTimeout user_online_timeout_{"UserOnlineTimeout"}; + MultiTimeout user_emoji_status_timeout_{"UserEmojiStatusTimeout"}; MultiTimeout channel_unban_timeout_{"ChannelUnbanTimeout"}; MultiTimeout user_nearby_timeout_{"UserNearbyTimeout"}; MultiTimeout slow_mode_delay_timeout_{"SlowModeDelayTimeout"}; diff --git a/td/telegram/DialogEventLog.cpp b/td/telegram/DialogEventLog.cpp index 41d7b65c0..56b5682e3 100644 --- a/td/telegram/DialogEventLog.cpp +++ b/td/telegram/DialogEventLog.cpp @@ -7,6 +7,7 @@ #include "td/telegram/DialogEventLog.h" #include "td/telegram/ChannelId.h" +#include "td/telegram/ChatReactions.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogLocation.h" @@ -151,9 +152,8 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionUpdatePinned::ID: { auto action = move_tl_object_as(action_ptr); - DialogId sender_dialog_id; auto message = td->messages_manager_->get_dialog_event_log_message_object( - DialogId(channel_id), std::move(action->message_), sender_dialog_id); + DialogId(channel_id), std::move(action->message_), actor_dialog_id); if (message == nullptr) { return nullptr; } @@ -346,8 +346,11 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionChangeAvailableReactions::ID: { auto action = move_tl_object_as(action_ptr); - return td_api::make_object(std::move(action->prev_value_), - std::move(action->new_value_)); + ChatReactions old_available_reactions(std::move(action->prev_value_)); + ChatReactions new_available_reactions(std::move(action->new_value_)); + return td_api::make_object( + old_available_reactions.get_chat_available_reactions_object(), + new_available_reactions.get_chat_available_reactions_object()); } default: UNREACHABLE(); diff --git a/td/telegram/DialogListId.h b/td/telegram/DialogListId.h index 4f8737929..79bc5960a 100644 --- a/td/telegram/DialogListId.h +++ b/td/telegram/DialogListId.h @@ -120,7 +120,14 @@ struct DialogListIdHash { inline StringBuilder &operator<<(StringBuilder &string_builder, DialogListId dialog_list_id) { if (dialog_list_id.is_folder()) { - return string_builder << "chat list " << dialog_list_id.get_folder_id(); + auto folder_id = dialog_list_id.get_folder_id(); + if (folder_id == FolderId::archive()) { + return string_builder << "Archive chat list"; + } + if (folder_id == FolderId::main()) { + return string_builder << "Main chat list"; + } + return string_builder << "chat list " << folder_id; } if (dialog_list_id.is_filter()) { return string_builder << "chat list " << dialog_list_id.get_filter_id(); diff --git a/td/telegram/DownloadManager.cpp b/td/telegram/DownloadManager.cpp index 8859cc4c2..1c4628a2b 100644 --- a/td/telegram/DownloadManager.cpp +++ b/td/telegram/DownloadManager.cpp @@ -110,7 +110,7 @@ class DownloadManagerImpl final : public DownloadManager { } void remove_file(FileId file_id, FileSourceId file_source_id, bool delete_from_cache, Promise promise) final { - promise.set_result(remove_file_impl(file_id, file_source_id, delete_from_cache)); + promise.set_result(remove_file_impl(file_id, file_source_id, delete_from_cache, "remove_file")); } void remove_file_if_finished(FileId file_id) final { @@ -131,7 +131,7 @@ class DownloadManagerImpl final : public DownloadManager { to_remove.push_back(file_info.file_id); } for (auto file_id : to_remove) { - remove_file_impl(file_id, {}, delete_from_cache); + remove_file_impl(file_id, {}, delete_from_cache, "remove_all_files"); } promise.set_value(Unit()); } @@ -140,7 +140,7 @@ class DownloadManagerImpl final : public DownloadManager { Promise> promise) final { TRY_STATUS_PROMISE(promise, check_is_active("add_file")); - remove_file_impl(file_id, {}, false); + remove_file_impl(file_id, {}, false, "add_file"); auto download_id = next_download_id(); @@ -301,19 +301,6 @@ class DownloadManagerImpl final : public DownloadManager { } } - void update_file_deleted(FileId internal_file_id) final { - if (!callback_ || !is_database_loaded_) { - return; - } - - auto r_file_info_ptr = get_file_info_by_internal(internal_file_id); - if (r_file_info_ptr.is_error()) { - return; - } - auto &file_info = *r_file_info_ptr.ok(); - remove_file_impl(file_info.file_id, {}, false); - } - void update_file_viewed(FileId file_id, FileSourceId file_source_id) final { if (unviewed_completed_download_ids_.empty() || !callback_ || !is_database_loaded_) { return; @@ -517,7 +504,7 @@ class DownloadManagerImpl final : public DownloadManager { if (r_search_text.is_error()) { if (!G()->close_flag()) { - remove_file_impl(it->second->file_id, {}, false); + remove_file_impl(it->second->file_id, {}, false, "add_download_to_hints"); } } else { auto search_text = r_search_text.move_as_ok(); @@ -572,9 +559,9 @@ class DownloadManagerImpl final : public DownloadManager { } } - Status remove_file_impl(FileId file_id, FileSourceId file_source_id, bool delete_from_cache) { + Status remove_file_impl(FileId file_id, FileSourceId file_source_id, bool delete_from_cache, const char *source) { LOG(INFO) << "Remove from downloads file " << file_id << " from " << file_source_id; - TRY_STATUS(check_is_active("remove_file_impl")); + TRY_STATUS(check_is_active(source)); TRY_RESULT(file_info_ptr, get_file_info(file_id, file_source_id)); auto &file_info = *file_info_ptr; auto download_id = file_info.download_id; @@ -608,7 +595,7 @@ class DownloadManagerImpl final : public DownloadManager { if (!is_completed(*file_info_ptr)) { return Status::Error("File is active"); } - return remove_file_impl(file_id, {}, false); + return remove_file_impl(file_id, {}, false, "remove_file_if_finished_impl"); } void timeout_expired() final { @@ -775,11 +762,15 @@ class DownloadManagerImpl final : public DownloadManager { } void check_completed_downloads_size() { + if (!is_database_loaded_) { + return; + } + constexpr size_t MAX_COMPLETED_DOWNLOADS = 200; while (completed_download_ids_.size() > MAX_COMPLETED_DOWNLOADS) { auto download_id = *completed_download_ids_.begin(); auto file_info = get_file_info(download_id).move_as_ok(); - remove_file_impl(file_info->file_id, FileSourceId(), false); + remove_file_impl(file_info->file_id, FileSourceId(), false, "check_completed_downloads_size"); } } diff --git a/td/telegram/DownloadManager.h b/td/telegram/DownloadManager.h index 51068e190..71624c611 100644 --- a/td/telegram/DownloadManager.h +++ b/td/telegram/DownloadManager.h @@ -106,7 +106,6 @@ class DownloadManager : public Actor { virtual void remove_file_if_finished(FileId file_id) = 0; virtual void update_file_download_state(FileId internal_file_id, int64 downloaded_size, int64 size, int64 expected_size, bool is_paused) = 0; - virtual void update_file_deleted(FileId internal_file_id) = 0; virtual void update_file_viewed(FileId file_id, FileSourceId file_source_id) = 0; }; diff --git a/td/telegram/EmailVerification.cpp b/td/telegram/EmailVerification.cpp new file mode 100644 index 000000000..234190a02 --- /dev/null +++ b/td/telegram/EmailVerification.cpp @@ -0,0 +1,53 @@ +// +// 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/EmailVerification.h" + +#include "td/telegram/misc.h" + +namespace td { + +EmailVerification::EmailVerification(td_api::object_ptr &&code) { + if (code == nullptr) { + return; + } + switch (code->get_id()) { + case td_api::emailAddressAuthenticationCode::ID: + type_ = Type::Code; + code_ = static_cast(code.get())->code_; + break; + case td_api::emailAddressAuthenticationAppleId::ID: + type_ = Type::Apple; + code_ = static_cast(code.get())->token_; + break; + case td_api::emailAddressAuthenticationGoogleId::ID: + type_ = Type::Google; + code_ = static_cast(code.get())->token_; + break; + default: + UNREACHABLE(); + break; + } + if (!clean_input_string(code_)) { + *this = {}; + } +} + +telegram_api::object_ptr EmailVerification::get_input_email_verification() const { + switch (type_) { + case Type::Code: + return telegram_api::make_object(code_); + case Type::Apple: + return telegram_api::make_object(code_); + case Type::Google: + return telegram_api::make_object(code_); + default: + UNREACHABLE(); + return nullptr; + } +} + +} // namespace td diff --git a/td/telegram/EmailVerification.h b/td/telegram/EmailVerification.h new file mode 100644 index 000000000..abd4d8ed3 --- /dev/null +++ b/td/telegram/EmailVerification.h @@ -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/telegram/telegram_api.h" + +#include "td/utils/common.h" + +namespace td { + +class EmailVerification { + enum class Type : int32 { None, Code, Apple, Google }; + Type type_ = Type::None; + string code_; + + public: + EmailVerification() = default; + + explicit EmailVerification(td_api::object_ptr &&code); + + telegram_api::object_ptr get_input_email_verification() const; + + bool is_empty() const { + return type_ == Type::None; + } + + bool is_email_code() const { + return type_ == Type::Code; + } +}; + +} // namespace td diff --git a/td/telegram/EmojiStatus.cpp b/td/telegram/EmojiStatus.cpp new file mode 100644 index 000000000..00de725b6 --- /dev/null +++ b/td/telegram/EmojiStatus.cpp @@ -0,0 +1,327 @@ +// +// 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/EmojiStatus.h" + +#include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/StickersManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/Status.h" + +namespace td { + +struct EmojiStatuses { + int64 hash_ = 0; + vector emoji_statuses_; + + td_api::object_ptr get_emoji_statuses_object() const { + auto emoji_statuses = transform(emoji_statuses_, [](const EmojiStatus &emoji_status) { + CHECK(!emoji_status.is_empty()); + return emoji_status.get_emoji_status_object(); + }); + + return td_api::make_object(std::move(emoji_statuses)); + } + + EmojiStatuses() = default; + + explicit EmojiStatuses(tl_object_ptr &&emoji_statuses) { + CHECK(emoji_statuses != nullptr); + hash_ = emoji_statuses->hash_; + for (auto &status : emoji_statuses->statuses_) { + EmojiStatus emoji_status(std::move(status)); + if (emoji_status.is_empty()) { + LOG(ERROR) << "Receive empty emoji status"; + continue; + } + if (emoji_status.get_until_date() != 0) { + LOG(ERROR) << "Receive temporary emoji status"; + emoji_status.clear_until_date(); + } + emoji_statuses_.push_back(emoji_status); + } + } + + template + void store(StorerT &storer) const { + td::store(hash_, storer); + td::store(emoji_statuses_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(hash_, parser); + td::parse(emoji_statuses_, parser); + } +}; + +static const string &get_default_emoji_statuses_database_key() { + static string key = "def_emoji_statuses"; + return key; +} + +static const string &get_recent_emoji_statuses_database_key() { + static string key = "rec_emoji_statuses"; + return key; +} + +static EmojiStatuses load_emoji_statuses(const string &key) { + EmojiStatuses result; + auto log_event_string = G()->td_db()->get_binlog_pmc()->get(key); + if (!log_event_string.empty()) { + log_event_parse(result, log_event_string).ensure(); + } else { + result.hash_ = -1; + } + return result; +} + +static void save_emoji_statuses(const string &key, const EmojiStatuses &emoji_statuses) { + G()->td_db()->get_binlog_pmc()->set(key, log_event_store(emoji_statuses).as_slice().str()); +} + +class GetDefaultEmojiStatusesQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetDefaultEmojiStatusesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::account_getDefaultEmojiStatuses(hash), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto emoji_statuses_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetDefaultEmojiStatusesQuery: " << to_string(emoji_statuses_ptr); + + if (emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatusesNotModified::ID) { + if (promise_) { + promise_.set_error(Status::Error(500, "Receive wrong server response")); + } + return; + } + + CHECK(emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatuses::ID); + EmojiStatuses emoji_statuses(move_tl_object_as(emoji_statuses_ptr)); + save_emoji_statuses(get_default_emoji_statuses_database_key(), emoji_statuses); + + if (promise_) { + promise_.set_value(emoji_statuses.get_emoji_statuses_object()); + } + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetRecentEmojiStatusesQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetRecentEmojiStatusesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::account_getRecentEmojiStatuses(hash), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto emoji_statuses_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetRecentEmojiStatusesQuery: " << to_string(emoji_statuses_ptr); + + if (emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatusesNotModified::ID) { + if (promise_) { + promise_.set_error(Status::Error(500, "Receive wrong server response")); + } + return; + } + + CHECK(emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatuses::ID); + EmojiStatuses emoji_statuses(move_tl_object_as(emoji_statuses_ptr)); + save_emoji_statuses(get_recent_emoji_statuses_database_key(), emoji_statuses); + + if (promise_) { + promise_.set_value(emoji_statuses.get_emoji_statuses_object()); + } + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ClearRecentEmojiStatusesQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ClearRecentEmojiStatusesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::account_clearRecentEmojiStatuses(), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + save_emoji_statuses(get_recent_emoji_statuses_database_key(), EmojiStatuses()); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +EmojiStatus::EmojiStatus(const td_api::object_ptr &emoji_status, int32 duration) { + if (emoji_status == nullptr) { + return; + } + + custom_emoji_id_ = emoji_status->custom_emoji_id_; + if (duration != 0) { + int32 current_time = G()->unix_time(); + if (duration >= std::numeric_limits::max() - current_time) { + until_date_ = std::numeric_limits::max(); + } else { + until_date_ = current_time + duration; + } + } +} + +EmojiStatus::EmojiStatus(tl_object_ptr &&emoji_status) { + if (emoji_status == nullptr) { + return; + } + switch (emoji_status->get_id()) { + case telegram_api::emojiStatusEmpty::ID: + break; + case telegram_api::emojiStatus::ID: { + auto status = static_cast(emoji_status.get()); + custom_emoji_id_ = status->document_id_; + break; + } + case telegram_api::emojiStatusUntil::ID: { + auto status = static_cast(emoji_status.get()); + custom_emoji_id_ = status->document_id_; + until_date_ = status->until_; + break; + } + default: + UNREACHABLE(); + } +} + +tl_object_ptr EmojiStatus::get_input_emoji_status() const { + if (is_empty()) { + return make_tl_object(); + } + if (until_date_ != 0) { + return make_tl_object(custom_emoji_id_, until_date_); + } + return make_tl_object(custom_emoji_id_); +} + +td_api::object_ptr EmojiStatus::get_emoji_status_object() const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(custom_emoji_id_); +} + +int64 EmojiStatus::get_effective_custom_emoji_id(bool is_premium, int32 unix_time) const { + if (!is_premium) { + return 0; + } + if (until_date_ != 0 && until_date_ <= unix_time) { + return 0; + } + return custom_emoji_id_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &emoji_status) { + if (emoji_status.is_empty()) { + return string_builder << "DefaultProfileBadge"; + } + string_builder << "CustomEmoji " << emoji_status.custom_emoji_id_; + if (emoji_status.until_date_ != 0) { + string_builder << " until " << emoji_status.until_date_; + } + return string_builder; +} + +void get_default_emoji_statuses(Td *td, Promise> &&promise) { + auto statuses = load_emoji_statuses(get_default_emoji_statuses_database_key()); + if (statuses.hash_ != -1 && promise) { + promise.set_value(statuses.get_emoji_statuses_object()); + promise = Promise>(); + } + td->create_handler(std::move(promise))->send(statuses.hash_); +} + +void get_recent_emoji_statuses(Td *td, Promise> &&promise) { + auto statuses = load_emoji_statuses(get_recent_emoji_statuses_database_key()); + if (statuses.hash_ != -1 && promise) { + promise.set_value(statuses.get_emoji_statuses_object()); + promise = Promise>(); + } + td->create_handler(std::move(promise))->send(statuses.hash_); +} + +void add_recent_emoji_status(Td *td, EmojiStatus emoji_status) { + if (emoji_status.is_empty()) { + return; + } + + if (td->stickers_manager_->is_default_emoji_status(emoji_status.get_custom_emoji_id())) { + LOG(INFO) << "Skip adding themed emoji status to recents"; + return; + } + + emoji_status.clear_until_date(); + auto statuses = load_emoji_statuses(get_recent_emoji_statuses_database_key()); + if (!statuses.emoji_statuses_.empty() && statuses.emoji_statuses_[0] == emoji_status) { + return; + } + + statuses.hash_ = 0; + td::remove(statuses.emoji_statuses_, emoji_status); + statuses.emoji_statuses_.insert(statuses.emoji_statuses_.begin(), emoji_status); + constexpr size_t MAX_RECENT_EMOJI_STATUSES = 50; // server-side limit + if (statuses.emoji_statuses_.size() > MAX_RECENT_EMOJI_STATUSES) { + statuses.emoji_statuses_.resize(MAX_RECENT_EMOJI_STATUSES); + } + save_emoji_statuses(get_recent_emoji_statuses_database_key(), statuses); +} + +void clear_recent_emoji_statuses(Td *td, Promise &&promise) { + save_emoji_statuses(get_recent_emoji_statuses_database_key(), EmojiStatuses()); + td->create_handler(std::move(promise))->send(); +} + +} // namespace td diff --git a/td/telegram/EmojiStatus.h b/td/telegram/EmojiStatus.h new file mode 100644 index 000000000..877ea04af --- /dev/null +++ b/td/telegram/EmojiStatus.h @@ -0,0 +1,109 @@ +// +// 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/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class Td; + +class EmojiStatus { + int64 custom_emoji_id_ = 0; + int32 until_date_ = 0; + + friend bool operator==(const EmojiStatus &lhs, const EmojiStatus &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &contact); + + public: + EmojiStatus() = default; + + EmojiStatus(const td_api::object_ptr &emoji_status, int32 duration); + + explicit EmojiStatus(tl_object_ptr &&emoji_status); + + tl_object_ptr get_input_emoji_status() const; + + td_api::object_ptr get_emoji_status_object() const; + + int64 get_effective_custom_emoji_id(bool is_premium, int32 unix_time) const; + + bool is_empty() const { + return custom_emoji_id_ == 0; + } + + int64 get_custom_emoji_id() const { + return custom_emoji_id_; + } + + int32 get_until_date() const { + return until_date_; + } + + void clear_until_date() { + until_date_ = 0; + } + + template + void store(StorerT &storer) const { + bool has_custom_emoji_id = custom_emoji_id_ != 0; + bool has_until_date = until_date_ != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_custom_emoji_id); + STORE_FLAG(has_until_date); + END_STORE_FLAGS(); + if (has_custom_emoji_id) { + td::store(custom_emoji_id_, storer); + } + if (has_until_date) { + td::store(until_date_, storer); + } + } + + template + void parse(ParserT &parser) { + bool has_custom_emoji_id; + bool has_until_date; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_custom_emoji_id); + PARSE_FLAG(has_until_date); + END_PARSE_FLAGS(); + if (has_custom_emoji_id) { + td::parse(custom_emoji_id_, parser); + } + if (has_until_date) { + td::parse(until_date_, parser); + } + } +}; + +inline bool operator==(const EmojiStatus &lhs, const EmojiStatus &rhs) { + return lhs.custom_emoji_id_ == rhs.custom_emoji_id_ && lhs.until_date_ == rhs.until_date_; +} + +inline bool operator!=(const EmojiStatus &lhs, const EmojiStatus &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &emoji_status); + +void get_default_emoji_statuses(Td *td, Promise> &&promise); + +void get_recent_emoji_statuses(Td *td, Promise> &&promise); + +void add_recent_emoji_status(Td *td, EmojiStatus emoji_status); + +void clear_recent_emoji_statuses(Td *td, Promise &&promise); + +} // namespace td diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index 523d1965b..0f969d9d4 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -175,7 +175,7 @@ class RequestSimpleWebViewQuery final : public Td::ResultHandler { } void send(tl_object_ptr &&input_user, const string &url, - const td_api::object_ptr &theme) { + const td_api::object_ptr &theme, string &&platform) { tl_object_ptr theme_parameters; int32 flags = 0; if (theme != nullptr) { @@ -184,8 +184,8 @@ class RequestSimpleWebViewQuery final : public Td::ResultHandler { theme_parameters = make_tl_object(string()); theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false); } - send_query(G()->net_query_creator().create( - telegram_api::messages_requestSimpleWebView(flags, std::move(input_user), url, std::move(theme_parameters)))); + send_query(G()->net_query_creator().create(telegram_api::messages_requestSimpleWebView( + flags, std::move(input_user), url, std::move(theme_parameters), platform))); } void on_result(BufferSlice packet) final { @@ -347,7 +347,7 @@ Result> InlineQueriesManager: tl_object_ptr &&input_message_content, tl_object_ptr &&reply_markup_ptr, int32 allowed_media_content_id) const { if (input_message_content == nullptr) { - return Status::Error(400, "Inline message can't be empty"); + return Status::Error(400, "Inline message must be non-empty"); } TRY_RESULT(reply_markup, get_reply_markup(std::move(reply_markup_ptr), true, true, false, true)); auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), reply_markup); @@ -500,11 +500,12 @@ void InlineQueriesManager::answer_inline_query( void InlineQueriesManager::get_simple_web_view_url(UserId bot_user_id, string &&url, const td_api::object_ptr &theme, - Promise &&promise) { + string &&platform, Promise &&promise) { TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); - td_->create_handler(std::move(promise))->send(std::move(input_user), url, theme); + td_->create_handler(std::move(promise)) + ->send(std::move(input_user), url, theme, std::move(platform)); } void InlineQueriesManager::send_web_view_data(UserId bot_user_id, string &&button_text, string &&data, diff --git a/td/telegram/InlineQueriesManager.h b/td/telegram/InlineQueriesManager.h index 396e6911b..bdd0ba334 100644 --- a/td/telegram/InlineQueriesManager.h +++ b/td/telegram/InlineQueriesManager.h @@ -48,7 +48,8 @@ class InlineQueriesManager final : public Actor { Promise &&promise) const; void get_simple_web_view_url(UserId bot_user_id, string &&url, - const td_api::object_ptr &theme, Promise &&promise); + const td_api::object_ptr &theme, string &&platform, + Promise &&promise); void send_web_view_data(UserId bot_user_id, string &&button_text, string &&data, Promise &&promise) const; diff --git a/td/telegram/InputDialogId.cpp b/td/telegram/InputDialogId.cpp index 3ffb79873..9ba53e3dc 100644 --- a/td/telegram/InputDialogId.cpp +++ b/td/telegram/InputDialogId.cpp @@ -10,6 +10,7 @@ #include "td/telegram/ChatId.h" #include "td/telegram/UserId.h" +#include "td/utils/algorithm.h" #include "td/utils/logging.h" namespace td { @@ -70,6 +71,10 @@ vector InputDialogId::get_input_dialog_ids( return result; } +vector InputDialogId::get_dialog_ids(const vector &input_dialog_ids) { + return transform(input_dialog_ids, [](InputDialogId input_dialog_id) { return input_dialog_id.get_dialog_id(); }); +} + vector> InputDialogId::get_input_dialog_peers( const vector &input_dialog_ids) { vector> result; @@ -143,4 +148,10 @@ bool InputDialogId::contains(const vector &input_dialog_ids, Dial return false; } +bool InputDialogId::remove(vector &input_dialog_ids, DialogId dialog_id) { + return td::remove_if(input_dialog_ids, [dialog_id](InputDialogId input_dialog_id) { + return input_dialog_id.get_dialog_id() == dialog_id; + }); +} + } // namespace td diff --git a/td/telegram/InputDialogId.h b/td/telegram/InputDialogId.h index ac03ad0c3..ed3a87968 100644 --- a/td/telegram/InputDialogId.h +++ b/td/telegram/InputDialogId.h @@ -30,6 +30,8 @@ class InputDialogId { static vector get_input_dialog_ids(const vector> &input_peers, FlatHashSet *added_dialog_ids = nullptr); + static vector get_dialog_ids(const vector &input_dialog_ids); + static vector> get_input_dialog_peers( const vector &input_dialog_ids); @@ -40,6 +42,8 @@ class InputDialogId { static bool contains(const vector &input_dialog_ids, DialogId dialog_id); + static bool remove(vector &input_dialog_ids, DialogId dialog_id); + bool operator==(const InputDialogId &other) const { return dialog_id == other.dialog_id && access_hash == other.access_hash; } diff --git a/td/telegram/JsonValue.cpp b/td/telegram/JsonValue.cpp index 52f8712c5..db67dd50b 100644 --- a/td/telegram/JsonValue.cpp +++ b/td/telegram/JsonValue.cpp @@ -217,6 +217,15 @@ int32 get_json_value_int(telegram_api::object_ptr &&jso return 0; } +int64 get_json_value_long(telegram_api::object_ptr &&json_value, Slice name) { + CHECK(json_value != nullptr); + if (json_value->get_id() == telegram_api::jsonString::ID) { + return to_integer(static_cast(json_value.get())->value_); + } + LOG(ERROR) << "Expected Long as " << name << ", but found " << to_string(json_value); + return 0; +} + double get_json_value_double(telegram_api::object_ptr &&json_value, Slice name) { CHECK(json_value != nullptr); if (json_value->get_id() == telegram_api::jsonNumber::ID) { diff --git a/td/telegram/JsonValue.h b/td/telegram/JsonValue.h index d3231b868..a84aa3215 100644 --- a/td/telegram/JsonValue.h +++ b/td/telegram/JsonValue.h @@ -30,6 +30,8 @@ bool get_json_value_bool(telegram_api::object_ptr &&jso int32 get_json_value_int(telegram_api::object_ptr &&json_value, Slice name); +int64 get_json_value_long(telegram_api::object_ptr &&json_value, Slice name); + double get_json_value_double(telegram_api::object_ptr &&json_value, Slice name); string get_json_value_string(telegram_api::object_ptr &&json_value, Slice name); diff --git a/td/telegram/LanguagePackManager.cpp b/td/telegram/LanguagePackManager.cpp index 19122457d..cef868df9 100644 --- a/td/telegram/LanguagePackManager.cpp +++ b/td/telegram/LanguagePackManager.cpp @@ -7,7 +7,6 @@ #include "td/telegram/LanguagePackManager.h" #include "td/telegram/Global.h" -#include "td/telegram/logevent/LogEvent.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/Td.h" diff --git a/td/telegram/LinkManager.cpp b/td/telegram/LinkManager.cpp index 6886df6ec..a62524317 100644 --- a/td/telegram/LinkManager.cpp +++ b/td/telegram/LinkManager.cpp @@ -337,6 +337,18 @@ class LinkManager::InternalLinkGame final : public InternalLink { } }; +class LinkManager::InternalLinkInstantView final : public InternalLink { + string url_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(url_); + } + + public: + explicit InternalLinkInstantView(string url) : url_(std::move(url)) { + } +}; + class LinkManager::InternalLinkInvoice final : public InternalLink { string invoice_name_; @@ -876,8 +888,7 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { return result; } - result.is_internal_ = true; - result.is_tg_ = true; + result.type_ = LinkType::Tg; result.query_ = link.str(); return result; } else { @@ -885,9 +896,27 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { return result; } + auto host = url_decode(http_url.host_, false); + to_lower_inplace(host); + if (ends_with(host, ".t.me") && host.size() >= 9 && host.find('.') == host.size() - 5) { + Slice subdomain(&host[0], host.size() - 5); + if (is_valid_username(subdomain) && subdomain != "addemoji" && subdomain != "addstickers" && + subdomain != "addtheme" && subdomain != "auth" && subdomain != "confirmphone" && subdomain != "invoice" && + subdomain != "joinchat" && subdomain != "login" && subdomain != "proxy" && subdomain != "setlanguage" && + subdomain != "share" && subdomain != "socks") { + result.type_ = LinkType::TMe; + result.query_ = PSTRING() << '/' << subdomain << http_url.query_; + return result; + } + } + if (begins_with(host, "www.")) { + host = host.substr(4); + } + + string cur_t_me_url; vector t_me_urls{Slice("t.me"), Slice("telegram.me"), Slice("telegram.dog")}; if (Scheduler::context() != nullptr) { // for tests only - string cur_t_me_url = G()->get_option_string("t_me_url"); + cur_t_me_url = G()->get_option_string("t_me_url"); if (tolower_begins_with(cur_t_me_url, "http://") || tolower_begins_with(cur_t_me_url, "https://")) { Slice t_me_url = cur_t_me_url; t_me_url = t_me_url.substr(t_me_url[4] == 's' ? 8 : 7); @@ -897,16 +926,9 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { } } - auto host = url_decode(http_url.host_, false); - to_lower_inplace(host); - if (begins_with(host, "www.")) { - host = host.substr(4); - } - for (auto t_me_url : t_me_urls) { if (host == t_me_url) { - result.is_internal_ = true; - result.is_tg_ = false; + result.type_ = LinkType::TMe; Slice query = http_url.query_; while (true) { @@ -924,24 +946,39 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { return result; } } + + if (http_url.query_.size() > 1) { + for (auto telegraph_url : {Slice("telegra.ph"), Slice("te.legra.ph"), Slice("graph.org")}) { + if (host == telegraph_url) { + result.type_ = LinkType::Telegraph; + result.query_ = std::move(http_url.query_); + return result; + } + } + } } return result; } bool LinkManager::is_internal_link(Slice link) { auto info = get_link_info(link); - return info.is_internal_; + return info.type_ != LinkType::External; } unique_ptr LinkManager::parse_internal_link(Slice link, bool is_trusted) { auto info = get_link_info(link); - if (!info.is_internal_) { - return nullptr; - } - if (info.is_tg_) { - return parse_tg_link_query(info.query_, is_trusted); - } else { - return parse_t_me_link_query(info.query_, is_trusted); + switch (info.type_) { + case LinkType::External: + return nullptr; + case LinkType::Tg: + return parse_tg_link_query(info.query_, is_trusted); + case LinkType::TMe: + return parse_t_me_link_query(info.query_, is_trusted); + case LinkType::Telegraph: + return td::make_unique(PSTRING() << "https://telegra.ph" << info.query_); + default: + UNREACHABLE(); + return nullptr; } } @@ -1324,6 +1361,12 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q // /share/url?url=&text= return get_internal_link_message_draft(get_arg("url"), get_arg("text")); } + } else if (path[0] == "iv") { + if (path.size() == 1 && has_arg("url")) { + // /iv?url=&rhash= + return td::make_unique(PSTRING() + << "https://t.me/iv" << copy_arg("url") << copy_arg("rhash")); + } } else if (is_valid_username(path[0])) { if (path.size() >= 2 && to_integer(path[1]) > 0) { // //12345?single&thread=&comment=&t= @@ -1554,11 +1597,11 @@ void LinkManager::get_link_login_url(const string &url, bool allow_write_access, string LinkManager::get_dialog_invite_link_hash(Slice invite_link) { auto link_info = get_link_info(invite_link); - if (!link_info.is_internal_) { + if (link_info.type_ != LinkType::Tg && link_info.type_ != LinkType::TMe) { return string(); } const auto url_query = parse_url_query(link_info.query_); - return get_url_query_hash(link_info.is_tg_, url_query); + return get_url_query_hash(link_info.type_ == LinkType::Tg, url_query); } string LinkManager::get_dialog_invite_link(Slice hash, bool is_internal) { @@ -1661,7 +1704,7 @@ Result LinkManager::get_message_link_info(Slice url) { return Status::Error("URL must be non-empty"); } auto link_info = get_link_info(url); - if (!link_info.is_internal_) { + if (link_info.type_ != LinkType::Tg && link_info.type_ != LinkType::TMe) { return Status::Error("Invalid message link URL"); } url = link_info.query_; @@ -1673,7 +1716,7 @@ Result LinkManager::get_message_link_info(Slice url) { Slice media_timestamp_slice; bool is_single = false; bool for_comment = false; - if (link_info.is_tg_) { + if (link_info.type_ == LinkType::Tg) { // resolve?domain=username&post=12345&single&t=123&comment=12&thread=21 // privatepost?channel=123456789&post=12345&single&t=123&comment=12&thread=21 diff --git a/td/telegram/LinkManager.h b/td/telegram/LinkManager.h index 579fcba97..bcd284bb3 100644 --- a/td/telegram/LinkManager.h +++ b/td/telegram/LinkManager.h @@ -101,6 +101,7 @@ class LinkManager final : public Actor { class InternalLinkDialogInvite; class InternalLinkFilterSettings; class InternalLinkGame; + class InternalLinkInstantView; class InternalLinkInvoice; class InternalLinkLanguage; class InternalLinkLanguageSettings; @@ -122,9 +123,10 @@ class LinkManager final : public Actor { class InternalLinkUserPhoneNumber; class InternalLinkVoiceChat; + enum class LinkType : int32 { External, TMe, Tg, Telegraph }; + struct LinkInfo { - bool is_internal_ = false; - bool is_tg_ = false; + LinkType type_ = LinkType::External; string query_; }; // returns information about the link diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index 0b6d09170..a8b71f5ea 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -2080,14 +2080,14 @@ Result get_input_message_content( auto input_message = static_cast(input_message_content.get()); auto file_type = input_message->disable_content_type_detection_ ? FileType::DocumentAsFile : FileType::Document; r_file_id = - td->file_manager_->get_input_file_id(file_type, input_message->document_, dialog_id, false, is_secret, true); + td->file_manager_->get_input_file_id(file_type, input_message->document_, dialog_id, false, is_secret); input_thumbnail = std::move(input_message->thumbnail_); break; } case td_api::inputMessagePhoto::ID: { auto input_message = static_cast(input_message_content.get()); - r_file_id = - td->file_manager_->get_input_file_id(FileType::Photo, input_message->photo_, dialog_id, false, is_secret); + r_file_id = td->file_manager_->get_input_file_id(FileType::Photo, input_message->photo_, dialog_id, false, + is_secret, false, false, true); input_thumbnail = std::move(input_message->thumbnail_); if (!input_message->added_sticker_file_ids_.empty()) { sticker_file_ids = td->stickers_manager_->get_attached_sticker_file_ids(input_message->added_sticker_file_ids_); @@ -2794,7 +2794,7 @@ bool can_forward_message_content(const MessageContent *content) { auto content_type = content->get_type(); if (content_type == MessageContentType::Text) { auto *text = static_cast(content); - return !is_empty_string(text->text.text); // text can't be empty in the new message + return !is_empty_string(text->text.text); // text must be non-empty in the new message } if (content_type == MessageContentType::Poll) { auto *poll = static_cast(content); @@ -4686,7 +4686,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) { remove_unallowed_entities(td, result->text, dialog_id); } - return result; + return std::move(result); } case MessageContentType::Venue: return make_unique(*static_cast(content)); @@ -5937,15 +5937,37 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC void on_sent_message_content(Td *td, const MessageContent *content) { switch (content->get_type()) { case MessageContentType::Animation: - return td->animations_manager_->add_saved_animation_by_id(get_message_content_any_file_id(content)); + return td->animations_manager_->add_saved_animation_by_id(get_message_content_upload_file_id(content)); case MessageContentType::Sticker: - return td->stickers_manager_->add_recent_sticker_by_id(false, get_message_content_any_file_id(content)); + return td->stickers_manager_->add_recent_sticker_by_id(false, get_message_content_upload_file_id(content)); default: // nothing to do return; } } +void move_message_content_sticker_set_to_top(Td *td, const MessageContent *content) { + CHECK(content != nullptr); + if (content->get_type() == MessageContentType::Sticker) { + td->stickers_manager_->move_sticker_set_to_top_by_sticker_id(get_message_content_upload_file_id(content)); + return; + } + + auto text = get_message_content_text(content); + if (text == nullptr) { + return; + } + vector custom_emoji_ids; + for (auto &entity : text->entities) { + if (entity.type == MessageEntity::Type::CustomEmoji) { + custom_emoji_ids.push_back(entity.document_id); + } + } + if (!custom_emoji_ids.empty()) { + td->stickers_manager_->move_sticker_set_to_top_by_custom_emoji_ids(custom_emoji_ids); + } +} + bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogAction &action) { auto emoji = action.get_watching_animations_emoji(); if (emoji.empty()) { diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index cde9f3f2f..9e7c6f6bd 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -246,6 +246,8 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC void on_sent_message_content(Td *td, const MessageContent *content); +void move_message_content_sticker_set_to_top(Td *td, const MessageContent *content); + bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogAction &action); void init_stickers_manager(Td *td); diff --git a/td/telegram/MessageReaction.cpp b/td/telegram/MessageReaction.cpp index ca6a429c4..2678bfc52 100644 --- a/td/telegram/MessageReaction.cpp +++ b/td/telegram/MessageReaction.cpp @@ -12,6 +12,7 @@ #include "td/telegram/Global.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/misc.h" #include "td/telegram/OptionManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StickersManager.h" @@ -21,16 +22,103 @@ #include "td/actor/actor.h" #include "td/utils/algorithm.h" +#include "td/utils/as.h" +#include "td/utils/base64.h" #include "td/utils/buffer.h" +#include "td/utils/crypto.h" +#include "td/utils/emoji.h" #include "td/utils/FlatHashSet.h" #include "td/utils/logging.h" #include "td/utils/Status.h" +#include "td/utils/utf8.h" #include #include namespace td { +static size_t get_max_reaction_count() { + bool is_premium = G()->get_option_boolean("is_premium"); + auto option_key = is_premium ? Slice("reactions_user_max_premium") : Slice("reactions_user_max_default"); + return static_cast( + max(static_cast(1), static_cast(G()->get_option_integer(option_key, is_premium ? 3 : 1)))); +} + +static int64 get_custom_emoji_id(const string &reaction) { + auto r_decoded = base64_decode(Slice(&reaction[1], reaction.size() - 1)); + CHECK(r_decoded.is_ok()); + CHECK(r_decoded.ok().size() == 8); + return as(r_decoded.ok().c_str()); +} + +static string get_custom_emoji_string(int64 custom_emoji_id) { + char s[8]; + as(&s) = custom_emoji_id; + return PSTRING() << '#' << base64_encode(Slice(s, 8)); +} + +telegram_api::object_ptr get_input_reaction(const string &reaction) { + if (reaction.empty()) { + return telegram_api::make_object(); + } + if (is_custom_reaction(reaction)) { + return telegram_api::make_object(get_custom_emoji_id(reaction)); + } + return telegram_api::make_object(reaction); +} + +string get_message_reaction_string(const telegram_api::object_ptr &reaction) { + if (reaction == nullptr) { + return string(); + } + switch (reaction->get_id()) { + case telegram_api::reactionEmpty::ID: + return string(); + case telegram_api::reactionEmoji::ID: { + const string &emoji = static_cast(reaction.get())->emoticon_; + if (is_custom_reaction(emoji)) { + return string(); + } + return emoji; + } + case telegram_api::reactionCustomEmoji::ID: + return get_custom_emoji_string( + static_cast(reaction.get())->document_id_); + default: + UNREACHABLE(); + return string(); + } +} + +td_api::object_ptr get_reaction_type_object(const string &reaction) { + CHECK(!reaction.empty()); + if (is_custom_reaction(reaction)) { + return td_api::make_object(get_custom_emoji_id(reaction)); + } + return td_api::make_object(reaction); +} + +string get_message_reaction_string(const td_api::object_ptr &type) { + if (type == nullptr) { + return string(); + } + switch (type->get_id()) { + case td_api::reactionTypeEmoji::ID: { + const string &emoji = static_cast(type.get())->emoji_; + if (!check_utf8(emoji) || is_custom_reaction(emoji)) { + return string(); + } + return emoji; + } + case td_api::reactionTypeCustomEmoji::ID: + return get_custom_emoji_string( + static_cast(type.get())->custom_emoji_id_); + default: + UNREACHABLE(); + return string(); + } +} + class GetMessagesReactionsQuery final : public Td::ResultHandler { DialogId dialog_id_; vector message_ids_; @@ -91,7 +179,7 @@ class SendReactionQuery final : public Td::ResultHandler { explicit SendReactionQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(FullMessageId full_message_id, string reaction, bool is_big) { + void send(FullMessageId full_message_id, vector reactions, bool is_big, bool add_to_recent) { dialog_id_ = full_message_id.get_dialog_id(); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); @@ -100,17 +188,22 @@ class SendReactionQuery final : public Td::ResultHandler { } int32 flags = 0; - if (!reaction.empty()) { + if (!reactions.empty()) { flags |= telegram_api::messages_sendReaction::REACTION_MASK; if (is_big) { flags |= telegram_api::messages_sendReaction::BIG_MASK; } + + if (add_to_recent) { + flags |= telegram_api::messages_sendReaction::ADD_TO_RECENT_MASK; + } } send_query(G()->net_query_creator().create( - telegram_api::messages_sendReaction(flags, false /*ignored*/, std::move(input_peer), - full_message_id.get_message_id().get_server_message_id().get(), reaction), + telegram_api::messages_sendReaction(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), + full_message_id.get_message_id().get_server_message_id().get(), + transform(reactions, get_input_reaction)), {{dialog_id_}, {full_message_id}})); } @@ -163,8 +256,9 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { } 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), + telegram_api::messages_getMessageReactionsList(flags, std::move(input_peer), + message_id_.get_server_message_id().get(), + get_input_reaction(reaction_), offset_, limit), {{full_message_id}})); } @@ -191,19 +285,20 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { FlatHashMap> recent_reactions; for (const auto &reaction : ptr->reactions_) { DialogId dialog_id(reaction->peer_id_); - if (!dialog_id.is_valid() || - (reaction_.empty() ? reaction->reaction_.empty() : reaction_ != reaction->reaction_)) { + auto reaction_str = get_message_reaction_string(reaction->reaction_); + if (!dialog_id.is_valid() || (reaction_.empty() ? reaction_str.empty() : reaction_ != reaction_str)) { LOG(ERROR) << "Receive unexpected " << to_string(reaction); continue; } if (offset_.empty()) { - recent_reactions[reaction->reaction_].push_back(dialog_id); + recent_reactions[reaction_str].push_back(dialog_id); } auto message_sender = get_min_message_sender_object(td_, dialog_id, "GetMessageReactionsListQuery"); if (message_sender != nullptr) { - reactions.push_back(td_api::make_object(reaction->reaction_, std::move(message_sender))); + reactions.push_back(td_api::make_object(get_reaction_type_object(reaction_str), + std::move(message_sender))); } } @@ -228,7 +323,8 @@ class SetDefaultReactionQuery final : public Td::ResultHandler { public: void send(const string &reaction) { reaction_ = reaction; - send_query(G()->net_query_creator().create(telegram_api::messages_setDefaultReaction(reaction))); + send_query( + G()->net_query_creator().create(telegram_api::messages_setDefaultReaction(get_input_reaction(reaction)))); } void on_result(BufferSlice packet) final { @@ -242,8 +338,6 @@ class SetDefaultReactionQuery final : public Td::ResultHandler { } auto default_reaction = td_->option_manager_->get_option_string("default_reaction", "-"); - LOG(INFO) << "Successfully set reaction " << reaction_ << " as default, current default is " << default_reaction; - if (default_reaction != reaction_) { send_set_default_reaction_query(td_); } else { @@ -256,9 +350,47 @@ class SetDefaultReactionQuery final : public Td::ResultHandler { return; } - LOG(INFO) << "Failed to set default reaction: " << status; + LOG(INFO) << "Receive error for SetDefaultReactionQuery: " << status; td_->option_manager_->set_option_empty("default_reaction_needs_sync"); - send_closure(G()->config_manager(), &ConfigManager::reget_app_config, Promise()); + send_closure(G()->config_manager(), &ConfigManager::request_config, false); + } +}; + +class ReportReactionQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit ReportReactionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, MessageId message_id, DialogId chooser_dialog_id) { + dialog_id_ = dialog_id; + + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + + auto chooser_input_peer = td_->messages_manager_->get_input_peer(chooser_dialog_id, AccessRights::Know); + if (chooser_input_peer == nullptr) { + return promise_.set_error(Status::Error(400, "Reaction sender is not accessible")); + } + + send_query(G()->net_query_creator().create(telegram_api::messages_reportReaction( + std::move(input_peer), message_id.get_server_message_id().get(), std::move(chooser_input_peer)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportReactionQuery"); + promise_.set_error(std::move(status)); } }; @@ -290,7 +422,7 @@ void MessageReaction::update_recent_chooser_dialog_ids(const MessageReaction &ol recent_chooser_min_channels_ = old_reaction.recent_chooser_min_channels_; } -void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool can_get_added_reactions) { +void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool have_recent_choosers) { if (is_chosen_ == is_chosen) { return; } @@ -299,7 +431,7 @@ void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, if (chooser_dialog_id.is_valid()) { choose_count_ += is_chosen_ ? 1 : -1; - if (can_get_added_reactions) { + if (have_recent_choosers) { remove_recent_chooser_dialog_id(chooser_dialog_id); if (is_chosen_) { add_recent_chooser_dialog_id(chooser_dialog_id); @@ -308,20 +440,38 @@ void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, } } -td_api::object_ptr MessageReaction::get_message_reaction_object(Td *td) const { +td_api::object_ptr MessageReaction::get_message_reaction_object(Td *td, UserId my_user_id, + UserId peer_user_id) const { CHECK(!is_empty()); vector> 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)); - if (recent_choosers.size() == MAX_RECENT_CHOOSERS) { - break; + if (my_user_id.is_valid()) { + CHECK(peer_user_id.is_valid()); + if (is_chosen()) { + auto recent_chooser = get_min_message_sender_object(td, DialogId(my_user_id), "get_message_reaction_object"); + if (recent_chooser != nullptr) { + recent_choosers.push_back(std::move(recent_chooser)); + } + } + if (choose_count_ >= (is_chosen() ? 2 : 1)) { + auto recent_chooser = get_min_message_sender_object(td, DialogId(peer_user_id), "get_message_reaction_object"); + if (recent_chooser != nullptr) { + recent_choosers.push_back(std::move(recent_chooser)); + } + } + } else { + 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)); + if (recent_choosers.size() == MAX_RECENT_CHOOSERS) { + break; + } } } } - return td_api::make_object(reaction_, choose_count_, is_chosen_, std::move(recent_choosers)); + return td_api::make_object(get_reaction_type_object(reaction_), choose_count_, is_chosen_, + std::move(recent_choosers)); } bool operator==(const MessageReaction &lhs, const MessageReaction &rhs) { @@ -342,7 +492,8 @@ td_api::object_ptr UnreadMessageReaction::get_unread_rea if (sender_id == nullptr) { return nullptr; } - return td_api::make_object(reaction_, std::move(sender_id), is_big_); + return td_api::make_object(get_reaction_type_object(reaction_), std::move(sender_id), + is_big_); } bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs) { @@ -365,31 +516,33 @@ unique_ptr MessageReactions::get_message_reactions( result->is_min_ = reactions->min_; FlatHashSet reaction_strings; - FlatHashSet recent_choosers; + vector> chosen_reaction_order; for (auto &reaction_count : reactions->results_) { + auto reaction_str = get_message_reaction_string(reaction_count->reaction_); if (reaction_count->count_ <= 0 || reaction_count->count_ >= MessageReaction::MAX_CHOOSE_COUNT || - reaction_count->reaction_.empty()) { - LOG(ERROR) << "Receive reaction " << reaction_count->reaction_ << " with invalid count " - << reaction_count->count_; + reaction_str.empty()) { + LOG(ERROR) << "Receive reaction " << reaction_str << " with invalid count " << reaction_count->count_; continue; } - if (!reaction_strings.insert(reaction_count->reaction_).second) { - LOG(ERROR) << "Receive duplicate reaction " << reaction_count->reaction_; + if (!reaction_strings.insert(reaction_str).second) { + LOG(ERROR) << "Receive duplicate reaction " << reaction_str; continue; } + FlatHashSet recent_choosers; vector recent_chooser_dialog_ids; vector> recent_chooser_min_channels; for (auto &peer_reaction : reactions->recent_reactions_) { - if (peer_reaction->reaction_ == reaction_count->reaction_) { + auto peer_reaction_str = get_message_reaction_string(peer_reaction->reaction_); + if (peer_reaction_str == reaction_str) { DialogId dialog_id(peer_reaction->peer_id_); if (!dialog_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << dialog_id << " as a recent chooser"; + LOG(ERROR) << "Receive invalid " << dialog_id << " as a recent chooser for reaction " << reaction_str; continue; } if (!recent_choosers.insert(dialog_id).second) { - LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent chooser"; + LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent chooser for reaction " << reaction_str; continue; } if (!td->messages_manager_->have_dialog_info(dialog_id)) { @@ -416,7 +569,7 @@ unique_ptr MessageReactions::get_message_reactions( 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_); + result->unread_reactions_.emplace_back(std::move(peer_reaction_str), dialog_id, peer_reaction->big_); } if (recent_chooser_dialog_ids.size() == MessageReaction::MAX_RECENT_CHOOSERS) { break; @@ -424,9 +577,17 @@ unique_ptr MessageReactions::get_message_reactions( } } - 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)); + bool is_chosen = (reaction_count->flags_ & telegram_api::reactionCount::CHOSEN_ORDER_MASK) != 0; + if (is_chosen) { + chosen_reaction_order.emplace_back(reaction_count->chosen_order_, reaction_str); + } + result->reactions_.push_back({std::move(reaction_str), reaction_count->count_, is_chosen, + std::move(recent_chooser_dialog_ids), std::move(recent_chooser_min_channels)}); + } + if (chosen_reaction_order.size() > 1) { + std::sort(chosen_reaction_order.begin(), chosen_reaction_order.end()); + result->chosen_reaction_order_ = + transform(chosen_reaction_order, [](const std::pair &order) { return order.second; }); } return result; } @@ -462,6 +623,7 @@ void MessageReactions::update_from(const MessageReactions &old_reactions) { } } unread_reactions_ = old_reactions.unread_reactions_; + chosen_reaction_order_ = old_reactions.chosen_reaction_order_; } for (const auto &old_reaction : old_reactions.reactions_) { if (old_reaction.is_chosen() && @@ -474,6 +636,82 @@ void MessageReactions::update_from(const MessageReactions &old_reactions) { } } +bool MessageReactions::add_reaction(const string &reaction, bool is_big, DialogId chooser_dialog_id, + bool have_recent_choosers) { + vector new_chosen_reaction_order = get_chosen_reactions(); + + auto added_reaction = get_reaction(reaction); + if (added_reaction == nullptr) { + vector recent_chooser_dialog_ids; + if (have_recent_choosers) { + recent_chooser_dialog_ids.push_back(chooser_dialog_id); + } + reactions_.push_back({reaction, 1, true, std::move(recent_chooser_dialog_ids), Auto()}); + new_chosen_reaction_order.emplace_back(reaction); + } else if (!added_reaction->is_chosen()) { + added_reaction->set_is_chosen(true, chooser_dialog_id, have_recent_choosers); + new_chosen_reaction_order.emplace_back(reaction); + } else if (!is_big) { + return false; + } + + auto max_reaction_count = get_max_reaction_count(); + while (new_chosen_reaction_order.size() > max_reaction_count) { + auto index = new_chosen_reaction_order[0] == reaction ? 1 : 0; + CHECK(static_cast(index) < new_chosen_reaction_order.size()); + bool is_removed = do_remove_reaction(new_chosen_reaction_order[index], chooser_dialog_id, have_recent_choosers); + CHECK(is_removed); + new_chosen_reaction_order.erase(new_chosen_reaction_order.begin() + index); + } + + if (new_chosen_reaction_order.size() == 1) { + new_chosen_reaction_order.clear(); + } + chosen_reaction_order_ = std::move(new_chosen_reaction_order); + return true; +} + +bool MessageReactions::remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers) { + if (do_remove_reaction(reaction, chooser_dialog_id, have_recent_choosers)) { + if (!chosen_reaction_order_.empty()) { + bool is_removed = td::remove(chosen_reaction_order_, reaction); + CHECK(is_removed); + + auto max_reaction_count = get_max_reaction_count(); + while (chosen_reaction_order_.size() > max_reaction_count) { + is_removed = do_remove_reaction(chosen_reaction_order_[0], chooser_dialog_id, have_recent_choosers); + CHECK(is_removed); + chosen_reaction_order_.erase(chosen_reaction_order_.begin()); + } + + if (chosen_reaction_order_.size() <= 1) { + reset_to_empty(chosen_reaction_order_); + } + } + + return true; + } + return false; +} + +bool MessageReactions::do_remove_reaction(const string &reaction, DialogId chooser_dialog_id, + bool have_recent_choosers) { + for (auto it = reactions_.begin(); it != reactions_.end(); ++it) { + auto &message_reaction = *it; + if (message_reaction.get_reaction() == reaction) { + if (message_reaction.is_chosen()) { + message_reaction.set_is_chosen(false, chooser_dialog_id, have_recent_choosers); + if (message_reaction.is_empty()) { + it = reactions_.erase(it); + } + return true; + } + break; + } + } + return false; +} + void MessageReactions::sort_reactions(const FlatHashMap &active_reaction_pos) { std::sort(reactions_.begin(), reactions_.end(), [&active_reaction_pos](const MessageReaction &lhs, const MessageReaction &rhs) { @@ -510,6 +748,20 @@ void MessageReactions::fix_chosen_reaction(DialogId my_dialog_id) { } } +vector MessageReactions::get_chosen_reactions() const { + if (!chosen_reaction_order_.empty()) { + return chosen_reaction_order_; + } + + vector reaction_order; + for (auto &reaction : reactions_) { + if (reaction.is_chosen()) { + reaction_order.push_back(reaction.get_reaction()); + } + } + return reaction_order; +} + bool MessageReactions::need_update_message_reactions(const MessageReactions *old_reactions, const MessageReactions *new_reactions) { if (old_reactions == nullptr) { @@ -521,7 +773,7 @@ bool MessageReactions::need_update_message_reactions(const MessageReactions *old return true; } - // unread_reactions_ are updated independently; compare all other fields + // unread_reactions_ and chosen_reaction_order_ 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_; @@ -537,7 +789,8 @@ bool MessageReactions::need_update_unread_reactions(const MessageReactions *old_ StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions) { return string_builder << (reactions.is_min_ ? "Min" : "") << "MessageReactions{" << reactions.reactions_ - << " with unread " << reactions.unread_reactions_ + << " with unread " << reactions.unread_reactions_ << ", reaction order " + << reactions.chosen_reaction_order_ << " and can_get_added_reactions = " << reactions.can_get_added_reactions_; } @@ -548,6 +801,14 @@ StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr &active_reaction_pos) { + return !reaction.empty() && (is_custom_reaction(reaction) || active_reaction_pos.count(reaction) > 0); +} + void reload_message_reactions(Td *td, DialogId dialog_id, vector &&message_ids) { if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read) || message_ids.empty()) { return; @@ -561,9 +822,10 @@ void reload_message_reactions(Td *td, DialogId dialog_id, vector &&me td->create_handler()->send(dialog_id, std::move(message_ids)); } -void set_message_reaction(Td *td, FullMessageId full_message_id, string reaction, bool is_big, - Promise &&promise) { - td->create_handler(std::move(promise))->send(full_message_id, std::move(reaction), is_big); +void send_message_reaction(Td *td, FullMessageId full_message_id, vector reactions, bool is_big, + bool add_to_recent, Promise &&promise) { + td->create_handler(std::move(promise)) + ->send(full_message_id, std::move(reactions), is_big, add_to_recent); } void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit, @@ -591,7 +853,10 @@ void get_message_added_reactions(Td *td, FullMessageId full_message_id, string r } void set_default_reaction(Td *td, string reaction, Promise &&promise) { - if (!td->stickers_manager_->is_active_reaction(reaction)) { + if (reaction.empty()) { + return promise.set_error(Status::Error(400, "Default reaction must be non-empty")); + } + if (!is_custom_reaction(reaction) && !td->stickers_manager_->is_active_reaction(reaction)) { return promise.set_error(Status::Error(400, "Can't set incative reaction as default")); } @@ -599,6 +864,7 @@ void set_default_reaction(Td *td, string reaction, Promise &&promise) { td->option_manager_->set_option_string("default_reaction", reaction); if (!td->option_manager_->get_option_boolean("default_reaction_needs_sync")) { td->option_manager_->set_option_boolean("default_reaction_needs_sync", true); + send_set_default_reaction_query(td); } } promise.set_value(Unit()); @@ -608,4 +874,75 @@ void send_set_default_reaction_query(Td *td) { td->create_handler()->send(td->option_manager_->get_option_string("default_reaction")); } +void send_update_default_reaction_type(const string &default_reaction) { + if (default_reaction.empty()) { + LOG(ERROR) << "Have no default reaction"; + return; + } + send_closure(G()->td(), &Td::send_update, + td_api::make_object(get_reaction_type_object(default_reaction))); +} + +void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId chooser_dialog_id, + Promise &&promise) { + auto dialog_id = full_message_id.get_dialog_id(); + if (!td->messages_manager_->have_dialog_force(dialog_id, "send_callback_query")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the chat")); + } + + if (!td->messages_manager_->have_message_force(full_message_id, "report_user_reactions")) { + return promise.set_error(Status::Error(400, "Message not found")); + } + auto message_id = full_message_id.get_message_id(); + if (message_id.is_valid_scheduled()) { + return promise.set_error(Status::Error(400, "Can't report reactions on scheduled messages")); + } + if (!message_id.is_server()) { + return promise.set_error(Status::Error(400, "Message reactions can't be reported")); + } + + if (!td->messages_manager_->have_input_peer(chooser_dialog_id, AccessRights::Know)) { + return promise.set_error(Status::Error(400, "Reaction sender not found")); + } + + td->create_handler(std::move(promise))->send(dialog_id, message_id, chooser_dialog_id); +} + +vector get_recent_reactions(Td *td) { + return td->stickers_manager_->get_recent_reactions(); +} + +vector get_top_reactions(Td *td) { + return td->stickers_manager_->get_top_reactions(); +} + +void add_recent_reaction(Td *td, const string &reaction) { + td->stickers_manager_->add_recent_reaction(reaction); +} + +int64 get_reactions_hash(const vector &reactions) { + vector numbers; + for (auto &reaction : reactions) { + if (is_custom_reaction(reaction)) { + auto custom_emoji_id = static_cast(get_custom_emoji_id(reaction)); + numbers.push_back(custom_emoji_id >> 32); + numbers.push_back(custom_emoji_id & 0xFFFFFFFF); + } else { + auto emoji = remove_emoji_selectors(reaction); + unsigned char hash[16]; + md5(emoji, {hash, sizeof(hash)}); + auto get = [hash](int num) { + return static_cast(hash[num]); + }; + + numbers.push_back(0); + numbers.push_back(static_cast((get(0) << 24) + (get(1) << 16) + (get(2) << 8) + get(3))); + } + } + return get_vector_hash(numbers); +} + } // namespace td diff --git a/td/telegram/MessageReaction.h b/td/telegram/MessageReaction.h index e46593921..9051a8a99 100644 --- a/td/telegram/MessageReaction.h +++ b/td/telegram/MessageReaction.h @@ -36,11 +36,7 @@ class MessageReaction { 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; + friend struct MessageReactions; MessageReaction(string reaction, int32 choose_count, bool is_chosen, vector &&recent_chooser_dialog_ids, vector> &&recent_chooser_min_channels) @@ -55,15 +51,27 @@ class MessageReaction { 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); + void set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool have_recent_choosers); + + void add_recent_chooser_dialog_id(DialogId dialog_id); + + bool remove_recent_chooser_dialog_id(DialogId dialog_id); + + void update_recent_chooser_dialog_ids(const MessageReaction &old_reaction); + + public: + static constexpr size_t MAX_RECENT_CHOOSERS = 3; + static constexpr int32 MAX_CHOOSE_COUNT = 2147483640; + + MessageReaction() = default; + + const string &get_reaction() const { + return reaction_; + } int32 get_choose_count() const { return choose_count_; @@ -77,13 +85,8 @@ class MessageReaction { return recent_chooser_min_channels_; } - void add_recent_chooser_dialog_id(DialogId dialog_id); - - bool remove_recent_chooser_dialog_id(DialogId dialog_id); - - void update_recent_chooser_dialog_ids(const MessageReaction &old_reaction); - - td_api::object_ptr get_message_reaction_object(Td *td) const; + td_api::object_ptr get_message_reaction_object(Td *td, UserId my_user_id, + UserId peer_user_id) const; template void store(StorerT &storer) const; @@ -136,6 +139,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReac struct MessageReactions { vector reactions_; vector unread_reactions_; + vector chosen_reaction_order_; bool is_min_ = false; bool need_polling_ = true; bool can_get_added_reactions_ = false; @@ -152,10 +156,16 @@ struct MessageReactions { void update_from(const MessageReactions &old_reactions); + bool add_reaction(const string &reaction, bool is_big, DialogId chooser_dialog_id, bool have_recent_choosers); + + bool remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers); + void sort_reactions(const FlatHashMap &active_reaction_pos); void fix_chosen_reaction(DialogId my_dialog_id); + vector get_chosen_reactions() const; + static bool need_update_message_reactions(const MessageReactions *old_reactions, const MessageReactions *new_reactions); @@ -167,15 +177,31 @@ struct MessageReactions { template void parse(ParserT &parser); + + private: + bool do_remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers); }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions); StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr &reactions); +telegram_api::object_ptr get_input_reaction(const string &reaction); + +td_api::object_ptr get_reaction_type_object(const string &reaction); + +string get_message_reaction_string(const telegram_api::object_ptr &reaction); + +string get_message_reaction_string(const td_api::object_ptr &type); + +bool is_custom_reaction(const string &reaction); + +bool is_active_reaction(const string &reaction, const FlatHashMap &active_reaction_pos); + void reload_message_reactions(Td *td, DialogId dialog_id, vector &&message_ids); -void set_message_reaction(Td *td, FullMessageId full_message_id, string reaction, bool is_big, Promise &&promise); +void send_message_reaction(Td *td, FullMessageId full_message_id, vector reactions, bool is_big, + bool add_to_recent, Promise &&promise); void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit, Promise> &&promise); @@ -184,4 +210,17 @@ void set_default_reaction(Td *td, string reaction, Promise &&promise); void send_set_default_reaction_query(Td *td); +void send_update_default_reaction_type(const string &default_reaction); + +void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId chooser_dialog_id, + Promise &&promise); + +vector get_recent_reactions(Td *td); + +vector get_top_reactions(Td *td); + +void add_recent_reaction(Td *td, const string &reaction); + +int64 get_reactions_hash(const vector &reactions); + } // namespace td diff --git a/td/telegram/MessageReaction.hpp b/td/telegram/MessageReaction.hpp index 8f7307d0f..040604b58 100644 --- a/td/telegram/MessageReaction.hpp +++ b/td/telegram/MessageReaction.hpp @@ -51,6 +51,7 @@ void MessageReaction::parse(ParserT &parser) { td::parse(recent_chooser_min_channels_, parser); } CHECK(!is_empty()); + CHECK(!reaction_.empty()); } template @@ -69,18 +70,21 @@ void UnreadMessageReaction::parse(ParserT &parser) { END_PARSE_FLAGS(); td::parse(reaction_, parser); td::parse(sender_dialog_id_, parser); + CHECK(!reaction_.empty()); } template void MessageReactions::store(StorerT &storer) const { bool has_reactions = !reactions_.empty(); bool has_unread_reactions = !unread_reactions_.empty(); + bool has_chosen_reaction_order = !chosen_reaction_order_.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); + STORE_FLAG(has_chosen_reaction_order); END_STORE_FLAGS(); if (has_reactions) { td::store(reactions_, storer); @@ -88,18 +92,23 @@ void MessageReactions::store(StorerT &storer) const { if (has_unread_reactions) { td::store(unread_reactions_, storer); } + if (has_chosen_reaction_order) { + td::store(chosen_reaction_order_, storer); + } } template void MessageReactions::parse(ParserT &parser) { bool has_reactions; bool has_unread_reactions; + bool has_chosen_reaction_order; 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); + PARSE_FLAG(has_chosen_reaction_order); END_PARSE_FLAGS(); if (has_reactions) { td::parse(reactions_, parser); @@ -107,6 +116,9 @@ void MessageReactions::parse(ParserT &parser) { if (has_unread_reactions) { td::parse(unread_reactions_, parser); } + if (has_chosen_reaction_order) { + td::parse(chosen_reaction_order_, parser); + } } } // namespace td diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index ad871ec94..48f90f0b1 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -304,6 +304,7 @@ class GetDialogQuery final : public Td::ResultHandler { class GetDialogsQuery final : public Td::ResultHandler { Promise promise_; + bool is_single_ = false; public: explicit GetDialogsQuery(Promise &&promise) : promise_(std::move(promise)) { @@ -312,6 +313,7 @@ class GetDialogsQuery final : public Td::ResultHandler { void send(vector input_dialog_ids) { CHECK(!input_dialog_ids.empty()); CHECK(input_dialog_ids.size() <= 100); + is_single_ = input_dialog_ids.size() == 1; auto input_dialog_peers = InputDialogId::get_input_dialog_peers(input_dialog_ids); CHECK(input_dialog_peers.size() == input_dialog_ids.size()); send_query(G()->net_query_creator().create(telegram_api::messages_getPeerDialogs(std::move(input_dialog_peers)))); @@ -333,6 +335,9 @@ class GetDialogsQuery final : public Td::ResultHandler { } void on_error(Status status) final { + if (is_single_ && status.code() == 400) { + return promise_.set_value(Unit()); + } promise_.set_error(std::move(status)); } }; @@ -1423,14 +1428,14 @@ class SetChatAvailableReactionsQuery final : public Td::ResultHandler { explicit SetChatAvailableReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(DialogId dialog_id, vector available_reactions) { + void send(DialogId dialog_id, const ChatReactions &available_reactions) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } - send_query(G()->net_query_creator().create( - telegram_api::messages_setChatAvailableReactions(std::move(input_peer), std::move(available_reactions)))); + send_query(G()->net_query_creator().create(telegram_api::messages_setChatAvailableReactions( + std::move(input_peer), available_reactions.get_input_chat_reactions()))); } void on_result(BufferSlice packet) final { @@ -3209,8 +3214,8 @@ class SendMessageQuery final : public Td::ResultHandler { auto query = G()->net_query_creator().create( telegram_api::messages_sendMessage( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - std::move(input_peer), reply_to_message_id.get_server_message_id().get(), text, random_id, - std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)), + false /*ignored*/, std::move(input_peer), reply_to_message_id.get_server_message_id().get(), text, + random_id, std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)), {{dialog_id, MessageContentType::Text}, {dialog_id, is_copy ? MessageContentType::Photo : MessageContentType::Text}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { @@ -3405,7 +3410,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server()); send_query(G()->net_query_creator().create( telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, std::move(input_peer), + false /*ignored*/, false /*ignored*/, std::move(input_peer), reply_to_message_id.get_server_message_id().get(), std::move(input_single_media), schedule_date, std::move(as_input_peer)), {{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo}, @@ -3522,9 +3527,9 @@ class SendMediaQuery final : public Td::ResultHandler { CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server()); auto query = G()->net_query_creator().create( telegram_api::messages_sendMedia( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), - reply_to_message_id.get_server_message_id().get(), std::move(input_media), text, random_id, - std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)), + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + std::move(input_peer), reply_to_message_id.get_server_message_id().get(), std::move(input_media), text, + random_id, std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)), {{dialog_id, content_type}, {dialog_id, is_copy ? MessageContentType::Text : content_type}}); if (td_->option_manager_->get_option_boolean("use_quick_ack") && was_uploaded_) { query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result result) { @@ -4758,6 +4763,7 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(has_explicit_sender); STORE_FLAG(has_reactions); STORE_FLAG(has_available_reactions_generation); + STORE_FLAG(update_stickersets_order); END_STORE_FLAGS(); } @@ -4999,6 +5005,7 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(has_explicit_sender); PARSE_FLAG(has_reactions); PARSE_FLAG(has_available_reactions_generation); + PARSE_FLAG(update_stickersets_order); END_PARSE_FLAGS(); } @@ -5204,9 +5211,10 @@ void MessagesManager::Dialog::store(StorerT &storer) const { bool has_pending_join_requests = pending_join_request_count != 0; bool has_action_bar = action_bar != nullptr; bool has_default_send_message_as_dialog_id = default_send_message_as_dialog_id.is_valid(); - bool has_available_reactions = !available_reactions.empty(); + bool has_legacy_available_reactions = false; bool has_available_reactions_generation = available_reactions_generation != 0; bool has_have_full_history_source = have_full_history && have_full_history_source != 0; + bool has_available_reactions = !available_reactions.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_draft_message); STORE_FLAG(has_last_database_message); @@ -5283,10 +5291,11 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(has_action_bar); STORE_FLAG(has_default_send_message_as_dialog_id); STORE_FLAG(need_drop_default_send_message_as_dialog_id); - STORE_FLAG(has_available_reactions); + STORE_FLAG(has_legacy_available_reactions); STORE_FLAG(is_available_reactions_inited); STORE_FLAG(has_available_reactions_generation); STORE_FLAG(has_have_full_history_source); + STORE_FLAG(has_available_reactions); END_STORE_FLAGS(); } @@ -5444,9 +5453,10 @@ void MessagesManager::Dialog::parse(ParserT &parser) { bool action_bar_can_invite_members = false; bool has_action_bar = false; bool has_default_send_message_as_dialog_id = false; - bool has_available_reactions = false; + bool has_legacy_available_reactions = false; bool has_available_reactions_generation = false; bool has_have_full_history_source = false; + bool has_available_reactions = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_draft_message); PARSE_FLAG(has_last_database_message); @@ -5538,10 +5548,11 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(has_action_bar); PARSE_FLAG(has_default_send_message_as_dialog_id); PARSE_FLAG(need_drop_default_send_message_as_dialog_id); - PARSE_FLAG(has_available_reactions); + PARSE_FLAG(has_legacy_available_reactions); PARSE_FLAG(is_available_reactions_inited); PARSE_FLAG(has_available_reactions_generation); PARSE_FLAG(has_have_full_history_source); + PARSE_FLAG(has_available_reactions); END_PARSE_FLAGS(); } else { need_repair_action_bar = false; @@ -5692,6 +5703,10 @@ void MessagesManager::Dialog::parse(ParserT &parser) { } if (has_available_reactions) { parse(available_reactions, parser); + } else if (has_legacy_available_reactions) { + vector legacy_available_reactions; + parse(legacy_available_reactions, parser); + available_reactions = ChatReactions(std::move(legacy_available_reactions)); } if (has_available_reactions_generation) { parse(available_reactions_generation, parser); @@ -6478,8 +6493,8 @@ void MessagesManager::skip_old_pending_pts_update(tl_object_ptr(update.get()); auto full_message_id = get_full_message_id(update_new_message->message_, false); if (update_message_ids_.count(full_message_id) > 0) { - if (new_pts == old_pts) { // otherwise message can be already deleted - // apply sent message anyway + if (new_pts == old_pts || old_pts == std::numeric_limits::max()) { + // apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow on_get_message(std::move(update_new_message->message_), true, false, false, true, true, "updateNewMessage with an awaited message"); return; @@ -6493,8 +6508,8 @@ void MessagesManager::skip_old_pending_pts_update(tl_object_ptrget_id() == updateSentMessage::ID) { auto update_sent_message = static_cast(update.get()); if (being_sent_messages_.count(update_sent_message->random_id_) > 0) { - if (new_pts == old_pts) { // otherwise message can be already deleted - // apply sent message anyway + if (new_pts == old_pts || old_pts == std::numeric_limits::max()) { + // apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, update_sent_message->date_, update_sent_message->ttl_period_, FileId(), "process old updateSentMessage"); @@ -6803,7 +6818,7 @@ void MessagesManager::on_get_message_reaction_list(FullMessageId full_message_id }; // it's impossible to use received reactions to update message reactions, because there is no way to find, - // which reaction is chosen by the current user, so just reload message reactions for consistency + // which reactions are chosen by the current user, so just reload message reactions for consistency bool need_reload = false; if (reaction.empty()) { // received list and total_count for all reactions @@ -7009,7 +7024,7 @@ td_api::object_ptr MessagesManager::get_message_ DialogId dialog_id, const Message *m) const { bool is_visible_reply_info = is_visible_message_reply_info(dialog_id, m); bool has_reactions = - is_visible_message_reactions(dialog_id, m) && m->reactions != nullptr && !m->reactions->reactions_.empty(); + m->reactions != nullptr && !m->reactions->reactions_.empty() && is_visible_message_reactions(dialog_id, m); if (m->view_count == 0 && m->forward_count == 0 && !is_visible_reply_info && !has_reactions) { return nullptr; } @@ -7028,9 +7043,16 @@ td_api::object_ptr MessagesManager::get_message_ vector> reactions; if (has_reactions) { - reactions = transform(m->reactions->reactions_, [td = td_](const MessageReaction &reaction) { - return reaction.get_message_reaction_object(td); - }); + UserId my_user_id; + UserId peer_user_id; + if (dialog_id.get_type() == DialogType::User) { + my_user_id = td_->contacts_manager_->get_my_id(); + peer_user_id = dialog_id.get_user_id(); + } + reactions = + transform(m->reactions->reactions_, [td = td_, my_user_id, peer_user_id](const MessageReaction &reaction) { + return reaction.get_message_reaction_object(td, my_user_id, peer_user_id); + }); } return td_api::make_object(m->view_count, m->forward_count, std::move(reply_info), @@ -7099,8 +7121,10 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int has_reactions && MessageReactions::need_update_message_reactions(m->reactions.get(), reactions.get()); bool need_update_unread_reactions = has_reactions && MessageReactions::need_update_unread_reactions(m->reactions.get(), reactions.get()); + bool need_update_chosen_reaction_order = has_reactions && reactions != nullptr && m->reactions != nullptr && + m->reactions->chosen_reaction_order_ != reactions->chosen_reaction_order_; if (view_count > m->view_count || forward_count > m->forward_count || need_update_reply_info || - need_update_reactions || need_update_unread_reactions) { + need_update_reactions || need_update_unread_reactions || need_update_chosen_reaction_order) { LOG(DEBUG) << "Update interaction info of " << FullMessageId{dialog_id, m->message_id} << " from " << m->view_count << '/' << m->forward_count << '/' << m->reply_info << '/' << m->reactions << " to " << view_count << '/' << forward_count << '/' << reply_info << '/' << reactions; @@ -7160,9 +7184,19 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int } } } - } else if (m->available_reactions_generation != d->available_reactions_generation) { - m->available_reactions_generation = d->available_reactions_generation; - on_message_changed(d, m, false, "update_message_interaction_info"); + } else if (has_reactions) { + bool is_changed = false; + if (m->available_reactions_generation != d->available_reactions_generation) { + m->available_reactions_generation = d->available_reactions_generation; + is_changed = true; + } + if (need_update_chosen_reaction_order) { + m->reactions->chosen_reaction_order_ = std::move(reactions->chosen_reaction_order_); + is_changed = true; + } + if (is_changed) { + on_message_changed(d, m, false, "update_message_interaction_info"); + } } if (need_update) { send_update_message_interaction_info(dialog_id, m); @@ -7171,7 +7205,7 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int send_update_message_unread_reactions(dialog_id, m, new_dialog_unread_reaction_count); } return true; - } else if (m->available_reactions_generation != d->available_reactions_generation) { + } else if (has_reactions && m->available_reactions_generation != d->available_reactions_generation) { m->available_reactions_generation = d->available_reactions_generation; on_message_changed(d, m, false, "update_message_interaction_info"); } @@ -7235,9 +7269,21 @@ void MessagesManager::on_external_update_message_content(FullMessageId full_mess Message *m = get_message(d, full_message_id.get_message_id()); CHECK(m != nullptr); send_update_message_content(d, m, true, "on_external_update_message_content"); + // must not call on_message_changed, because the message itself wasn't changed if (m->message_id == d->last_message_id) { send_update_chat_last_message_impl(d, "on_external_update_message_content"); } + on_message_notification_changed(d, m, "on_external_update_message_content"); +} + +void MessagesManager::on_update_message_content(FullMessageId full_message_id) { + Dialog *d = get_dialog(full_message_id.get_dialog_id()); + CHECK(d != nullptr); + Message *m = get_message(d, full_message_id.get_message_id()); + CHECK(m != nullptr); + send_update_message_content(d, m, true, "on_update_message_content"); + on_message_changed(d, m, true, "on_update_message_content"); + on_message_notification_changed(d, m, "on_update_message_content"); } bool MessagesManager::update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, @@ -7931,8 +7977,8 @@ void MessagesManager::on_message_edited(FullMessageId full_message_id, int32 pts Message *m = get_message(d, full_message_id.get_message_id()); CHECK(m != nullptr); m->last_edit_pts = pts; + d->last_edited_message_id = m->message_id; if (td_->auth_manager_->is_bot()) { - d->last_edited_message_id = m->message_id; send_update_message_edited(dialog_id, m); } update_used_hashtags(dialog_id, m); @@ -8196,7 +8242,8 @@ void MessagesManager::on_update_dialog_notify_settings( update_dialog_notification_settings(dialog_id, current_settings, std::move(notification_settings)); } -void MessagesManager::on_update_dialog_available_reactions(DialogId dialog_id, vector &&available_reactions) { +void MessagesManager::on_update_dialog_available_reactions( + DialogId dialog_id, telegram_api::object_ptr &&available_reactions) { if (td_->auth_manager_->is_bot()) { return; } @@ -8206,10 +8253,10 @@ void MessagesManager::on_update_dialog_available_reactions(DialogId dialog_id, v return; } - set_dialog_available_reactions(d, std::move(available_reactions)); + set_dialog_available_reactions(d, ChatReactions(std::move(available_reactions))); } -void MessagesManager::set_dialog_available_reactions(Dialog *d, vector &&available_reactions) { +void MessagesManager::set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions) { CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); switch (d->dialog_id.get_type()) { @@ -8302,20 +8349,19 @@ void MessagesManager::hide_dialog_message_reactions(Dialog *d) { } } -void MessagesManager::set_active_reactions(vector active_reactions) { +void MessagesManager::set_active_reactions(vector active_reactions) { if (active_reactions == active_reactions_) { return; } - auto old_active_reactions = std::move(active_reactions_); + LOG(INFO) << "Set active reactions to " << active_reactions; + bool is_changed = active_reactions != active_reactions_; active_reactions_ = std::move(active_reactions); + + auto old_active_reaction_pos_ = std::move(active_reaction_pos_); active_reaction_pos_.clear(); - bool is_changed = old_active_reactions.size() != active_reactions_.size(); for (size_t i = 0; i < active_reactions_.size(); i++) { - active_reaction_pos_[active_reactions_[i].reaction_] = i; - if (!is_changed && active_reactions_[i].reaction_ != old_active_reactions[i].reaction_) { - is_changed = true; - } + active_reaction_pos_[active_reactions_[i]] = i; } dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { @@ -8328,8 +8374,8 @@ void MessagesManager::set_active_reactions(vector active_reac break; case DialogType::Chat: case DialogType::Channel: { - auto old_reactions = ::td::get_active_reactions(d->available_reactions, old_active_reactions); - auto new_reactions = ::td::get_active_reactions(d->available_reactions, active_reactions_); + auto old_reactions = d->available_reactions.get_active_reactions(old_active_reaction_pos_); + auto new_reactions = d->available_reactions.get_active_reactions(active_reaction_pos_); if (old_reactions != new_reactions) { if (old_reactions.empty() != new_reactions.empty()) { if (!old_reactions.empty()) { @@ -8351,15 +8397,15 @@ void MessagesManager::set_active_reactions(vector active_reac }); } -vector MessagesManager::get_active_reactions(const vector &available_reactions) const { - return ::td::get_active_reactions(available_reactions, active_reactions_); +ChatReactions MessagesManager::get_active_reactions(const ChatReactions &available_reactions) const { + return available_reactions.get_active_reactions(active_reaction_pos_); } -vector MessagesManager::get_dialog_active_reactions(const Dialog *d) const { +ChatReactions MessagesManager::get_dialog_active_reactions(const Dialog *d) const { CHECK(d != nullptr); switch (d->dialog_id.get_type()) { case DialogType::User: - return transform(active_reactions_, [](auto &reaction) { return reaction.reaction_; }); + return ChatReactions(true, true); case DialogType::Chat: case DialogType::Channel: return get_active_reactions(d->available_reactions); @@ -8371,18 +8417,18 @@ vector MessagesManager::get_dialog_active_reactions(const Dialog *d) con } } -vector MessagesManager::get_message_active_reactions(const Dialog *d, const Message *m) const { +ChatReactions MessagesManager::get_message_active_reactions(const Dialog *d, const Message *m) const { CHECK(d != nullptr); CHECK(m != nullptr); if (is_service_message_content(m->content->get_type()) || m->ttl > 0) { - return vector(); + return ChatReactions(); } if (is_discussion_message(d->dialog_id, m)) { d = get_dialog(m->forward_info->from_dialog_id); if (d == nullptr) { LOG(ERROR) << "Failed to find linked " << m->forward_info->from_dialog_id << " to determine correct active reactions"; - return vector(); + return ChatReactions(); } } return get_dialog_active_reactions(d); @@ -9088,7 +9134,7 @@ void MessagesManager::on_upload_media_error(FileId file_id, Status status) { bool is_edit = full_message_id.get_message_id().is_any_server(); if (is_edit) { - fail_edit_message_media(full_message_id, Status::Error(status.code() > 0 ? status.code() : 500, status.message())); + fail_edit_message_media(full_message_id, std::move(status)); } else { fail_send_message(full_message_id, std::move(status)); } @@ -11709,9 +11755,12 @@ int32 MessagesManager::get_unload_dialog_delay() const { return narrow_cast(td_->option_manager_->get_option_integer("message_unload_delay", default_unload_delay)); } -int32 MessagesManager::get_next_unload_dialog_delay() const { - auto delay = get_unload_dialog_delay(); - return Random::fast(delay / 4, delay / 2); +double MessagesManager::get_next_unload_dialog_delay(Dialog *d) const { + if (d->unload_dialog_delay_seed == 0) { + d->unload_dialog_delay_seed = Random::fast(1, 1000000000); + } + auto delay = get_unload_dialog_delay() / 4; + return delay + delay * 1e-9 * d->unload_dialog_delay_seed; } void MessagesManager::unload_dialog(DialogId dialog_id) { @@ -11759,7 +11808,7 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { if (has_left_to_unload_messages) { LOG(DEBUG) << "Need to unload more messages in " << dialog_id; - pending_unload_dialog_timeout_.add_timeout_in(d->dialog_id.get(), get_next_unload_dialog_delay()); + pending_unload_dialog_timeout_.add_timeout_in(d->dialog_id.get(), get_next_unload_dialog_delay(d)); } else { d->has_unload_timeout = false; } @@ -14553,6 +14602,16 @@ std::pair> MessagesManager::creat ttl = max(ttl, get_message_content_duration(message_info.content.get(), td_) + 1); } + if (message_id.is_scheduled()) { + if (message_info.reply_info != nullptr) { + LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reply info"; + message_info.reply_info = nullptr; + } + if (message_info.reactions != nullptr) { + LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reactions"; + message_info.reactions = nullptr; + } + } int32 view_count = message_info.view_count; if (view_count < 0) { LOG(ERROR) << "Wrong view_count = " << view_count << " received in " << message_id << " in " << dialog_id; @@ -14573,6 +14632,10 @@ std::pair> MessagesManager::creat } auto reactions = MessageReactions::get_message_reactions(td_, std::move(message_info.reactions), td_->auth_manager_->is_bot()); + if (reactions != nullptr) { + reactions->sort_reactions(active_reaction_pos_); + reactions->fix_chosen_reaction(get_my_dialog_id()); + } bool has_forward_info = message_info.forward_header != nullptr; @@ -14614,6 +14677,7 @@ std::pair> MessagesManager::creat message->is_from_scheduled = is_from_scheduled; message->is_pinned = is_pinned; message->noforwards = noforwards; + message->interaction_info_update_date = G()->unix_time(); message->view_count = view_count; message->forward_count = forward_count; message->reply_info = std::move(reply_info); @@ -14880,12 +14944,26 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f return FullMessageId(dialog_id, message_id); } -void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source) { +void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source, + const Message *m) { CHECK(!last_message_id.is_scheduled()); LOG(INFO) << "Set " << d->dialog_id << " last message to " << last_message_id << " from " << source; d->last_message_id = last_message_id; + if (m != nullptr) { + d->last_media_album_id = m->media_album_id; + } else if (!last_message_id.is_valid()) { + d->last_media_album_id = 0; + } else { + m = get_message(d, last_message_id); + if (m == nullptr) { + LOG(ERROR) << "Failed to find last " << last_message_id << " in " << d->dialog_id; + d->last_media_album_id = 0; + } else { + d->last_media_album_id = m->media_album_id; + } + } if (!last_message_id.is_valid()) { d->suffix_load_first_message_id_ = MessageId(); d->suffix_load_done_ = false; @@ -15111,12 +15189,8 @@ bool MessagesManager::is_dialog_pinned(DialogListId dialog_list_id, DialogId dia } if (dialog_list_id.is_filter()) { const auto *filter = get_dialog_filter(dialog_list_id.get_filter_id()); - if (filter != nullptr) { - for (const auto &input_dialog_id : filter->pinned_dialog_ids) { - if (input_dialog_id.get_dialog_id() == dialog_id) { - return true; - } - } + if (filter != nullptr && InputDialogId::contains(filter->pinned_dialog_ids, dialog_id)) { + return true; } } return false; @@ -15199,13 +15273,8 @@ bool MessagesManager::set_dialog_is_pinned(DialogListId dialog_list_id, Dialog * } LOG(INFO) << "Set " << d->dialog_id << " is pinned in " << dialog_list_id << " to " << is_pinned; - if (dialog_list_id.is_folder() && G()->parameters().use_message_db) { - G()->td_db()->get_binlog_pmc()->set( - PSTRING() << "pinned_dialog_ids" << dialog_list_id.get_folder_id().get(), - implode(transform(list->pinned_dialogs_, - [](auto &pinned_dialog) { return PSTRING() << pinned_dialog.get_dialog_id().get(); }), - ',')); - } + + save_pinned_folder_dialog_ids(*list); if (need_update_dialog_lists) { update_dialog_lists(d, std::move(positions), true, false, "set_dialog_is_pinned"); @@ -15213,6 +15282,17 @@ bool MessagesManager::set_dialog_is_pinned(DialogListId dialog_list_id, Dialog * return true; } +void MessagesManager::save_pinned_folder_dialog_ids(const DialogList &list) const { + if (!list.dialog_list_id.is_folder() || !G()->parameters().use_message_db) { + return; + } + G()->td_db()->get_binlog_pmc()->set( + PSTRING() << "pinned_dialog_ids" << list.dialog_list_id.get_folder_id().get(), + implode(transform(list.pinned_dialogs_, + [](auto &pinned_dialog) { return PSTRING() << pinned_dialog.get_dialog_id().get(); }), + ',')); +} + void MessagesManager::set_dialog_reply_markup(Dialog *d, MessageId message_id) { if (td_->auth_manager_->is_bot()) { return; @@ -15426,12 +15506,11 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, "on_update_sent_text_message"); m->content = std::move(new_content); m->is_content_secret = is_secret_message_content(m->ttl, MessageContentType::Text); - } - if (need_update) { - send_update_message_content(dialog_id, m, true, "on_update_sent_text_message"); - if (m->message_id == d->last_message_id) { - send_update_chat_last_message_impl(d, "on_update_sent_text_message"); + + if (need_update) { + send_update_message_content(d, m, true, "on_update_sent_text_message"); } + on_message_changed(d, m, need_update, "on_update_sent_text_message"); } } @@ -15502,12 +15581,16 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vector full_message_id_to_dialog_date; FlatHashMap, FullMessageIdHash> full_message_id_to_message; @@ -15605,12 +15688,12 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorflags_ & DIALOG_FLAG_HAS_FOLDER_ID) != 0 ? dialog->folder_id_ : 0)); - on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_), "on_get_dialogs"); + on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_), source); if (!d->notification_settings.is_synchronized && !td_->auth_manager_->is_bot()) { LOG(ERROR) << "Failed to synchronize settings in " << dialog_id; d->notification_settings.is_synchronized = true; @@ -15684,21 +15767,21 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorpts == 0 || dialog->pts_ <= d->pts || d->is_channel_difference_finished) { auto last_message = std::move(it->second); auto added_full_message_id = - on_get_message(std::move(last_message), false, has_pts, false, false, false, "get chats"); + on_get_message(std::move(last_message), false, has_pts, false, false, false, source); CHECK(d->last_new_message_id == MessageId()); - set_dialog_last_new_message_id(d, last_message_id, "on_get_dialogs"); + set_dialog_last_new_message_id(d, last_message_id, source); if (d->last_new_message_id > d->last_message_id && added_full_message_id.get_message_id().is_valid()) { CHECK(added_full_message_id.get_message_id() == d->last_new_message_id); - set_dialog_last_message_id(d, d->last_new_message_id, "on_get_dialogs"); - send_update_chat_last_message(d, "on_get_dialogs"); + set_dialog_last_message_id(d, d->last_new_message_id, source); + send_update_chat_last_message(d, source); } } else { - get_channel_difference(dialog_id, d->pts, true, "on_get_dialogs"); + get_channel_difference(dialog_id, d->pts, true, source); } } if (has_pts && !running_get_channel_difference(dialog_id)) { - set_channel_pts(d, dialog->pts_, "get channel"); + set_channel_pts(d, dialog->pts_, source); } } bool is_marked_as_unread = dialog->unread_mark_; @@ -15707,7 +15790,7 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorauth_manager_->is_bot() && !from_pinned_dialog_list) { @@ -15753,7 +15836,7 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorserver_unread_count, "on_get_dialogs"); + repair_server_unread_count(dialog_id, d->server_unread_count, source); } } if (!d->need_repair_server_unread_count) { @@ -15763,7 +15846,7 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorlast_read_inbox_message_id == read_inbox_max_message_id) || d->last_read_inbox_message_id < read_inbox_max_message_id) { set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, dialog->unread_count_, - d->local_unread_count, true, "on_get_dialogs"); + d->local_unread_count, true, source); } if (!d->is_last_read_inbox_message_id_inited) { d->is_last_read_inbox_message_id_inited = true; @@ -15790,13 +15873,13 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorunread_reaction_count != dialog->unread_reactions_count_) { set_dialog_unread_reaction_count(d, dialog->unread_reactions_count_); // update_dialog_mention_notification_count(d); - send_update_chat_unread_reaction_count(d, "on_get_dialogs"); + send_update_chat_unread_reaction_count(d, source); } } being_added_dialog_id_ = DialogId(); - update_dialog_lists(d, std::move(positions), true, false, "on_get_dialogs"); + update_dialog_lists(d, std::move(positions), true, false, source); } if (from_dialog_list) { @@ -15809,7 +15892,7 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectordialog_id, m->message_id}; @@ -15954,7 +16038,8 @@ bool MessagesManager::can_unload_message(const Dialog *d, const Message *m) cons !m->message_id.is_yet_unsent() && active_live_location_full_message_ids_.count(full_message_id) == 0 && replied_by_yet_unsent_messages_.count(full_message_id) == 0 && m->edited_content == nullptr && d->suffix_load_queries_.empty() && m->message_id != d->reply_markup_message_id && - m->message_id != d->last_pinned_message_id && m->message_id != d->last_edited_message_id; + m->message_id != d->last_pinned_message_id && m->message_id != d->last_edited_message_id && + (m->media_album_id != d->last_media_album_id || m->media_album_id == 0); } void MessagesManager::unload_message(Dialog *d, MessageId message_id) { @@ -16246,7 +16331,7 @@ unique_ptr MessagesManager::do_delete_message(Dialog * if ((*it)->have_previous) { --it; if (*it != nullptr) { - set_dialog_last_message_id(d, (*it)->message_id, "do_delete_message"); + set_dialog_last_message_id(d, (*it)->message_id, "do_delete_message", *it); } else { LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from " << source; @@ -16650,6 +16735,70 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise &&input_dialog_ids, Promise &&promise) { + const size_t MAX_SLICE_SIZE = 100; // server side limit + MultiPromiseActorSafe mpas{"GetFilterDialogsOnServerMultiPromiseActor"}; + mpas.add_promise(std::move(promise)); + auto lock = mpas.get_promise(); + + for (size_t i = 0; i < input_dialog_ids.size(); i += MAX_SLICE_SIZE) { + auto end_i = i + MAX_SLICE_SIZE; + auto end = end_i < input_dialog_ids.size() ? input_dialog_ids.begin() + end_i : input_dialog_ids.end(); + vector slice_input_dialog_ids = {input_dialog_ids.begin() + i, end}; + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id, + dialog_ids = InputDialogId::get_dialog_ids(slice_input_dialog_ids), + promise = mpas.get_promise()](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &MessagesManager::on_load_dialog_filter_dialogs, dialog_filter_id, std::move(dialog_ids), + std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(std::move(slice_input_dialog_ids)); + } + + lock.set_value(Unit()); +} + +void MessagesManager::on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector &&dialog_ids, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + td::remove_if(dialog_ids, + [this](DialogId dialog_id) { return have_dialog_force(dialog_id, "on_load_dialog_filter_dialogs"); }); + if (dialog_ids.empty()) { + LOG(INFO) << "All chats from " << dialog_filter_id << " were loaded"; + return promise.set_value(Unit()); + } + + LOG(INFO) << "Failed to load chats " << dialog_ids << " from " << dialog_filter_id; + + auto old_dialog_filter = get_dialog_filter(dialog_filter_id); + if (old_dialog_filter == nullptr) { + return promise.set_value(Unit()); + } + CHECK(is_update_chat_filters_sent_); + + auto new_dialog_filter = td::make_unique(*old_dialog_filter); + for (auto dialog_id : dialog_ids) { + InputDialogId::remove(new_dialog_filter->pinned_dialog_ids, dialog_id); + InputDialogId::remove(new_dialog_filter->included_dialog_ids, dialog_id); + InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id); + } + + if (*new_dialog_filter != *old_dialog_filter) { + LOG(INFO) << "Update " << dialog_filter_id << " from " << *old_dialog_filter << " to " << *new_dialog_filter; + edit_dialog_filter(std::move(new_dialog_filter), "on_load_dialog_filter_dialogs"); + save_dialog_filters(); + send_update_chat_filters(); + + synchronize_dialog_filters(); + } + + promise.set_value(Unit()); +} + void MessagesManager::load_dialog_filter(DialogFilterId dialog_filter_id, bool force, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); if (!dialog_filter_id.is_valid()) { @@ -16692,20 +16841,7 @@ void MessagesManager::load_dialog_filter(const DialogFilter *filter, bool force, } if (!input_dialog_ids.empty() && !force) { - const size_t MAX_SLICE_SIZE = 100; // server side limit - MultiPromiseActorSafe mpas{"GetFilterDialogsOnServerMultiPromiseActor"}; - mpas.add_promise(std::move(promise)); - mpas.set_ignore_errors(true); - auto lock = mpas.get_promise(); - - for (size_t i = 0; i < input_dialog_ids.size(); i += MAX_SLICE_SIZE) { - auto end_i = i + MAX_SLICE_SIZE; - auto end = end_i < input_dialog_ids.size() ? input_dialog_ids.begin() + end_i : input_dialog_ids.end(); - td_->create_handler(mpas.get_promise())->send({input_dialog_ids.begin() + i, end}); - } - - lock.set_value(Unit()); - return; + return load_dialog_filter_dialogs(filter->dialog_filter_id, std::move(input_dialog_ids), std::move(promise)); } promise.set_value(Unit()); @@ -16857,14 +16993,15 @@ vector MessagesManager::get_dialogs(DialogListId dialog_list_id, Dialo promise.set_value(Unit()); return result; } else { - td_->create_handler(std::move(promise))->send(std::move(input_dialog_ids)); + load_dialog_filter_dialogs(filter->dialog_filter_id, std::move(input_dialog_ids), std::move(promise)); return {}; } } } - bool need_reload_pinned_dialogs = false; if (!list.pinned_dialogs_.empty() && offset < list.pinned_dialogs_.back() && limit > 0) { + bool need_reload_pinned_dialogs = false; + bool need_remove_unknown_secret_chats = false; for (auto &pinned_dialog : list.pinned_dialogs_) { if (offset < pinned_dialog) { auto dialog_id = pinned_dialog.get_dialog_id(); @@ -16873,6 +17010,8 @@ vector MessagesManager::get_dialogs(DialogListId dialog_list_id, Dialo LOG(ERROR) << "Failed to load pinned " << dialog_id << " from " << dialog_list_id; if (dialog_id.get_type() != DialogType::SecretChat) { need_reload_pinned_dialogs = true; + } else { + need_remove_unknown_secret_chats = true; } continue; } @@ -16888,9 +17027,20 @@ vector MessagesManager::get_dialogs(DialogListId dialog_list_id, Dialo } } } - } - if (need_reload_pinned_dialogs) { - reload_pinned_dialogs(dialog_list_id, Auto()); + if (need_reload_pinned_dialogs) { + reload_pinned_dialogs(dialog_list_id, Auto()); + } + if (need_remove_unknown_secret_chats) { + td::remove_if(list.pinned_dialogs_, [this, &list](const DialogDate &dialog_date) { + auto dialog_id = dialog_date.get_dialog_id(); + if (dialog_id.get_type() == DialogType::SecretChat && !have_dialog_force(dialog_id, "get_dialogs 2")) { + list.pinned_dialog_id_orders_.erase(dialog_id); + return true; + } + return false; + }); + save_pinned_folder_dialog_ids(list); + } } update_list_last_pinned_dialog_date(list); @@ -16927,6 +17077,7 @@ vector MessagesManager::get_dialogs(DialogListId dialog_list_id, Dialo if ((!result.empty() && (!exact_limit || limit == 0)) || force || list.list_last_dialog_date_ == MAX_DIALOG_DATE) { if (limit > 0 && list.list_last_dialog_date_ != MAX_DIALOG_DATE) { + LOG(INFO) << "Preload next " << limit << " chats in " << dialog_list_id; load_dialog_list(list, limit, Promise()); } @@ -16960,7 +17111,7 @@ void MessagesManager::load_dialog_list(DialogList &list, int32 limit, Promise MessagesManager::get_pinned_dialog_ids(DialogListId dialog_list if (filter == nullptr) { return {}; } - return transform(filter->pinned_dialog_ids, [](auto &input_dialog) { return input_dialog.get_dialog_id(); }); + return InputDialogId::get_dialog_ids(filter->pinned_dialog_ids); } auto *list = get_dialog_list(dialog_list_id); @@ -17799,7 +17950,7 @@ std::pair> MessagesManager::get_common_dialogs(UserId us if (it != found_common_dialogs_.end() && !it->second.dialog_ids.empty()) { int32 total_count = it->second.total_count; vector &common_dialog_ids = it->second.dialog_ids; - bool use_cache = (!it->second.is_outdated && it->second.received_date >= Time::now() - 3600) || force || + bool use_cache = (!it->second.is_outdated && it->second.receive_time >= Time::now() - 3600) || force || offset_chat_id != 0 || common_dialog_ids.size() >= static_cast(MAX_GET_DIALOGS); // use cache if it is up-to-date, or we required to use it or we can't update it if (use_cache) { @@ -17847,8 +17998,8 @@ void MessagesManager::on_get_common_dialogs(UserId user_id, int64 offset_chat_id // drop outdated cache if possible common_dialogs = CommonDialogs(); } - if (common_dialogs.received_date == 0) { - common_dialogs.received_date = Time::now(); + if (common_dialogs.receive_time == 0) { + common_dialogs.receive_time = Time::now(); } common_dialogs.is_outdated = false; auto &result = common_dialogs.dialog_ids; @@ -18812,6 +18963,25 @@ Status MessagesManager::can_get_media_timestamp_link(DialogId dialog_id, const M return Status::OK(); } +bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Message *m) const { + CHECK(m != nullptr); + if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id) || + !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id())) { + return false; + } + if (m->message_id.is_scheduled() || !m->message_id.is_server()) { + return false; + } + if (m->message_id.is_scheduled() || !m->message_id.is_server()) { + return false; + } + if (is_discussion_message(dialog_id, m)) { + return false; + } + + return true; +} + Result> MessagesManager::get_message_link(FullMessageId full_message_id, int32 media_timestamp, bool for_group, bool for_comment) { auto dialog_id = full_message_id.get_dialog_id(); @@ -20057,16 +20227,13 @@ Status MessagesManager::toggle_dialog_is_pinned(DialogListId dialog_list_id, Dia auto old_dialog_filter = get_dialog_filter(dialog_filter_id); CHECK(old_dialog_filter != nullptr); auto new_dialog_filter = make_unique(*old_dialog_filter); - auto is_changed_dialog = [dialog_id](InputDialogId input_dialog_id) { - return dialog_id == input_dialog_id.get_dialog_id(); - }; if (is_pinned) { new_dialog_filter->pinned_dialog_ids.insert(new_dialog_filter->pinned_dialog_ids.begin(), get_input_dialog_id(dialog_id)); - td::remove_if(new_dialog_filter->included_dialog_ids, is_changed_dialog); - td::remove_if(new_dialog_filter->excluded_dialog_ids, is_changed_dialog); + InputDialogId::remove(new_dialog_filter->included_dialog_ids, dialog_id); + InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id); } else { - bool is_removed = td::remove_if(new_dialog_filter->pinned_dialog_ids, is_changed_dialog); + bool is_removed = InputDialogId::remove(new_dialog_filter->pinned_dialog_ids, dialog_id); CHECK(is_removed); new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id)); } @@ -20661,7 +20828,7 @@ DialogId MessagesManager::create_new_group_chat(const vector &user_ids, auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { - promise.set_error(Status::Error(400, "Title can't be empty")); + promise.set_error(Status::Error(400, "Title must be non-empty")); return DialogId(); } @@ -20709,7 +20876,7 @@ DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_m auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { - promise.set_error(Status::Error(400, "Title can't be empty")); + promise.set_error(Status::Error(400, "Title must be non-empty")); return DialogId(); } @@ -21291,7 +21458,7 @@ void MessagesManager::close_dialog(Dialog *d) { if (is_message_unload_enabled()) { CHECK(!d->has_unload_timeout); - pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay()); + pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d)); d->has_unload_timeout = true; } @@ -21429,6 +21596,7 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * auto can_delete = can_delete_dialog(d); // TODO hide/show draft message when can_send_message(dialog_id) changes auto draft_message = can_send_message(d->dialog_id).is_ok() ? get_draft_message_object(d->draft_message) : nullptr; + auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); return make_tl_object( d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id), get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)), @@ -21440,7 +21608,7 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message, d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(), d->last_read_outbox_message_id.get(), d->unread_mention_count, d->unread_reaction_count, - get_chat_notification_settings_object(&d->notification_settings), get_dialog_active_reactions(d), + get_chat_notification_settings_object(&d->notification_settings), std::move(available_reactions), d->message_ttl.get_message_ttl_object(), get_dialog_theme_name(d), get_chat_action_bar_object(d), get_video_chat_object(d), get_chat_join_requests_info_object(d), d->reply_markup_message_id.get(), std::move(draft_message), d->client_data); @@ -24426,7 +24594,12 @@ void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id set_promises(promises); } -Result> MessagesManager::get_message_available_reactions(FullMessageId full_message_id) { +Result> MessagesManager::get_message_available_reactions( + FullMessageId full_message_id, int32 row_size) { + if (row_size < 5 || row_size > 25) { + row_size = 8; + } + auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "get_message_available_reactions"); if (d == nullptr) { @@ -24437,10 +24610,103 @@ Result> MessagesManager::get_message_available_reactio if (m == nullptr) { return Status::Error(400, "Message not found"); } - return get_message_available_reactions(d, m); + + auto available_reactions = get_message_available_reactions(d, m, false); + bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); + bool show_premium = is_premium; + + auto recent_reactions = get_recent_reactions(td_); + auto top_reactions = get_top_reactions(td_); + auto active_reactions = get_message_active_reactions(d, m); + LOG(INFO) << "Have available reactions " << available_reactions << " to be sorted by top reactions " << top_reactions + << " and recent reactions " << recent_reactions; + if (active_reactions.allow_custom_ && active_reactions.allow_all_) { + for (auto &reaction : recent_reactions) { + if (is_custom_reaction(reaction)) { + show_premium = true; + } + } + for (auto &reaction : top_reactions) { + if (is_custom_reaction(reaction)) { + show_premium = true; + } + } + } + + FlatHashSet all_available_reactions; + for (const auto &reaction : available_reactions.reactions_) { + CHECK(!reaction.empty()); + all_available_reactions.insert(reaction); + } + + vector> top_reaction_objects; + vector> recent_reaction_objects; + vector> popular_reaction_objects; + vector> last_reaction_objects; + + FlatHashSet added_custom_reactions; + auto add_reactions = [&](vector> &reaction_objects, + const vector &reactions) { + for (auto &reaction : reactions) { + if (all_available_reactions.erase(reaction) != 0) { + // add available reaction + if (is_custom_reaction(reaction)) { + added_custom_reactions.insert(reaction); + } + reaction_objects.push_back( + td_api::make_object(get_reaction_type_object(reaction), false)); + } else if (is_custom_reaction(reaction) && available_reactions.allow_custom_ && + added_custom_reactions.insert(reaction).second) { + // add implicitly available custom reaction + reaction_objects.push_back( + td_api::make_object(get_reaction_type_object(reaction), !is_premium)); + } else { + // skip the reaction + } + } + }; + if (show_premium) { + if (top_reactions.size() > 2 * static_cast(row_size)) { + top_reactions.resize(2 * static_cast(row_size)); + } + add_reactions(top_reaction_objects, top_reactions); + + if (!recent_reactions.empty()) { + add_reactions(recent_reaction_objects, recent_reactions); + } + } else { + add_reactions(top_reaction_objects, top_reactions); + } + add_reactions(last_reaction_objects, active_reactions_); + add_reactions(last_reaction_objects, available_reactions.reactions_); + + if (show_premium) { + if (recent_reactions.empty()) { + popular_reaction_objects = std::move(last_reaction_objects); + } else { + auto max_objects = 10 * static_cast(row_size); + if (recent_reaction_objects.size() + last_reaction_objects.size() > max_objects) { + if (last_reaction_objects.size() < max_objects) { + recent_reaction_objects.resize(max_objects - last_reaction_objects.size()); + } else { + recent_reaction_objects.clear(); + } + } + append(recent_reaction_objects, std::move(last_reaction_objects)); + } + } else { + append(top_reaction_objects, std::move(last_reaction_objects)); + } + + CHECK(all_available_reactions.empty()); + + return td_api::make_object( + std::move(top_reaction_objects), std::move(recent_reaction_objects), std::move(popular_reaction_objects), + available_reactions.allow_custom_); } -vector MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m) { +ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m, + bool dissalow_custom_for_non_premium) { CHECK(d != nullptr); CHECK(m != nullptr); auto active_reactions = get_message_active_reactions(d, m); @@ -24458,116 +24724,118 @@ vector MessagesManager::get_message_available_reactions(const } } - vector result; - if (can_use_reactions) { - bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); - int64 reactions_uniq_max = td_->option_manager_->get_option_integer("reactions_uniq_max", 11); - bool can_add_new_reactions = - m->reactions == nullptr || static_cast(m->reactions->reactions_.size()) < reactions_uniq_max; - // can add only active available reactions or remove previously set reaction - for (const auto &active_reaction : active_reactions_) { - // can add the reaction if it has already been used for the message or is available in the chat - bool is_set = (m->reactions != nullptr && m->reactions->get_reaction(active_reaction.reaction_) != nullptr); - if (is_set || (can_add_new_reactions && td::contains(active_reactions, active_reaction.reaction_))) { - result.emplace_back(active_reaction.reaction_, !is_premium && active_reaction.is_premium_ && !is_set); - } - } + int64 reactions_uniq_max = td_->option_manager_->get_option_integer("reactions_uniq_max", 11); + bool can_add_new_reactions = + m->reactions == nullptr || static_cast(m->reactions->reactions_.size()) < reactions_uniq_max; + + if (!can_use_reactions || !can_add_new_reactions) { + active_reactions = ChatReactions(); + } + + if (active_reactions.allow_all_) { + active_reactions.reactions_ = active_reactions_; + active_reactions.allow_all_ = false; } if (m->reactions != nullptr) { for (const auto &reaction : m->reactions->reactions_) { - if (reaction.is_chosen() && - get_reaction_type(result, reaction.get_reaction()) == AvailableReactionType::Unavailable) { - CHECK(!can_use_reactions || - get_reaction_type(active_reactions_, reaction.get_reaction()) == AvailableReactionType::Unavailable); - result.emplace_back(reaction.get_reaction(), false); + // an already used reaction can be added if it is an active reaction + const string &reaction_str = reaction.get_reaction(); + if (can_use_reactions && is_active_reaction(reaction_str, active_reaction_pos_) && + !td::contains(active_reactions.reactions_, reaction_str)) { + active_reactions.reactions_.push_back(reaction_str); } } } - return result; + if (dissalow_custom_for_non_premium && !td_->option_manager_->get_option_boolean("is_premium")) { + active_reactions.allow_custom_ = false; + } + return active_reactions; } -void MessagesManager::set_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, - Promise &&promise) { +void MessagesManager::add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, + bool add_to_recent, Promise &&promise) { auto dialog_id = full_message_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "set_message_reaction"); + Dialog *d = get_dialog_force(dialog_id, "add_message_reaction"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } - Message *m = get_message_force(d, full_message_id.get_message_id(), "set_message_reaction"); + Message *m = get_message_force(d, full_message_id.get_message_id(), "add_message_reaction"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!reaction.empty()) { - auto reaction_type = get_reaction_type(get_message_available_reactions(d, m), reaction); - if (reaction_type == AvailableReactionType::Unavailable) { - return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); - } - if (reaction_type == AvailableReactionType::NeedsPremium) { - return promise.set_error(Status::Error(400, "The reaction is available only for Telegram Premium users")); - } + if (!get_message_available_reactions(d, m, true).is_allowed_reaction(reaction)) { + return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); } - bool can_get_added_reactions = !is_broadcast_channel(dialog_id) && dialog_id.get_type() != DialogType::User && - !is_discussion_message(dialog_id, m); + bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m); if (m->reactions == nullptr) { - if (reaction.empty()) { - return promise.set_value(Unit()); - } - m->reactions = make_unique(); - m->reactions->can_get_added_reactions_ = can_get_added_reactions; + m->reactions->can_get_added_reactions_ = have_recent_choosers && dialog_id.get_type() != DialogType::User; m->available_reactions_generation = d->available_reactions_generation; } - bool is_found = false; - for (auto it = m->reactions->reactions_.begin(); it != m->reactions->reactions_.end();) { - auto &message_reaction = *it; - if (message_reaction.is_chosen()) { - if (message_reaction.get_reaction() == reaction && !is_big) { - // double set removes reaction, unless a big reaction is set - reaction = string(); - } - message_reaction.set_is_chosen(false, get_my_dialog_id(), can_get_added_reactions); - } - if (message_reaction.get_reaction() == reaction) { - message_reaction.set_is_chosen(true, get_my_dialog_id(), can_get_added_reactions); - is_found = true; - } - - if (message_reaction.is_empty()) { - it = m->reactions->reactions_.erase(it); - } else { - ++it; - } + if (!m->reactions->add_reaction(reaction, is_big, get_my_dialog_id(), have_recent_choosers)) { + return promise.set_value(Unit()); } - pending_reactions_[full_message_id].query_count++; - - if (!is_found && !reaction.empty()) { - vector recent_chooser_dialog_ids; - if (!is_broadcast_channel(dialog_id)) { - recent_chooser_dialog_ids.push_back(get_my_dialog_id()); - } - m->reactions->reactions_.emplace_back(reaction, 1, true, std::move(recent_chooser_dialog_ids), Auto()); + if (add_to_recent) { + add_recent_reaction(td_, reaction); } - m->reactions->sort_reactions(active_reaction_pos_); - send_update_message_interaction_info(dialog_id, m); - on_message_changed(d, m, true, "set_message_reaction"); - - // TODO invoke_after, cancel previous queries, log event - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result &&result) mutable { - send_closure(actor_id, &MessagesManager::on_set_message_reaction, full_message_id, std::move(result), - std::move(promise)); - }); - ::td::set_message_reaction(td_, full_message_id, std::move(reaction), is_big, std::move(query_promise)); + set_message_reactions(d, m, is_big, add_to_recent, std::move(promise)); } -void MessagesManager::on_set_message_reaction(FullMessageId full_message_id, Result result, - Promise promise) { +void MessagesManager::remove_message_reaction(FullMessageId full_message_id, string reaction, Promise &&promise) { + auto dialog_id = full_message_id.get_dialog_id(); + Dialog *d = get_dialog_force(dialog_id, "remove_message_reaction"); + if (d == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + Message *m = get_message_force(d, full_message_id.get_message_id(), "remove_message_reaction"); + if (m == nullptr) { + return promise.set_error(Status::Error(400, "Message not found")); + } + + if (reaction.empty()) { + return promise.set_error(Status::Error(400, "Invalid reaction specified")); + } + + bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m); + if (m->reactions == nullptr || !m->reactions->remove_reaction(reaction, get_my_dialog_id(), have_recent_choosers)) { + return promise.set_value(Unit()); + } + + set_message_reactions(d, m, false, false, std::move(promise)); +} + +void MessagesManager::set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, + Promise &&promise) { + CHECK(m->reactions != nullptr); + m->reactions->sort_reactions(active_reaction_pos_); + + LOG(INFO) << "Update message reactions to " << *m->reactions; + + FullMessageId full_message_id{d->dialog_id, m->message_id}; + pending_reactions_[full_message_id].query_count++; + + send_update_message_interaction_info(d->dialog_id, m); + on_message_changed(d, m, true, "set_message_reactions"); + + // TODO cancel previous queries, log event + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result &&result) mutable { + send_closure(actor_id, &MessagesManager::on_set_message_reactions, full_message_id, std::move(result), + std::move(promise)); + }); + send_message_reaction(td_, full_message_id, m->reactions->get_chosen_reactions(), is_big, add_to_recent, + std::move(query_promise)); +} + +void MessagesManager::on_set_message_reactions(FullMessageId full_message_id, Result result, + Promise promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); bool need_reload = result.is_error(); @@ -24808,6 +25076,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto can_get_message_thread = for_event_log ? false : get_top_thread_full_message_id(dialog_id, m).is_ok(); auto can_get_viewers = for_event_log ? false : can_get_message_viewers(dialog_id, m).is_ok(); auto can_get_media_timestamp_links = for_event_log ? false : can_get_media_timestamp_link(dialog_id, m).is_ok(); + auto can_report_reactions = for_event_log ? false : can_report_message_reactions(dialog_id, m); auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"); auto media_album_id = for_event_log ? static_cast(0) : m->media_album_id; auto reply_to_message_id = for_event_log ? static_cast(0) : m->reply_to_message_id.get(); @@ -24831,11 +25100,11 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial m->message_id.get(), std::move(sender), dialog_id.get(), std::move(sending_state), std::move(scheduling_state), is_outgoing, is_pinned, can_be_edited, can_be_forwarded, can_be_saved, can_delete_for_self, can_delete_for_all_users, can_get_added_reactions, can_get_statistics, can_get_message_thread, can_get_viewers, - can_get_media_timestamp_links, has_timestamped_media, m->is_channel_post, contains_unread_mention, date, - edit_date, std::move(forward_info), std::move(interaction_info), std::move(unread_reactions), - reply_in_dialog_id.get(), reply_to_message_id, top_thread_message_id, ttl, ttl_expires_in, via_bot_user_id, - m->author_signature, media_album_id, get_restriction_reason_description(m->restriction_reasons), - std::move(content), std::move(reply_markup)); + can_get_media_timestamp_links, can_report_reactions, has_timestamped_media, m->is_channel_post, + contains_unread_mention, date, edit_date, std::move(forward_info), std::move(interaction_info), + std::move(unread_reactions), reply_in_dialog_id.get(), reply_to_message_id, top_thread_message_id, ttl, + ttl_expires_in, via_bot_user_id, m->author_signature, media_album_id, + get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } tl_object_ptr MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id, @@ -24979,6 +25248,7 @@ unique_ptr MessagesManager::create_message_to_send( m->is_channel_post = is_channel_post; m->is_outgoing = is_scheduled || dialog_id != DialogId(my_id); m->from_background = options.from_background; + m->update_stickersets_order = options.update_stickersets_order; m->noforwards = options.protect_content; m->view_count = is_channel_post && !is_scheduled ? 1 : 0; m->forward_count = 0; @@ -25068,6 +25338,9 @@ MessagesManager::Message *MessagesManager::get_message_to_send( if (result->message_id.is_scheduled()) { send_update_chat_has_scheduled_messages(d, false); } + if (options.update_stickersets_order && !td_->auth_manager_->is_bot()) { + move_message_content_sticker_set_to_top(td_, result->content.get()); + } return result; } @@ -25537,7 +25810,7 @@ Result> MessagesManager::send_message( TRY_STATUS(can_send_message(dialog_id)); TRY_RESULT(message_reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup))); TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); - TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options))); + TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); TRY_STATUS(can_use_message_send_options(message_send_options, message_content)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id)); @@ -25658,11 +25931,15 @@ Result MessagesManager::process_message_copy_options( } Result MessagesManager::process_message_send_options( - DialogId dialog_id, tl_object_ptr &&options) const { + DialogId dialog_id, tl_object_ptr &&options, + bool allow_update_stickersets_order) const { MessageSendOptions result; if (options != nullptr) { result.disable_notification = options->disable_notification_; result.from_background = options->from_background_; + if (allow_update_stickersets_order) { + result.update_stickersets_order = options->update_order_of_installed_sticker_sets_; + } result.protect_content = options->protect_content_; TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_))); } @@ -25767,7 +26044,7 @@ Result> MessagesManager::send_message_group } TRY_STATUS(can_send_message(dialog_id)); - TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options))); + TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); vector, int32>> message_contents; std::unordered_set message_content_types; @@ -26130,9 +26407,13 @@ void MessagesManager::on_upload_message_media_success(DialogId dialog_id, Messag auto content = get_message_content(td_, caption == nullptr ? FormattedText() : *caption, std::move(media), dialog_id, false, UserId(), nullptr, nullptr, "on_upload_message_media_success"); - if (update_message_content(dialog_id, m, std::move(content), true, true, true) && - m->message_id == d->last_message_id) { - send_update_chat_last_message_impl(d, "on_upload_message_media_success"); + bool is_content_changed = false; + bool need_update = update_message_content(dialog_id, m, std::move(content), true, true, is_content_changed); + if (need_update) { + send_update_message_content(d, m, true, "on_upload_message_media_success"); + } + if (is_content_changed || need_update) { + on_message_changed(d, m, need_update, "on_upload_message_media_success"); } auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true); @@ -26647,7 +26928,7 @@ Result MessagesManager::send_inline_query_result_message(DialogId dia } TRY_STATUS(can_send_message(dialog_id)); - TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options))); + TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), false)); bool to_secret = false; switch (dialog_id.get_type()) { case DialogType::User: @@ -27215,7 +27496,9 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess // must not run getDifference CHECK(message_id.is_any_server()); - auto m = get_message({dialog_id, message_id}); + Dialog *d = get_dialog(dialog_id); + CHECK(d != nullptr); + auto m = get_message(d, message_id); if (m == nullptr || m->edit_generation != generation) { // message is already deleted or was edited again return; @@ -27234,8 +27517,17 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess bool need_send_update_message_content = m->edited_content->get_type() == MessageContentType::Photo && m->content->get_type() == MessageContentType::Photo; bool need_merge_files = pts != 0 && pts == m->last_edit_pts; - update_message_content(dialog_id, m, std::move(m->edited_content), need_send_update_message_content, - need_merge_files, true); + bool is_content_changed = false; + bool need_update = + update_message_content(dialog_id, m, std::move(m->edited_content), need_merge_files, true, is_content_changed); + if (need_send_update_message_content) { + if (need_update) { + send_update_message_content(d, m, true, "on_message_media_edited"); + } + if (is_content_changed || need_update) { + on_message_changed(d, m, need_update, "on_message_media_edited"); + } + } } else { LOG(INFO) << "Failed to edit " << message_id << " in " << dialog_id << ": " << result.error(); if (was_uploaded) { @@ -27906,6 +28198,9 @@ int32 MessagesManager::get_message_flags(const Message *m) { if (m->noforwards) { flags |= SEND_MESSAGE_FLAG_NOFORWARDS; } + if (m->update_stickersets_order) { + flags |= SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER; + } return flags; } @@ -28431,7 +28726,7 @@ Result MessagesManager::get_forwarded_messag } TRY_STATUS(can_send_message(to_dialog_id)); - TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options))); + TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false)); { MessageId last_message_id; @@ -28832,7 +29127,8 @@ Result> MessagesManager::resend_messages(DialogId dialog_id, v auto need_another_sender = message->send_error_code == 400 && message->send_error_message == CSlice("SEND_AS_PEER_INVALID"); - MessageSendOptions options(message->disable_notification, message->from_background, message->noforwards, + MessageSendOptions options(message->disable_notification, message->from_background, + message->update_stickersets_order, message->noforwards, get_message_schedule_date(message.get())); Message *m = get_message_to_send( d, message->top_thread_message_id, @@ -29039,6 +29335,7 @@ Result MessagesManager::add_local_message( m->is_outgoing = dialog_id != DialogId(my_id) && sender_user_id == my_id; m->disable_notification = disable_notification; m->from_background = false; + m->update_stickersets_order = false; m->view_count = 0; m->forward_count = 0; m->content = std::move(message_content.content); @@ -30619,14 +30916,6 @@ void MessagesManager::send_update_message_send_succeeded(Dialog *d, MessageId ol get_message_object(d->dialog_id, m, "send_update_message_send_succeeded"), old_message_id.get())); } -void MessagesManager::send_update_message_content(DialogId dialog_id, Message *m, bool is_message_in_dialog, - const char *source) { - Dialog *d = get_dialog(dialog_id); - LOG_CHECK(d != nullptr) << "Send updateMessageContent in unknown " << dialog_id << " from " << source - << " with load count " << loaded_dialogs_.count(dialog_id); - send_update_message_content(d, m, is_message_in_dialog, source); -} - void MessagesManager::send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog, const char *source) { CHECK(d != nullptr); @@ -31051,9 +31340,10 @@ void MessagesManager::send_update_chat_available_reactions(const Dialog *d) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_available_reactions"; + auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); send_closure( G()->td(), &Td::send_update, - td_api::make_object(d->dialog_id.get(), get_dialog_active_reactions(d))); + td_api::make_object(d->dialog_id.get(), std::move(available_reactions))); } void MessagesManager::send_update_secret_chats_with_user_theme(const Dialog *d) const { @@ -33446,9 +33736,7 @@ vector MessagesManager::get_dialog_lists_to_add_dialog(DialogId di auto new_dialog_filter = make_unique(*dialog_filter); new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id)); - td::remove_if(new_dialog_filter->excluded_dialog_ids, [dialog_id](InputDialogId input_dialog_id) { - return dialog_id == input_dialog_id.get_dialog_id(); - }); + InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id); if (new_dialog_filter->check_limits().is_ok()) { result.push_back(DialogListId(dialog_filter_id)); @@ -33490,8 +33778,7 @@ void MessagesManager::add_dialog_to_list(DialogId dialog_id, DialogListId dialog auto new_dialog_filter = make_unique(*old_dialog_filter); new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id)); - td::remove_if(new_dialog_filter->excluded_dialog_ids, - [dialog_id](InputDialogId input_dialog_id) { return dialog_id == input_dialog_id.get_dialog_id(); }); + InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id); auto status = new_dialog_filter->check_limits(); if (status.is_error()) { @@ -33704,7 +33991,7 @@ void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, auto new_title = clean_name(title, MAX_TITLE_LENGTH); if (new_title.empty()) { - return promise.set_error(Status::Error(400, "Title can't be empty")); + return promise.set_error(Status::Error(400, "Title must be non-empty")); } switch (dialog_id.get_type()) { @@ -33742,15 +34029,17 @@ void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, td_->create_handler(std::move(promise))->send(dialog_id, new_title); } -void MessagesManager::set_dialog_available_reactions(DialogId dialog_id, vector available_reactions, - Promise &&promise) { +void MessagesManager::set_dialog_available_reactions( + DialogId dialog_id, td_api::object_ptr &&available_reactions_ptr, + Promise &&promise) { Dialog *d = get_dialog_force(dialog_id, "set_dialog_available_reactions"); if (d == nullptr) { return promise.set_error(Status::Error(400, "Chat not found")); } + ChatReactions available_reactions(std::move(available_reactions_ptr), !is_broadcast_channel(dialog_id)); auto active_reactions = get_active_reactions(available_reactions); - if (active_reactions.size() != available_reactions.size()) { + if (active_reactions.reactions_.size() != available_reactions.reactions_.size()) { return promise.set_error(Status::Error(400, "Invalid reactions specified")); } available_reactions = std::move(active_reactions); @@ -33783,7 +34072,7 @@ void MessagesManager::set_dialog_available_reactions(DialogId dialog_id, vector< bool is_changed = d->available_reactions != available_reactions; - set_dialog_available_reactions(d, vector(available_reactions)); + set_dialog_available_reactions(d, ChatReactions(available_reactions)); if (!is_changed) { return promise.set_value(Unit()); @@ -34393,7 +34682,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(DialogId dialog CHECK(!being_added_by_new_message_dialog_id_.is_valid()); being_added_by_new_message_dialog_id_ = dialog_id; } - d = add_dialog(dialog_id, "add_message_to_dialog"); + d = add_dialog(dialog_id, source); *need_update_dialog_pos = true; being_added_by_new_message_dialog_id_ = DialogId(); } else { @@ -34664,27 +34953,8 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq try_add_active_live_location(dialog_id, m); } change_message_files(dialog_id, m, old_file_ids); - if (need_send_update && m->notification_id.is_valid() && is_message_notification_active(d, m)) { - auto &group_info = get_notification_group_info(d, m); - if (group_info.group_id.is_valid()) { - send_closure_later( - G()->notification_manager(), &NotificationManager::edit_notification, group_info.group_id, - m->notification_id, - create_new_message_notification( - m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m)))); - } - } - if (need_send_update && m->is_pinned && d->pinned_message_notification_message_id.is_valid() && - d->mention_notification_group.group_id.is_valid()) { - auto pinned_message = get_message_force(d, d->pinned_message_notification_message_id, "after update_message"); - if (pinned_message != nullptr && pinned_message->notification_id.is_valid() && - is_message_notification_active(d, pinned_message) && - get_message_content_pinned_message_id(pinned_message->content.get()) == message_id) { - send_closure_later( - G()->notification_manager(), &NotificationManager::edit_notification, - d->mention_notification_group.group_id, pinned_message->notification_id, - create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true))); - } + if (need_send_update) { + on_message_notification_changed(d, m, source); } update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask); update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask); @@ -34785,7 +35055,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled() && !d->has_unload_timeout) { LOG(INFO) << "Schedule unload of " << dialog_id; - pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay()); + pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d)); d->has_unload_timeout = true; } @@ -34972,8 +35242,9 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } + const Message *m = message.get(); if (*need_update && message_id > d->last_read_inbox_message_id && !td_->auth_manager_->is_bot()) { - if (has_incoming_notification(dialog_id, message.get())) { + if (has_incoming_notification(dialog_id, m)) { int32 server_unread_count = d->server_unread_count; int32 local_unread_count = d->local_unread_count; if (message_id.is_server()) { @@ -34993,19 +35264,19 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } } } - if (*need_update && message->contains_unread_mention) { + if (*need_update && m->contains_unread_mention) { set_dialog_unread_mention_count(d, d->unread_mention_count + 1); send_update_chat_unread_mention_count(d); } - if (*need_update && has_unread_message_reactions(dialog_id, message.get())) { + if (*need_update && has_unread_message_reactions(dialog_id, m)) { set_dialog_unread_reaction_count(d, d->unread_reaction_count + 1); send_update_chat_unread_reaction_count(d, "add_message_to_dialog"); } if (*need_update) { - update_message_count_by_index(d, +1, message.get()); + update_message_count_by_index(d, +1, m); } if (auto_attach && message_id > d->last_message_id && message_id >= d->last_new_message_id) { - set_dialog_last_message_id(d, message_id, "add_message_to_dialog"); + set_dialog_last_message_id(d, message_id, "add_message_to_dialog", m); *need_update_dialog_pos = true; } if (auto_attach && !message_id.is_yet_unsent() && message_id >= d->last_new_message_id && @@ -35018,12 +35289,11 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq set_dialog_last_database_message_id(d, message_id, "add_message_to_dialog"); if (!d->first_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, message_id, "add_message_to_dialog"); - try_restore_dialog_reply_markup(d, message.get()); + try_restore_dialog_reply_markup(d, m); } } } - const Message *m = message.get(); if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) { if (!m->reply_to_message_id.is_yet_unsent()) { if (!m->reply_to_message_id.is_scheduled()) { @@ -35414,6 +35684,32 @@ void MessagesManager::on_message_changed(const Dialog *d, const Message *m, bool } } +void MessagesManager::on_message_notification_changed(Dialog *d, const Message *m, const char *source) { + CHECK(d != nullptr); + CHECK(m != nullptr); + if (m->notification_id.is_valid() && is_message_notification_active(d, m)) { + auto &group_info = get_notification_group_info(d, m); + if (group_info.group_id.is_valid()) { + send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, group_info.group_id, + m->notification_id, + create_new_message_notification( + m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m)))); + } + } + if (m->is_pinned && d->pinned_message_notification_message_id.is_valid() && + d->mention_notification_group.group_id.is_valid()) { + auto pinned_message = get_message_force(d, d->pinned_message_notification_message_id, "after update_message"); + if (pinned_message != nullptr && pinned_message->notification_id.is_valid() && + is_message_notification_active(d, pinned_message) && + get_message_content_pinned_message_id(pinned_message->content.get()) == m->message_id) { + send_closure_later( + G()->notification_manager(), &NotificationManager::edit_notification, d->mention_notification_group.group_id, + pinned_message->notification_id, + create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true))); + } + } +} + void MessagesManager::add_message_to_database(const Dialog *d, const Message *m, const char *source) { if (!G()->parameters().use_message_db) { return; @@ -36081,15 +36377,6 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr old_message->disable_web_page_preview = new_message->disable_web_page_preview; } - if (!is_scheduled && - update_message_contains_unread_mention(d, old_message, new_message->contains_unread_mention, "update_message")) { - need_send_update = true; - } - if (update_message_interaction_info(d, old_message, new_message->view_count, new_message->forward_count, true, - std::move(new_message->reply_info), true, std::move(new_message->reactions), - "update_message")) { - need_send_update = true; - } if (old_message->noforwards != new_message->noforwards) { LOG(DEBUG) << "Message can_be_saved has changed from " << !old_message->noforwards << " to " << !old_message->noforwards; @@ -36125,10 +36412,6 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr // old_message->is_from_scheduled = new_message->is_from_scheduled; } - if (!is_scheduled && update_message_is_pinned(d, old_message, new_message->is_pinned, "update_message")) { - need_send_update = true; - } - if (old_message->edit_date > 0) { // inline keyboard can be edited bool reply_markup_changed = @@ -36193,10 +36476,23 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr if (old_message->last_access_date < new_message->last_access_date) { old_message->last_access_date = new_message->last_access_date; } - if (new_message->is_update_sent) { + if (new_message->is_update_sent || is_edited) { old_message->is_update_sent = true; } + if (!is_scheduled && update_message_is_pinned(d, old_message, new_message->is_pinned, "update_message")) { + need_send_update = true; + } + if (!is_scheduled && + update_message_contains_unread_mention(d, old_message, new_message->contains_unread_mention, "update_message")) { + need_send_update = true; + } + if (update_message_interaction_info(d, old_message, new_message->view_count, new_message->forward_count, true, + std::move(new_message->reply_info), true, std::move(new_message->reactions), + "update_message")) { + need_send_update = true; + } + if (!is_scheduled) { CHECK(!new_message->have_previous || !new_message->have_next); if (new_message->have_previous && !old_message->have_previous) { @@ -36208,8 +36504,11 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } } - if (update_message_content(dialog_id, old_message, std::move(new_message->content), true, - message_id.is_yet_unsent() && new_message->edit_date == 0, is_message_in_dialog)) { + bool is_content_changed = false; + if (update_message_content(dialog_id, old_message, std::move(new_message->content), + message_id.is_yet_unsent() && new_message->edit_date == 0, is_message_in_dialog, + is_content_changed)) { + send_update_message_content(d, old_message, is_message_in_dialog, "update_message"); need_send_update = true; } @@ -36218,7 +36517,6 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } if (is_edited && !td_->auth_manager_->is_bot()) { - d->last_edited_message_id = message_id; send_update_message_edited(dialog_id, old_message); } @@ -36252,11 +36550,11 @@ bool MessagesManager::need_message_changed_warning(const Message *old_message) { } bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_message, - unique_ptr new_content, - bool need_send_update_message_content, bool need_merge_files, - bool is_message_in_dialog) { - bool is_content_changed = false; + unique_ptr new_content, bool need_merge_files, + bool is_message_in_dialog, bool &is_content_changed) { + is_content_changed = false; bool need_update = false; + unique_ptr &old_content = old_message->content; MessageContentType old_content_type = old_content->get_type(); MessageContentType new_content_type = new_content->get_type(); @@ -36325,10 +36623,6 @@ bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_me } if (need_update) { - if (need_send_update_message_content) { - send_update_message_content(dialog_id, old_message, is_message_in_dialog, "update_message_content"); - } - auto file_ids = get_message_content_file_ids(old_content.get(), td_); if (!file_ids.empty()) { auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, old_message->message_id)); @@ -36428,8 +36722,8 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source return; } - d = add_dialog(dialog_id, "force_create_dialog"); - update_dialog_pos(d, "force_create_dialog"); + d = add_dialog(dialog_id, source); + update_dialog_pos(d, source); if (dialog_id.get_type() == DialogType::SecretChat && !d->notification_settings.is_synchronized && td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) != SecretChatState::Closed) { @@ -36673,7 +36967,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di fix_new_dialog(d, std::move(last_database_message), last_database_message_id, order, last_clear_history_date, last_clear_history_message_id, default_join_group_call_as_dialog_id, default_send_message_as_dialog_id, - need_drop_default_send_message_as_dialog_id, is_loaded_from_database); + need_drop_default_send_message_as_dialog_id, is_loaded_from_database, source); return d; } @@ -36683,7 +36977,8 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab MessageId last_clear_history_message_id, DialogId default_join_group_call_as_dialog_id, DialogId default_send_message_as_dialog_id, - bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database) { + bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database, + const char *source) { CHECK(d != nullptr); auto dialog_id = d->dialog_id; auto dialog_type = dialog_id.get_type(); @@ -37034,9 +37329,10 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab auto common_data = PSTRING() << ' ' << last_message_id << ' ' << d->last_message_id << ' ' << d->last_database_message_id << ' ' << d->debug_set_dialog_last_database_message_id << ' ' << d->messages->debug_source << ' ' - << is_loaded_from_database << ' ' << being_added_dialog_id_ << ' ' << being_added_new_dialog_id_ - << ' ' << dialog_id << ' ' << d->is_channel_difference_finished << ' ' - << debug_last_get_channel_difference_dialog_id_ << ' ' << debug_last_get_channel_difference_source_; + << is_loaded_from_database << ' ' << source << ' ' << being_added_dialog_id_ << ' ' + << being_added_new_dialog_id_ << ' ' << dialog_id << ' ' << d->is_channel_difference_finished << ' ' + << debug_last_get_channel_difference_dialog_id_ << ' ' << debug_last_get_channel_difference_source_ + << ' ' << G()->parameters().use_message_db; LOG_CHECK(d->messages->message_id == last_message_id) << d->messages->message_id << common_data; LOG_CHECK(d->messages->left == nullptr) << d->messages->left->message_id << ' ' << d->messages->message_id << ' ' << d->messages->left->message_id @@ -37107,7 +37403,7 @@ bool MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptrmessage_id, "add_dialog_last_database_message 2"); + set_dialog_last_message_id(d, m->message_id, "add_dialog_last_database_message 2", m); send_update_chat_last_message(d, "add_dialog_last_database_message 3"); } else { if (d->pending_last_message_date != 0) { @@ -37583,7 +37879,7 @@ bool MessagesManager::do_update_list_last_pinned_dialog_date(DialogList &list) c } DialogDate max_dialog_date = MIN_DIALOG_DATE; - for (auto &pinned_dialog : list.pinned_dialogs_) { + for (const auto &pinned_dialog : list.pinned_dialogs_) { if (!have_dialog(pinned_dialog.get_dialog_id())) { break; } @@ -38654,7 +38950,8 @@ void MessagesManager::on_get_channel_difference( if (delay == 0) { delay = 1; } - channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), delay); + channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), + Random::fast(delay * 1000, delay * 1500) * 1e-3); delay *= 2; if (delay > 60) { delay = Random::fast(60, 80); @@ -38779,10 +39076,10 @@ void MessagesManager::on_get_channel_difference( case telegram_api::updates_channelDifferenceTooLong::ID: { auto difference = move_tl_object_as(difference_ptr); - tl_object_ptr dialog; + telegram_api::dialog *dialog = nullptr; switch (difference->dialog_->get_id()) { case telegram_api::dialog::ID: - dialog = telegram_api::move_object_as(difference->dialog_); + dialog = static_cast(difference->dialog_.get()); break; case telegram_api::dialogFolder::ID: return after_get_channel_difference(dialog_id, false); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index dce02bd81..e16ccbf9b 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -8,8 +8,8 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AffectedHistory.h" -#include "td/telegram/AvailableReaction.h" #include "td/telegram/ChannelId.h" +#include "td/telegram/ChatReactions.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogDate.h" #include "td/telegram/DialogDb.h" @@ -144,6 +144,7 @@ class MessagesManager final : public Actor { static constexpr int32 SEND_MESSAGE_FLAG_HAS_MESSAGE = 1 << 11; static constexpr int32 SEND_MESSAGE_FLAG_HAS_SEND_AS = 1 << 13; static constexpr int32 SEND_MESSAGE_FLAG_NOFORWARDS = 1 << 14; + static constexpr int32 SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER = 1 << 15; static constexpr int32 ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME = 30 * 60; @@ -367,6 +368,8 @@ class MessagesManager final : public Actor { void on_external_update_message_content(FullMessageId full_message_id); + void on_update_message_content(FullMessageId full_message_id); + void on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count, int32 pts, const char *source); @@ -538,9 +541,11 @@ class MessagesManager final : public Actor { void set_dialog_description(DialogId dialog_id, const string &description, Promise &&promise); - void set_active_reactions(vector active_reactions); + void set_active_reactions(vector active_reactions); - void set_dialog_available_reactions(DialogId dialog_id, vector available_reactions, Promise &&promise); + void set_dialog_available_reactions(DialogId dialog_id, + td_api::object_ptr &&available_reactions_ptr, + Promise &&promise); void set_dialog_permissions(DialogId dialog_id, const td_api::object_ptr &permissions, Promise &&promise); @@ -804,9 +809,13 @@ class MessagesManager final : public Actor { vector get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result, Promise &&promise); - Result> get_message_available_reactions(FullMessageId full_message_id); + Result> get_message_available_reactions(FullMessageId full_message_id, + int32 row_size); - void set_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, Promise &&promise); + void add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, bool add_to_recent, + Promise &&promise); + + void remove_message_reaction(FullMessageId full_message_id, string reaction, Promise &&promise); void get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit, Promise> &&promise); @@ -863,7 +872,8 @@ class MessagesManager final : public Actor { tl_object_ptr &&peer_notify_settings, const char *source); - void on_update_dialog_available_reactions(DialogId dialog_id, vector &&available_reactions); + void on_update_dialog_available_reactions( + DialogId dialog_id, telegram_api::object_ptr &&available_reactions); void hide_dialog_action_bar(DialogId dialog_id); @@ -1078,13 +1088,26 @@ class MessagesManager final : public Actor { } friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageForwardInfo &forward_info) { - return string_builder << "MessageForwardInfo[" << (forward_info.is_imported ? "imported " : "") << "sender " - << forward_info.sender_user_id << "(" << forward_info.author_signature << "/" - << forward_info.sender_name << "), psa_type " << forward_info.psa_type << ", source " - << forward_info.sender_dialog_id << ", source " << forward_info.message_id << ", from " - << forward_info.from_dialog_id << ", from " << forward_info.from_message_id << " at " - << forward_info.date << " " - << "]"; + string_builder << "MessageForwardInfo[" << (forward_info.is_imported ? "imported " : "") << "sender " + << forward_info.sender_user_id; + if (!forward_info.author_signature.empty() || !forward_info.sender_name.empty()) { + string_builder << '(' << forward_info.author_signature << '/' << forward_info.sender_name << ')'; + } + if (!forward_info.psa_type.empty()) { + string_builder << ", psa_type " << forward_info.psa_type; + } + if (forward_info.sender_dialog_id.is_valid()) { + string_builder << ", source "; + if (forward_info.message_id.is_valid()) { + string_builder << FullMessageId(forward_info.sender_dialog_id, forward_info.message_id); + } else { + string_builder << forward_info.sender_dialog_id; + } + } + if (forward_info.from_dialog_id.is_valid() || forward_info.from_message_id.is_valid()) { + string_builder << ", from " << FullMessageId(forward_info.from_dialog_id, forward_info.from_message_id); + } + return string_builder << " at " << forward_info.date << ']'; } }; @@ -1135,6 +1158,7 @@ class MessagesManager final : public Actor { bool has_explicit_sender = false; // for send_message bool is_copy = false; // for send_message bool from_background = false; // for send_message + bool update_stickersets_order = false; // for send_message bool disable_web_page_preview = false; // for send_message bool clear_draft = false; // for send_message bool in_game_share = false; // for send_message @@ -1252,7 +1276,7 @@ class MessagesManager final : public Actor { MessageId last_pinned_message_id; MessageId reply_markup_message_id; DialogNotificationSettings notification_settings; - vector available_reactions; + ChatReactions available_reactions; uint32 available_reactions_generation = 0; MessageTtl message_ttl; unique_ptr draft_message; @@ -1270,6 +1294,8 @@ class MessagesManager final : public Actor { int32 pending_join_request_count = 0; vector pending_join_request_user_ids; int32 have_full_history_source = 0; + int32 unload_dialog_delay_seed = 0; + int64 last_media_album_id = 0; FolderId folder_id; vector dialog_list_ids; // TODO replace with mask @@ -1704,13 +1730,16 @@ class MessagesManager final : public Actor { struct MessageSendOptions { bool disable_notification = false; bool from_background = false; + bool update_stickersets_order = false; bool protect_content = false; int32 schedule_date = 0; MessageSendOptions() = default; - MessageSendOptions(bool disable_notification, bool from_background, bool protect_content, int32 schedule_date) + MessageSendOptions(bool disable_notification, bool from_background, bool update_stickersets_order, + bool protect_content, int32 schedule_date) : disable_notification(disable_notification) , from_background(from_background) + , update_stickersets_order(update_stickersets_order) , protect_content(protect_content) , schedule_date(schedule_date) { } @@ -1864,7 +1893,8 @@ class MessagesManager final : public Actor { tl_object_ptr &&options) const; Result process_message_send_options(DialogId dialog_id, - tl_object_ptr &&options) const; + tl_object_ptr &&options, + bool allow_update_stickersets_order) const; static Status can_use_message_send_options(const MessageSendOptions &options, const unique_ptr &content, int32 ttl); @@ -1903,6 +1933,8 @@ class MessagesManager final : public Actor { static Status can_get_media_timestamp_link(DialogId dialog_id, const Message *m); + bool can_report_message_reactions(DialogId dialog_id, const Message *m) const; + Status can_get_message_viewers(FullMessageId full_message_id) TD_WARN_UNUSED_RESULT; Status can_get_message_viewers(DialogId dialog_id, const Message *m) const TD_WARN_UNUSED_RESULT; @@ -2086,7 +2118,7 @@ class MessagesManager final : public Actor { int32 get_unload_dialog_delay() const; - int32 get_next_unload_dialog_delay() const; + double get_next_unload_dialog_delay(Dialog *d) const; void unload_dialog(DialogId dialog_id); @@ -2329,6 +2361,8 @@ class MessagesManager final : public Actor { void on_message_changed(const Dialog *d, const Message *m, bool need_send_update, const char *source); + void on_message_notification_changed(Dialog *d, const Message *m, const char *source); + bool need_delete_file(FullMessageId full_message_id, FileId file_id) const; bool need_delete_message_files(DialogId dialog_id, const Message *m) const; @@ -2377,7 +2411,7 @@ class MessagesManager final : public Actor { static bool need_message_changed_warning(const Message *old_message); bool update_message_content(DialogId dialog_id, Message *old_message, unique_ptr new_content, - bool need_send_update_message_content, bool need_merge_files, bool is_message_in_dialog); + bool need_merge_files, bool is_message_in_dialog, bool &is_content_changed); void update_message_max_reply_media_timestamp(const Dialog *d, Message *m, bool need_send_update_message_content); @@ -2450,8 +2484,6 @@ class MessagesManager final : public Actor { void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const; - void send_update_message_content(DialogId dialog_id, Message *m, bool is_message_in_dialog, const char *source); - void send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog, const char *source); void send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const; @@ -2569,7 +2601,7 @@ class MessagesManager final : public Actor { void set_dialog_last_read_outbox_message_id(Dialog *d, MessageId message_id); - void set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source); + void set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source, const Message *m = nullptr); void set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id, const char *source); @@ -2598,6 +2630,8 @@ class MessagesManager final : public Actor { bool set_dialog_is_pinned(DialogListId dialog_list_id, Dialog *d, bool is_pinned, bool need_update_dialog_lists = true); + void save_pinned_folder_dialog_ids(const DialogList &list) const; + void set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread); void set_dialog_is_blocked(Dialog *d, bool is_blocked); @@ -2661,21 +2695,24 @@ class MessagesManager final : public Actor { bool update_dialog_silent_send_message(Dialog *d, bool silent_send_message); - vector get_message_available_reactions(const Dialog *d, const Message *m); + ChatReactions get_message_available_reactions(const Dialog *d, const Message *m, + bool dissalow_custom_for_non_premium); - void on_set_message_reaction(FullMessageId full_message_id, Result result, Promise promise); + void set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, Promise &&promise); - void set_dialog_available_reactions(Dialog *d, vector &&available_reactions); + void on_set_message_reactions(FullMessageId full_message_id, Result result, Promise promise); + + void set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions); void set_dialog_next_available_reactions_generation(Dialog *d, uint32 generation); void hide_dialog_message_reactions(Dialog *d); - vector get_active_reactions(const vector &available_reactions) const; + ChatReactions get_active_reactions(const ChatReactions &available_reactions) const; - vector get_dialog_active_reactions(const Dialog *d) const; + ChatReactions get_dialog_active_reactions(const Dialog *d) const; - vector get_message_active_reactions(const Dialog *d, const Message *m) const; + ChatReactions get_message_active_reactions(const Dialog *d, const Message *m) const; static bool need_poll_dialog_message_reactions(const Dialog *d); @@ -2706,7 +2743,8 @@ class MessagesManager final : public Actor { void fix_new_dialog(Dialog *d, unique_ptr &&last_database_message, MessageId last_database_message_id, int64 order, int32 last_clear_history_date, MessageId last_clear_history_message_id, DialogId default_join_group_call_as_dialog_id, DialogId default_send_message_as_dialog_id, - bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database); + bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database, + const char *source); bool add_dialog_last_database_message(Dialog *d, unique_ptr &&last_database_message); @@ -2763,6 +2801,12 @@ class MessagesManager final : public Actor { td_api::object_ptr get_chat_filter_object(const DialogFilter *filter) const; + void load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector &&input_dialog_ids, + Promise &&promise); + + void on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector &&dialog_ids, + Promise &&promise); + void load_dialog_filter(const DialogFilter *filter, bool force, Promise &&promise); void on_get_recommended_dialog_filters(Result>> result, @@ -3485,7 +3529,7 @@ class MessagesManager final : public Actor { struct CommonDialogs { vector dialog_ids; - double received_date = 0; + double receive_time = 0; int32 total_count = 0; bool is_outdated = false; }; @@ -3701,7 +3745,7 @@ class MessagesManager final : public Actor { }; FlatHashMap pending_reactions_; - vector active_reactions_; + vector active_reactions_; FlatHashMap active_reaction_pos_; uint32 scheduled_messages_sync_generation_ = 1; diff --git a/td/telegram/NotificationManager.cpp b/td/telegram/NotificationManager.cpp index 13955832d..d4f5b81df 100644 --- a/td/telegram/NotificationManager.cpp +++ b/td/telegram/NotificationManager.cpp @@ -3332,7 +3332,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), sender_access_hash, user_name, - string(), string(), string(), std::move(sender_photo), nullptr, 0, Auto(), string(), string()); + string(), string(), string(), std::move(sender_photo), nullptr, 0, Auto(), string(), string(), nullptr); td_->contacts_manager_->on_get_user(std::move(user), "process_push_notification_payload"); } @@ -3690,7 +3690,7 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), 0, user_name, string(), string(), - string(), nullptr, nullptr, 0, Auto(), string(), string()); + string(), nullptr, nullptr, 0, Auto(), string(), string(), nullptr); td_->contacts_manager_->on_get_user(std::move(user), "add_message_push_notification"); } diff --git a/td/telegram/OptionManager.cpp b/td/telegram/OptionManager.cpp index 3394bff67..dfd7b8bb9 100644 --- a/td/telegram/OptionManager.cpp +++ b/td/telegram/OptionManager.cpp @@ -54,17 +54,18 @@ OptionManager::OptionManager(Td *td) all_options["utc_time_offset"] = PSTRING() << 'I' << Clocks::tz_offset(); for (const auto &name_value : all_options) { const string &name = name_value.first; - const string &value = name_value.second; - options_->set(name, value); + options_->set(name, name_value.second); if (!is_internal_option(name)) { send_closure(G()->td(), &Td::send_update, - td_api::make_object(name, get_option_value_object(value))); + td_api::make_object(name, get_option_value_object(name_value.second))); } else if (name == "otherwise_relogin_days") { auto days = narrow_cast(get_option_integer(name)); if (days > 0) { vector added_actions{SuggestedAction{SuggestedAction::Type::SetPassword, DialogId(), days}}; send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object(added_actions, {})); } + } else if (name == "default_reaction") { + send_update_default_reaction_type(get_option_string(name)); } } @@ -101,6 +102,11 @@ OptionManager::OptionManager(Td *td) if (!have_option("chat_filter_chosen_chat_count_max")) { set_option_integer("chat_filter_chosen_chat_count_max", G()->is_test_dc() ? 5 : 100); } + if (!have_option("themed_emoji_statuses_sticker_set_id")) { + auto sticker_set_id = + G()->is_test_dc() ? static_cast(2964141614563343) : static_cast(773947703670341676); + set_option_integer("themed_emoji_statuses_sticker_set_id", sticker_set_id); + } } OptionManager::~OptionManager() = default; @@ -245,7 +251,7 @@ bool OptionManager::is_internal_option(Slice name) { 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 == "default_reaction_needs_sync" || + return name == "dc_txt_domain_name" || name == "default_reaction" || name == "default_reaction_needs_sync" || name == "dialog_filters_chats_limit_default" || name == "dialog_filters_chats_limit_premium" || name == "dialog_filters_limit_default" || name == "dialog_filters_limit_premium" || name == "dialogs_folder_pinned_limit_default" || name == "dialogs_folder_pinned_limit_premium" || @@ -266,8 +272,9 @@ bool OptionManager::is_internal_option(Slice name) { case 'p': return name == "premium_bot_username" || name == "premium_features" || name == "premium_invoice_slug"; case 'r': - 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"; + return name == "rating_e_decay" || name == "reactions_uniq_max" || name == "reactions_user_max_default" || + name == "reactions_user_max_premium" || 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 == "saved_gifs_limit_default" || name == "saved_gifs_limit_premium" || name == "session_count" || name == "stickers_faved_limit_default" || @@ -317,8 +324,8 @@ void OptionManager::on_option_updated(Slice name) { } break; case 'd': - if (name == "default_reaction_needs_sync" && get_option_boolean(name)) { - send_set_default_reaction_query(td_); + if (name == "default_reaction") { + send_update_default_reaction_type(get_option_string(name)); } if (name == "dice_emojis") { send_closure(td_->stickers_manager_actor_, &StickersManager::on_update_dice_emojis); @@ -504,7 +511,7 @@ td_api::object_ptr OptionManager::get_option_synchronously( break; case 'v': if (name == "version") { - return td_api::make_object("1.8.5"); + return td_api::make_object("1.8.6"); } break; } @@ -621,14 +628,6 @@ void OptionManager::set_option(const string &name, td_api::object_ptr(value.get())->value_; - } - set_default_reaction(td_, std::move(reaction), std::move(promise)); - return; - } if (!is_bot && set_boolean_option("disable_animated_emoji")) { return; } diff --git a/td/telegram/PasswordManager.cpp b/td/telegram/PasswordManager.cpp index e95900e03..20f44b80d 100644 --- a/td/telegram/PasswordManager.cpp +++ b/td/telegram/PasswordManager.cpp @@ -180,6 +180,46 @@ void PasswordManager::set_password(string current_password, string new_password, update_password_settings(std::move(update_settings), std::move(promise)); } +void PasswordManager::set_login_email_address(string new_login_email_address, Promise promise) { + last_set_login_email_address_ = new_login_email_address; + auto query = G()->net_query_creator().create(telegram_api::account_sendVerifyEmailCode( + make_tl_object(), std::move(new_login_email_address))); + send_with_promise(std::move(query), + PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { + auto r_result = fetch_result(std::move(r_query)); + if (r_result.is_error()) { + return promise.set_error(r_result.move_as_error()); + } + return promise.set_value(SentEmailCode(r_result.move_as_ok())); + })); +} + +void PasswordManager::resend_login_email_address_code(Promise promise) { + if (last_set_login_email_address_.empty()) { + return promise.set_error(Status::Error(400, "No login email address code was sent")); + } + set_login_email_address(last_set_login_email_address_, std::move(promise)); +} + +void PasswordManager::check_login_email_address_code(EmailVerification &&code, Promise promise) { + if (last_set_login_email_address_.empty()) { + return promise.set_error(Status::Error(400, "No login email address code was sent")); + } + if (code.is_empty()) { + return promise.set_error(Status::Error(400, "Verification code must be non-empty")); + } + auto query = G()->net_query_creator().create(telegram_api::account_verifyEmail( + make_tl_object(), code.get_input_email_verification())); + send_with_promise(std::move(query), + PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { + auto r_result = fetch_result(std::move(r_query)); + if (r_result.is_error()) { + return promise.set_error(r_result.move_as_error()); + } + return promise.set_value(Unit()); + })); +} + void PasswordManager::set_recovery_email_address(string password, string new_recovery_email_address, Promise promise) { UpdateSettings update_settings; @@ -420,28 +460,21 @@ void PasswordManager::resend_recovery_email_address_code(Promise promise) })); } -void PasswordManager::send_email_address_verification_code( - string email, Promise> promise) { +void PasswordManager::send_email_address_verification_code(string email, Promise promise) { last_verified_email_address_ = email; - auto query = G()->net_query_creator().create(telegram_api::account_sendVerifyEmailCode(std::move(email))); - send_with_promise( - std::move(query), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { - auto r_result = fetch_result(std::move(r_query)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - auto result = r_result.move_as_ok(); - if (result->length_ < 0 || result->length_ >= 100) { - LOG(ERROR) << "Receive wrong code length " << result->length_; - result->length_ = 0; - } - return promise.set_value( - make_tl_object(result->email_pattern_, result->length_)); - })); + auto query = G()->net_query_creator().create(telegram_api::account_sendVerifyEmailCode( + make_tl_object(), std::move(email))); + send_with_promise(std::move(query), + PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { + auto r_result = fetch_result(std::move(r_query)); + if (r_result.is_error()) { + return promise.set_error(r_result.move_as_error()); + } + return promise.set_value(SentEmailCode(r_result.move_as_ok())); + })); } -void PasswordManager::resend_email_address_verification_code( - Promise> promise) { +void PasswordManager::resend_email_address_verification_code(Promise promise) { if (last_verified_email_address_.empty()) { return promise.set_error(Status::Error(400, "No email address verification was sent")); } @@ -452,8 +485,9 @@ void PasswordManager::check_email_address_verification_code(string code, Promise if (last_verified_email_address_.empty()) { return promise.set_error(Status::Error(400, "No email address verification was sent")); } - auto query = - G()->net_query_creator().create(telegram_api::account_verifyEmail(last_verified_email_address_, std::move(code))); + auto verification_code = make_tl_object(std::move(code)); + auto query = G()->net_query_creator().create(telegram_api::account_verifyEmail( + make_tl_object(), std::move(verification_code))); send_with_promise(std::move(query), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); @@ -464,19 +498,17 @@ void PasswordManager::check_email_address_verification_code(string code, Promise })); } -void PasswordManager::request_password_recovery( - Promise> promise) { +void PasswordManager::request_password_recovery(Promise promise) { // is called only after authorization - send_with_promise( - G()->net_query_creator().create(telegram_api::auth_requestPasswordRecovery()), - PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { - auto r_result = fetch_result(std::move(r_query)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - auto result = r_result.move_as_ok(); - return promise.set_value(make_tl_object(result->email_pattern_, 0)); - })); + send_with_promise(G()->net_query_creator().create(telegram_api::auth_requestPasswordRecovery()), + PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { + auto r_result = fetch_result(std::move(r_query)); + if (r_result.is_error()) { + return promise.set_error(r_result.move_as_error()); + } + auto result = r_result.move_as_ok(); + return promise.set_value(SentEmailCode(std::move(result->email_pattern_), 0)); + })); } void PasswordManager::check_password_recovery_code(string code, Promise promise) { @@ -793,8 +825,8 @@ void PasswordManager::do_get_state(Promise promise) { state.has_password = false; send_closure(actor_id, &PasswordManager::drop_cached_secret); } - state.unconfirmed_recovery_email_address_pattern = std::move(password->email_unconfirmed_pattern_); - state.code_length = code_length; + state.unconfirmed_recovery_email_code = {std::move(password->email_unconfirmed_pattern_), code_length}; + state.login_email_pattern = std::move(password->login_email_pattern_); if (password->flags_ & telegram_api::account_password::PENDING_RESET_DATE_MASK) { state.pending_reset_date = td::max(password->pending_reset_date_, 0); diff --git a/td/telegram/PasswordManager.h b/td/telegram/PasswordManager.h index dd5eb1546..3698dca0f 100644 --- a/td/telegram/PasswordManager.h +++ b/td/telegram/PasswordManager.h @@ -6,9 +6,11 @@ // #pragma once +#include "td/telegram/EmailVerification.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/NewPasswordState.h" #include "td/telegram/SecureStorage.h" +#include "td/telegram/SentEmailCode.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -69,18 +71,21 @@ class PasswordManager final : public NetQueryCallback { void get_state(Promise promise); void set_password(string current_password, string new_password, string new_hint, bool set_recovery_email_address, string recovery_email_address, Promise promise); + + void set_login_email_address(string new_login_email_address, Promise promise); + void resend_login_email_address_code(Promise promise); + void check_login_email_address_code(EmailVerification &&code, Promise promise); + void set_recovery_email_address(string password, string new_recovery_email_address, Promise promise); void get_recovery_email_address(string password, Promise> promise); void check_recovery_email_address_code(string code, Promise promise); void resend_recovery_email_address_code(Promise promise); - void send_email_address_verification_code( - string email, Promise> promise); - void resend_email_address_verification_code( - Promise> promise); + void send_email_address_verification_code(string email, Promise promise); + void resend_email_address_verification_code(Promise promise); void check_email_address_verification_code(string code, Promise promise); - void request_password_recovery(Promise> promise); + void request_password_recovery(Promise promise); void check_password_recovery_code(string code, Promise promise); void recover_password(string code, string new_password, string new_hint, Promise promise); @@ -106,8 +111,8 @@ class PasswordManager final : public NetQueryCallback { string password_hint; bool has_recovery_email_address = false; bool has_secure_values = false; - string unconfirmed_recovery_email_address_pattern; - int32 code_length = 0; + SentEmailCode unconfirmed_recovery_email_code; + string login_email_pattern; int32 pending_reset_date = 0; string current_client_salt; @@ -120,13 +125,10 @@ class PasswordManager final : public NetQueryCallback { NewPasswordState new_state; State get_password_state_object() const { - td_api::object_ptr code_info; - if (!unconfirmed_recovery_email_address_pattern.empty()) { - code_info = td_api::make_object( - unconfirmed_recovery_email_address_pattern, code_length); - } - return td_api::make_object(has_password, password_hint, has_recovery_email_address, - has_secure_values, std::move(code_info), pending_reset_date); + return td_api::make_object( + has_password, password_hint, has_recovery_email_address, has_secure_values, + unconfirmed_recovery_email_code.get_email_address_authentication_code_info_object(), login_email_pattern, + pending_reset_date); } }; @@ -159,6 +161,7 @@ class PasswordManager final : public NetQueryCallback { TempPasswordState temp_password_state_; Promise create_temp_password_promise_; + string last_set_login_email_address_; string last_verified_email_address_; int32 last_code_length_ = 0; diff --git a/td/telegram/PhoneNumberManager.cpp b/td/telegram/PhoneNumberManager.cpp index 835ba1911..8ef10aca1 100644 --- a/td/telegram/PhoneNumberManager.cpp +++ b/td/telegram/PhoneNumberManager.cpp @@ -45,7 +45,7 @@ void PhoneNumberManager::send_new_send_code_query(uint64 query_id, const telegra void PhoneNumberManager::set_phone_number(uint64 query_id, string phone_number, Settings settings) { if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(400, "Phone number can't be empty")); + return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty")); } switch (type_) { @@ -64,10 +64,10 @@ void PhoneNumberManager::set_phone_number(uint64 query_id, string phone_number, void PhoneNumberManager::set_phone_number_and_hash(uint64 query_id, string hash, string phone_number, Settings settings) { if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(400, "Phone number can't be empty")); + return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty")); } if (hash.empty()) { - return on_query_error(query_id, Status::Error(400, "Hash can't be empty")); + return on_query_error(query_id, Status::Error(400, "Hash must be non-empty")); } switch (type_) { @@ -213,6 +213,14 @@ void PhoneNumberManager::on_send_code_result(NetQueryPtr &result) { LOG(INFO) << "Receive " << to_string(sent_code); + switch (sent_code->type_->get_id()) { + case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID: + case telegram_api::auth_sentCodeTypeEmailCode::ID: + return on_query_error(Status::Error(500, "Receive incorrect response")); + default: + break; + } + send_code_helper_.on_sent_code(std::move(sent_code)); state_ = State::WaitCode; diff --git a/td/telegram/Premium.cpp b/td/telegram/Premium.cpp index b43df2ccf..170aaefec 100644 --- a/td/telegram/Premium.cpp +++ b/td/telegram/Premium.cpp @@ -45,7 +45,7 @@ static td_api::object_ptr get_premium_feature_object(Sli if (premium_feature == "no_ads") { return td_api::make_object(); } - if (premium_feature == "unique_reactions") { + if (premium_feature == "unique_reactions" || premium_feature == "infinite_reactions") { return td_api::make_object(); } if (premium_feature == "premium_stickers") { @@ -60,6 +60,9 @@ static td_api::object_ptr get_premium_feature_object(Sli if (premium_feature == "profile_badge") { return td_api::make_object(); } + if (premium_feature == "emoji_status") { + return td_api::make_object(); + } if (premium_feature == "animated_userpics") { return td_api::make_object(); } @@ -130,14 +133,6 @@ class GetPremiumPromoQuery final : public Td::ResultHandler { return on_error(Status::Error(500, "Receive wrong number of videos")); } - if (promo->monthly_amount_ < 0 || !check_currency_amount(promo->monthly_amount_)) { - return on_error(Status::Error(500, "Receive invalid monthly amount")); - } - - if (promo->currency_.size() != 3) { - return on_error(Status::Error(500, "Receive invalid currency")); - } - vector> animations; for (size_t i = 0; i < promo->video_sections_.size(); i++) { auto feature = get_premium_feature_object(promo->video_sections_[i]); @@ -164,8 +159,9 @@ class GetPremiumPromoQuery final : public Td::ResultHandler { std::move(animation_object))); } + auto period_options = get_premium_gift_options(std::move(promo->period_options_)); promise_.set_value(td_api::make_object(get_formatted_text_object(state, true, 0), - std::move(promo->currency_), promo->monthly_amount_, + get_premium_payment_options_object(period_options), std::move(animations))); } @@ -349,7 +345,7 @@ static string get_premium_source(const td_api::PremiumFeature *feature) { case td_api::premiumFeatureDisabledAds::ID: return "no_ads"; case td_api::premiumFeatureUniqueReactions::ID: - return "unique_reactions"; + return "infinite_reactions"; case td_api::premiumFeatureUniqueStickers::ID: return "premium_stickers"; case td_api::premiumFeatureCustomEmoji::ID: @@ -358,6 +354,8 @@ static string get_premium_source(const td_api::PremiumFeature *feature) { return "advanced_chat_management"; case td_api::premiumFeatureProfileBadge::ID: return "profile_badge"; + case td_api::premiumFeatureEmojiStatus::ID: + return "emoji_status"; case td_api::premiumFeatureAnimatedProfilePhoto::ID: return "animated_userpics"; case td_api::premiumFeatureAppIcons::ID: @@ -450,12 +448,12 @@ void get_premium_limit(const td_api::object_ptr &limit void get_premium_features(Td *td, const td_api::object_ptr &source, Promise> &&promise) { - auto premium_features = - full_split(G()->get_option_string( - "premium_features", - "double_limits,more_upload,faster_download,voice_to_text,no_ads,unique_reactions,premium_stickers," - "animated_emoji,advanced_chat_management,profile_badge,animated_userpics,app_icons"), - ','); + auto premium_features = full_split( + G()->get_option_string( + "premium_features", + "double_limits,more_upload,faster_download,voice_to_text,no_ads,infinite_reactions,premium_stickers," + "animated_emoji,advanced_chat_management,profile_badge,emoji_status,animated_userpics,app_icons"), + ','); vector> features; for (const auto &premium_feature : premium_features) { auto feature = get_premium_feature_object(premium_feature); diff --git a/td/telegram/PremiumGiftOption.cpp b/td/telegram/PremiumGiftOption.cpp index 733ca57b9..aa99f944c 100644 --- a/td/telegram/PremiumGiftOption.cpp +++ b/td/telegram/PremiumGiftOption.cpp @@ -9,9 +9,11 @@ #include "td/telegram/LinkManager.h" #include "td/telegram/Payments.h" +#include "td/utils/algorithm.h" #include "td/utils/common.h" #include "td/utils/logging.h" +#include #include namespace td { @@ -22,17 +24,33 @@ PremiumGiftOption::PremiumGiftOption(telegram_api::object_ptramount_) , bot_url_(std::move(option->bot_url_)) , store_product_(std::move(option->store_product_)) { +} + +PremiumGiftOption::PremiumGiftOption(telegram_api::object_ptr &&option) + : months_(option->months_) + , currency_(std::move(option->currency_)) + , amount_(option->amount_) + , bot_url_(std::move(option->bot_url_)) + , store_product_(std::move(option->store_product_)) { +} + +bool PremiumGiftOption::is_valid() const { if (amount_ <= 0 || !check_currency_amount(amount_)) { - LOG(ERROR) << "Receive invalid premium gift option amount " << amount_; - amount_ = static_cast(1) << 40; + LOG(ERROR) << "Receive invalid premium payment option amount " << amount_; + return false; } + if (currency_.size() != 3) { + LOG(ERROR) << "Receive invalid premium payment option currency " << currency_; + return false; + } + return true; } double PremiumGiftOption::get_monthly_price() const { return static_cast(amount_) / static_cast(months_); } -td_api::object_ptr PremiumGiftOption::get_premium_gift_option_object( +td_api::object_ptr PremiumGiftOption::get_premium_payment_option_object( const PremiumGiftOption &base_option) const { auto link_type = LinkManager::parse_internal_link(bot_url_, true); int32 discount_percentage = 0; @@ -42,7 +60,7 @@ td_api::object_ptr PremiumGiftOption::get_premium_gif discount_percentage = static_cast(100 * (1.0 - relative_price)); } } - return td_api::make_object( + return td_api::make_object( currency_, amount_, discount_percentage, months_, store_product_, link_type == nullptr ? nullptr : link_type->get_internal_link_type_object()); } @@ -61,4 +79,31 @@ bool operator!=(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs) { return !(lhs == rhs); } +vector get_premium_gift_options( + vector> &&options) { + auto premium_gift_options = transform( + std::move(options), [](auto &&premium_gift_option) { return PremiumGiftOption(std::move(premium_gift_option)); }); + td::remove_if(premium_gift_options, [](const auto &premium_gift_option) { return !premium_gift_option.is_valid(); }); + return premium_gift_options; +} + +vector get_premium_gift_options( + vector> &&options) { + auto premium_gift_options = transform( + std::move(options), [](auto &&premium_gift_option) { return PremiumGiftOption(std::move(premium_gift_option)); }); + td::remove_if(premium_gift_options, [](const auto &premium_gift_option) { return !premium_gift_option.is_valid(); }); + return premium_gift_options; +} + +vector> get_premium_payment_options_object( + const vector &options) { + if (options.empty()) { + return {}; + } + auto base_premium_option_it = std::min_element(options.begin(), options.end()); + return transform(options, [&base_premium_option_it](const auto &option) { + return option.get_premium_payment_option_object(*base_premium_option_it); + }); +} + } // namespace td diff --git a/td/telegram/PremiumGiftOption.h b/td/telegram/PremiumGiftOption.h index cb235ee76..09b8be7d1 100644 --- a/td/telegram/PremiumGiftOption.h +++ b/td/telegram/PremiumGiftOption.h @@ -29,10 +29,13 @@ class PremiumGiftOption { public: PremiumGiftOption() = default; explicit PremiumGiftOption(telegram_api::object_ptr &&option); + explicit PremiumGiftOption(telegram_api::object_ptr &&option); - td_api::object_ptr get_premium_gift_option_object( + td_api::object_ptr get_premium_payment_option_object( const PremiumGiftOption &base_option) const; + bool is_valid() const; + template void store(StorerT &storer) const; @@ -43,4 +46,13 @@ class PremiumGiftOption { bool operator==(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs); bool operator!=(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs); +vector get_premium_gift_options( + vector> &&options); + +vector get_premium_gift_options( + vector> &&options); + +vector> get_premium_payment_options_object( + const vector &options); + } // namespace td diff --git a/td/telegram/ReplyMarkup.cpp b/td/telegram/ReplyMarkup.cpp index a0e42ac33..f3771a20f 100644 --- a/td/telegram/ReplyMarkup.cpp +++ b/td/telegram/ReplyMarkup.cpp @@ -500,7 +500,7 @@ static Result get_inline_keyboard_button(tl_object_ptrtext_); if (button->type_ == nullptr) { - return Status::Error(400, "Inline keyboard button type can't be empty"); + return Status::Error(400, "Inline keyboard button type must be non-empty"); } int32 button_type_id = button->type_->get_id(); diff --git a/td/telegram/SendCodeHelper.cpp b/td/telegram/SendCodeHelper.cpp index 3a2d69f03..f073f9e7d 100644 --- a/td/telegram/SendCodeHelper.cpp +++ b/td/telegram/SendCodeHelper.cpp @@ -12,10 +12,15 @@ namespace td { void SendCodeHelper::on_sent_code(telegram_api::object_ptr sent_code) { - phone_code_hash_ = sent_code->phone_code_hash_; - sent_code_info_ = get_authentication_code_info(std::move(sent_code->type_)); + phone_code_hash_ = std::move(sent_code->phone_code_hash_); + sent_code_info_ = get_sent_authentication_code_info(std::move(sent_code->type_)); next_code_info_ = get_authentication_code_info(std::move(sent_code->next_type_)); - next_code_timestamp_ = Timestamp::in((sent_code->flags_ & SENT_CODE_FLAG_HAS_TIMEOUT) != 0 ? sent_code->timeout_ : 0); + next_code_timestamp_ = + Time::now() + ((sent_code->flags_ & SENT_CODE_FLAG_HAS_TIMEOUT) != 0 ? sent_code->timeout_ : 0); +} + +void SendCodeHelper::on_phone_code_hash(string &&phone_code_hash) { + phone_code_hash_ = std::move(phone_code_hash); } td_api::object_ptr SendCodeHelper::get_authorization_state_wait_code() const { @@ -26,7 +31,7 @@ td_api::object_ptr SendCodeHelper::get_authentic return make_tl_object( phone_number_, get_authentication_code_type_object(sent_code_info_), get_authentication_code_type_object(next_code_info_), - max(static_cast(next_code_timestamp_.in() + 1 - 1e-9), 0)); + max(static_cast(next_code_timestamp_ - Time::now() + 1 - 1e-9), 0)); } Result SendCodeHelper::resend_code() const { @@ -76,6 +81,10 @@ telegram_api::auth_sendCode SendCodeHelper::send_code(string phone_number, const return telegram_api::auth_sendCode(phone_number_, api_id, api_hash, get_input_code_settings(settings)); } +telegram_api::account_sendVerifyEmailCode SendCodeHelper::send_verify_email_code(const string &email_address) { + return telegram_api::account_sendVerifyEmailCode(get_email_verify_purpose_login_setup(), email_address); +} + telegram_api::account_sendChangePhoneCode SendCodeHelper::send_change_phone_code(Slice phone_number, const Settings &settings) { phone_number_ = phone_number.str(); @@ -116,7 +125,7 @@ SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_i } } -SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_info( +SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_sent_authentication_code_info( tl_object_ptr &&sent_code_type_ptr) { CHECK(sent_code_type_ptr != nullptr); switch (sent_code_type_ptr->get_id()) { @@ -141,6 +150,8 @@ SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_i return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::MissedCall, code_type->length_, std::move(code_type->prefix_)}; } + case telegram_api::auth_sentCodeTypeEmailCode::ID: + case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID: default: UNREACHABLE(); return AuthenticationCodeInfo(); @@ -169,4 +180,9 @@ td_api::object_ptr SendCodeHelper::get_authentic } } +telegram_api::object_ptr +SendCodeHelper::get_email_verify_purpose_login_setup() const { + return telegram_api::make_object(phone_number_, phone_code_hash_); +} + } // namespace td diff --git a/td/telegram/SendCodeHelper.h b/td/telegram/SendCodeHelper.h index f37fb8d8c..d78464594 100644 --- a/td/telegram/SendCodeHelper.h +++ b/td/telegram/SendCodeHelper.h @@ -20,6 +20,8 @@ class SendCodeHelper { public: void on_sent_code(telegram_api::object_ptr sent_code); + void on_phone_code_hash(string &&phone_code_hash); + td_api::object_ptr get_authorization_state_wait_code() const; td_api::object_ptr get_authentication_code_info_object() const; @@ -31,6 +33,8 @@ class SendCodeHelper { telegram_api::auth_sendCode send_code(string phone_number, const Settings &settings, int32 api_id, const string &api_hash); + telegram_api::account_sendVerifyEmailCode send_verify_email_code(const string &email_address); + telegram_api::account_sendChangePhoneCode send_change_phone_code(Slice phone_number, const Settings &settings); telegram_api::account_sendVerifyPhoneCode send_verify_phone_code(Slice phone_number, const Settings &settings); @@ -38,6 +42,8 @@ class SendCodeHelper { telegram_api::account_sendConfirmPhoneCode send_confirm_phone_code(const string &hash, Slice phone_number, const Settings &settings); + telegram_api::object_ptr get_email_verify_purpose_login_setup() const; + Slice phone_number() const { return phone_number_; } @@ -77,11 +83,11 @@ class SendCodeHelper { SendCodeHelper::AuthenticationCodeInfo sent_code_info_; SendCodeHelper::AuthenticationCodeInfo next_code_info_; - Timestamp next_code_timestamp_; + double next_code_timestamp_ = 0.0; static AuthenticationCodeInfo get_authentication_code_info( tl_object_ptr &&code_type_ptr); - static AuthenticationCodeInfo get_authentication_code_info( + static AuthenticationCodeInfo get_sent_authentication_code_info( tl_object_ptr &&sent_code_type_ptr); static td_api::object_ptr get_authentication_code_type_object( diff --git a/td/telegram/SendCodeHelper.hpp b/td/telegram/SendCodeHelper.hpp index 673f20b43..1fab66ac5 100644 --- a/td/telegram/SendCodeHelper.hpp +++ b/td/telegram/SendCodeHelper.hpp @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/SendCodeHelper.h" #include "td/utils/tl_helpers.h" @@ -36,7 +37,7 @@ void SendCodeHelper::store(StorerT &storer) const { store(phone_code_hash_, storer); store(sent_code_info_, storer); store(next_code_info_, storer); - store(next_code_timestamp_, storer); + store_time(next_code_timestamp_, storer); } template @@ -48,7 +49,7 @@ void SendCodeHelper::parse(ParserT &parser) { parse(phone_code_hash_, parser); parse(sent_code_info_, parser); parse(next_code_info_, parser); - parse(next_code_timestamp_, parser); + parse_time(next_code_timestamp_, parser); } } // namespace td diff --git a/td/telegram/SentEmailCode.cpp b/td/telegram/SentEmailCode.cpp new file mode 100644 index 000000000..04a0e883a --- /dev/null +++ b/td/telegram/SentEmailCode.cpp @@ -0,0 +1,27 @@ +// +// 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/SentEmailCode.h" + +namespace td { + +SentEmailCode::SentEmailCode(telegram_api::object_ptr &&email_code) + : email_address_pattern_(std::move(email_code->email_pattern_)), code_length_(email_code->length_) { + if (code_length_ < 0 || code_length_ >= 100) { + LOG(ERROR) << "Receive wrong email code length " << code_length_; + code_length_ = 0; + } +} + +td_api::object_ptr +SentEmailCode::get_email_address_authentication_code_info_object() const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(email_address_pattern_, code_length_); +} + +} // namespace td diff --git a/td/telegram/SentEmailCode.h b/td/telegram/SentEmailCode.h new file mode 100644 index 000000000..564bbff88 --- /dev/null +++ b/td/telegram/SentEmailCode.h @@ -0,0 +1,50 @@ +// +// 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/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class SentEmailCode { + string email_address_pattern_; + int32 code_length_ = 0; + + public: + SentEmailCode() = default; + + SentEmailCode(string &&email_address_pattern, int32 code_length) + : email_address_pattern_(std::move(email_address_pattern)), code_length_(code_length) { + } + + explicit SentEmailCode(telegram_api::object_ptr &&email_code); + + td_api::object_ptr get_email_address_authentication_code_info_object() + const; + + bool is_empty() const { + return email_address_pattern_.empty(); + } + + template + void store(StorerT &storer) const { + td::store(email_address_pattern_, storer); + td::store(code_length_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(email_address_pattern_, parser); + td::parse(code_length_, parser); + } +}; + +} // namespace td diff --git a/td/telegram/SpecialStickerSetType.cpp b/td/telegram/SpecialStickerSetType.cpp index 4b8c9d413..106a8079c 100644 --- a/td/telegram/SpecialStickerSetType.cpp +++ b/td/telegram/SpecialStickerSetType.cpp @@ -29,6 +29,14 @@ SpecialStickerSetType SpecialStickerSetType::premium_gifts() { return SpecialStickerSetType("premium_gifts_sticker_set"); } +SpecialStickerSetType SpecialStickerSetType::generic_animations() { + return SpecialStickerSetType("generic_animations_sticker_set"); +} + +SpecialStickerSetType SpecialStickerSetType::default_statuses() { + return SpecialStickerSetType("default_statuses_sticker_set"); +} + SpecialStickerSetType::SpecialStickerSetType( const telegram_api::object_ptr &input_sticker_set) { CHECK(input_sticker_set != nullptr); @@ -45,6 +53,12 @@ SpecialStickerSetType::SpecialStickerSetType( case telegram_api::inputStickerSetPremiumGifts::ID: *this = premium_gifts(); break; + case telegram_api::inputStickerSetEmojiGenericAnimations::ID: + *this = generic_animations(); + break; + case telegram_api::inputStickerSetEmojiDefaultStatuses::ID: + *this = default_statuses(); + break; default: UNREACHABLE(); break; @@ -69,6 +83,12 @@ telegram_api::object_ptr SpecialStickerSetType::g if (*this == premium_gifts()) { return telegram_api::make_object(); } + if (*this == generic_animations()) { + return telegram_api::make_object(); + } + if (*this == default_statuses()) { + return telegram_api::make_object(); + } auto emoji = get_dice_emoji(); if (!emoji.empty()) { return telegram_api::make_object(emoji); diff --git a/td/telegram/SpecialStickerSetType.h b/td/telegram/SpecialStickerSetType.h index 3bd34a93e..7f80f7a71 100644 --- a/td/telegram/SpecialStickerSetType.h +++ b/td/telegram/SpecialStickerSetType.h @@ -31,6 +31,10 @@ class SpecialStickerSetType { static SpecialStickerSetType premium_gifts(); + static SpecialStickerSetType generic_animations(); + + static SpecialStickerSetType default_statuses(); + string get_dice_emoji() const; bool is_empty() const { diff --git a/td/telegram/StickerType.cpp b/td/telegram/StickerType.cpp index f635242b1..4b17753ba 100644 --- a/td/telegram/StickerType.cpp +++ b/td/telegram/StickerType.cpp @@ -8,6 +8,16 @@ namespace td { +StickerType get_sticker_type(bool is_mask, bool is_custom_emoji) { + if (is_custom_emoji) { + return StickerType::CustomEmoji; + } + if (is_mask) { + return StickerType::Mask; + } + return StickerType::Regular; +} + StickerType get_sticker_type(const td_api::object_ptr &type) { if (type == nullptr) { return StickerType::Regular; diff --git a/td/telegram/StickerType.h b/td/telegram/StickerType.h index 46fa08796..d4e2f18d1 100644 --- a/td/telegram/StickerType.h +++ b/td/telegram/StickerType.h @@ -18,6 +18,8 @@ enum class StickerType : int32 { Regular, Mask, CustomEmoji }; static constexpr int32 MAX_STICKER_TYPE = 3; +StickerType get_sticker_type(bool is_mask, bool is_custom_emoji); + StickerType get_sticker_type(const td_api::object_ptr &type); td_api::object_ptr get_sticker_type_object(StickerType sticker_type); diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index 750459bc8..fc245845b 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -8,7 +8,6 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/AvailableReaction.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" @@ -22,6 +21,7 @@ #include "td/telegram/LanguagePackManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/MessageReaction.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" @@ -93,6 +93,82 @@ class GetAvailableReactionsQuery final : public Td::ResultHandler { } }; +class GetRecentReactionsQuery final : public Td::ResultHandler { + public: + void send(int32 limit, int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::messages_getRecentReactions(limit, hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(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 GetRecentReactionsQuery: " << to_string(ptr); + td_->stickers_manager_->on_get_recent_reactions(std::move(ptr)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetRecentReactionsQuery: " << status; + td_->stickers_manager_->on_get_recent_reactions(nullptr); + } +}; + +class GetTopReactionsQuery final : public Td::ResultHandler { + public: + void send(int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::messages_getTopReactions(50, hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(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 GetTopReactionsQuery: " << to_string(ptr); + td_->stickers_manager_->on_get_top_reactions(std::move(ptr)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetTopReactionsQuery: " << status; + td_->stickers_manager_->on_get_top_reactions(nullptr); + } +}; + +class ClearRecentReactionsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ClearRecentReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::messages_clearRecentReactions())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->stickers_manager_->reload_recent_reactions(); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (!G()->is_expected_error(status)) { + LOG(ERROR) << "Receive error for clear recent reactions: " << status; + } + td_->stickers_manager_->reload_recent_reactions(); + promise_.set_error(std::move(status)); + } +}; + class GetAllStickersQuery final : public Td::ResultHandler { StickerType sticker_type_; @@ -918,9 +994,8 @@ class ReadFeaturedStickerSetsQuery final : public Td::ResultHandler { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for ReadFeaturedStickerSetsQuery: " << status; } - for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { - td_->stickers_manager_->reload_featured_sticker_sets(static_cast(type), true); - } + td_->stickers_manager_->reload_featured_sticker_sets(StickerType::Regular, true); + td_->stickers_manager_->reload_featured_sticker_sets(StickerType::CustomEmoji, true); } }; @@ -1417,7 +1492,6 @@ void StickersManager::init() { is_inited_ = true; { - // add animated emoji sticker set auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji()); if (G()->is_test_dc()) { init_special_sticker_set(sticker_set, 1258816259751954, 4879754868529595811, "emojies"); @@ -1427,13 +1501,21 @@ void StickersManager::init() { load_special_sticker_set_info_from_binlog(sticker_set); } if (!G()->is_test_dc()) { - // add animated emoji click sticker set auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click()); load_special_sticker_set_info_from_binlog(sticker_set); } - // add premium gifts sticker set - auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts()); - load_special_sticker_set_info_from_binlog(sticker_set); + { + auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts()); + load_special_sticker_set_info_from_binlog(sticker_set); + } + { + auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::generic_animations()); + load_special_sticker_set_info_from_binlog(sticker_set); + } + { + auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses()); + load_special_sticker_set_info_from_binlog(sticker_set); + } dice_emojis_str_ = td_->option_manager_->get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01⚽\x01⚽️\x01🎰\x01🎳"); @@ -1444,7 +1526,7 @@ void StickersManager::init() { } send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object()); - send_closure_later(actor_id(this), &StickersManager::load_reactions); + load_active_reactions(); on_update_dice_success_values(); on_update_dice_emojis(); @@ -1476,15 +1558,95 @@ void StickersManager::init() { td_->option_manager_->set_option_empty("animated_emoji_sticker_set_name"); // legacy } +td_api::object_ptr StickersManager::get_emoji_reaction_object(const string &emoji) { + load_reactions(); + for (auto &reaction : reactions_.reactions_) { + if (reaction.reaction_ == emoji) { + return td_api::make_object( + 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 nullptr; +} + +vector StickersManager::get_recent_reactions() { + load_recent_reactions(); + return recent_reactions_.reactions_; +} + +vector StickersManager::get_top_reactions() { + load_top_reactions(); + return top_reactions_.reactions_; +} + +void StickersManager::add_recent_reaction(const string &reaction) { + load_recent_reactions(); + + auto &reactions = recent_reactions_.reactions_; + if (!reactions.empty() && reactions[0] == reaction) { + return; + } + + auto it = std::find(reactions.begin(), reactions.end(), reaction); + if (it == reactions.end()) { + if (static_cast(reactions.size()) == MAX_RECENT_REACTIONS) { + reactions.back() = reaction; + } else { + reactions.push_back(reaction); + } + it = reactions.end() - 1; + } + std::rotate(reactions.begin(), it, it + 1); + + recent_reactions_.hash_ = get_reactions_hash(reactions); +} + +void StickersManager::clear_recent_reactions(Promise &&promise) { + load_recent_reactions(); + + if (recent_reactions_.reactions_.empty()) { + return promise.set_value(Unit()); + } + + recent_reactions_.hash_ = 0; + recent_reactions_.reactions_.clear(); + + td_->create_handler(std::move(promise))->send(); +} + void StickersManager::reload_reactions() { if (G()->close_flag() || reactions_.are_being_reloaded_) { return; } CHECK(!td_->auth_manager_->is_bot()); reactions_.are_being_reloaded_ = true; + load_reactions(); // must be after are_being_reloaded_ is set to true to avoid recursion td_->create_handler()->send(reactions_.hash_); } +void StickersManager::reload_recent_reactions() { + if (G()->close_flag() || recent_reactions_.is_being_reloaded_) { + return; + } + CHECK(!td_->auth_manager_->is_bot()); + recent_reactions_.is_being_reloaded_ = true; + load_recent_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion + td_->create_handler()->send(MAX_RECENT_REACTIONS, recent_reactions_.hash_); +} + +void StickersManager::reload_top_reactions() { + if (G()->close_flag() || top_reactions_.is_being_reloaded_) { + return; + } + CHECK(!td_->auth_manager_->is_bot()); + top_reactions_.is_being_reloaded_ = true; + load_top_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion + td_->create_handler()->send(top_reactions_.hash_); +} + StickersManager::SpecialStickerSet &StickersManager::add_special_sticker_set(const SpecialStickerSetType &type) { CHECK(!type.is_empty()); auto &result_ptr = special_sticker_sets_[type]; @@ -1656,6 +1818,14 @@ void StickersManager::on_load_special_sticker_set(const SpecialStickerSetType &t try_update_premium_gift_messages(); return; } + if (type == SpecialStickerSetType::generic_animations()) { + set_promises(pending_get_generic_animations_queries_); + return; + } + if (type == SpecialStickerSetType::default_statuses()) { + set_promises(pending_get_default_statuses_queries_); + return; + } CHECK(special_sticker_set.id_.is_valid()); auto sticker_set = get_sticker_set(special_sticker_set.id_); @@ -1723,13 +1893,11 @@ StickerType StickersManager::get_sticker_type(FileId file_id) const { bool StickersManager::is_premium_custom_emoji(int64 custom_emoji_id, bool default_result) const { auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id); - const Sticker *s = get_sticker(sticker_id); - if (s == nullptr) { - if (sticker_id.is_valid()) { - LOG(ERROR) << "Failed to find custom emoji sticker " << sticker_id; - } + if (!sticker_id.is_valid()) { return default_result; } + const Sticker *s = get_sticker(sticker_id); + CHECK(s != nullptr); return s->is_premium_; } @@ -2739,6 +2907,8 @@ StickerSetId StickersManager::get_sticker_set_id(const tl_object_ptr StickersManager::get_update_reactions_object() const { - auto reactions = transform(reactions_.reactions_, [this](const Reaction &reaction) { - return td_api::make_object( - reaction.reaction_, reaction.title_, reaction.is_active_, reaction.is_premium_, - 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(std::move(reactions)); +td_api::object_ptr StickersManager::get_update_active_emoji_reactions_object() + const { + return td_api::make_object(vector(active_reactions_)); +} + +void StickersManager::save_active_reactions() { + LOG(INFO) << "Save active reactions"; + G()->td_db()->get_binlog_pmc()->set("active_reactions", log_event_store(active_reactions_).as_slice().str()); } void StickersManager::save_reactions() { @@ -3654,40 +3826,124 @@ void StickersManager::save_reactions() { G()->td_db()->get_binlog_pmc()->set("reactions", log_event_store(reactions_).as_slice().str()); } +void StickersManager::save_recent_reactions() { + LOG(INFO) << "Save recent reactions"; + G()->td_db()->get_binlog_pmc()->set("recent_reactions", log_event_store(recent_reactions_).as_slice().str()); +} + +void StickersManager::save_top_reactions() { + LOG(INFO) << "Save top reactions"; + G()->td_db()->get_binlog_pmc()->set("top_reactions", log_event_store(top_reactions_).as_slice().str()); +} + +void StickersManager::load_active_reactions() { + string active_reactions = G()->td_db()->get_binlog_pmc()->get("active_reactions"); + if (active_reactions.empty()) { + return reload_reactions(); + } + + auto status = log_event_parse(active_reactions_, active_reactions); + if (status.is_error()) { + LOG(ERROR) << "Can't load active reactions: " << status; + active_reactions_ = {}; + return reload_reactions(); + } + + LOG(INFO) << "Successfully loaded " << active_reactions_.size() << " active reactions"; + + td_->messages_manager_->set_active_reactions(vector(active_reactions_)); + + send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); +} + void StickersManager::load_reactions() { + if (are_reactions_loaded_from_database_) { + return; + } + are_reactions_loaded_from_database_ = true; + string reactions = G()->td_db()->get_binlog_pmc()->get("reactions"); if (reactions.empty()) { return reload_reactions(); } - auto status = log_event_parse(reactions_, reactions); + auto new_reactions = reactions_; + auto status = log_event_parse(new_reactions, reactions); if (status.is_error()) { LOG(ERROR) << "Can't load available reactions: " << status; - reactions_ = {}; return reload_reactions(); } - for (auto &reaction : reactions_.reactions_) { + for (auto &reaction : new_reactions.reactions_) { if (!reaction.is_valid()) { LOG(ERROR) << "Loaded invalid reaction"; - reactions_ = {}; return reload_reactions(); } } + reactions_ = std::move(new_reactions); LOG(INFO) << "Successfully loaded " << reactions_.reactions_.size() << " available reactions"; - send_closure(G()->td(), &Td::send_update, get_update_reactions_object()); - LOG(INFO) << "Successfully sent updateReactions"; update_active_reactions(); } +void StickersManager::load_recent_reactions() { + if (are_recent_reactions_loaded_from_database_) { + return; + } + are_recent_reactions_loaded_from_database_ = true; + + string recent_reactions = G()->td_db()->get_binlog_pmc()->get("recent_reactions"); + if (recent_reactions.empty()) { + return reload_recent_reactions(); + } + + auto status = log_event_parse(recent_reactions_, recent_reactions); + if (status.is_error()) { + LOG(ERROR) << "Can't load recent reactions: " << status; + recent_reactions_ = {}; + return reload_recent_reactions(); + } + + LOG(INFO) << "Successfully loaded " << recent_reactions_.reactions_.size() << " recent reactions"; +} + +void StickersManager::load_top_reactions() { + if (are_top_reactions_loaded_from_database_) { + return; + } + are_top_reactions_loaded_from_database_ = true; + + string top_reactions = G()->td_db()->get_binlog_pmc()->get("top_reactions"); + if (top_reactions.empty()) { + return reload_top_reactions(); + } + + auto status = log_event_parse(top_reactions_, top_reactions); + if (status.is_error()) { + LOG(ERROR) << "Can't load top reactions: " << status; + top_reactions_ = {}; + return reload_top_reactions(); + } + + LOG(INFO) << "Successfully loaded " << top_reactions_.reactions_.size() << " top reactions"; +} + void StickersManager::update_active_reactions() { - vector active_reactions; + vector active_reactions; for (auto &reaction : reactions_.reactions_) { if (reaction.is_active_) { - active_reactions.emplace_back(reaction.reaction_, reaction.is_premium_); + active_reactions.emplace_back(reaction.reaction_); } } + if (active_reactions == active_reactions_) { + return; + } + active_reactions_ = active_reactions; + + save_active_reactions(); + + send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); + td_->messages_manager_->set_active_reactions(std::move(active_reactions)); } @@ -3735,18 +3991,89 @@ void StickersManager::on_get_available_reactions( LOG(ERROR) << "Receive invalid reaction " << reaction.reaction_; continue; } + if (reaction.is_premium_) { + LOG(ERROR) << "Receive premium 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_recent_reactions(tl_object_ptr &&reactions_ptr) { + CHECK(recent_reactions_.is_being_reloaded_); + recent_reactions_.is_being_reloaded_ = false; + + if (reactions_ptr == nullptr) { + // failed to get recent reactions + return; + } + + int32 constructor_id = reactions_ptr->get_id(); + if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + + CHECK(constructor_id == telegram_api::messages_reactions::ID); + auto reactions = move_tl_object_as(reactions_ptr); + auto new_reactions = + transform(reactions->reactions_, [](const telegram_api::object_ptr &reaction) { + return get_message_reaction_string(reaction); + }); + if (new_reactions == recent_reactions_.reactions_ && recent_reactions_.hash_ == reactions->hash_) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + recent_reactions_.reactions_ = std::move(new_reactions); + recent_reactions_.hash_ = reactions->hash_; + + auto expected_hash = get_reactions_hash(recent_reactions_.reactions_); + if (recent_reactions_.hash_ != expected_hash) { + LOG(ERROR) << "Receive hash " << recent_reactions_.hash_ << " instead of " << expected_hash << " for reactions " + << recent_reactions_.reactions_; + } + + save_recent_reactions(); +} + +void StickersManager::on_get_top_reactions(tl_object_ptr &&reactions_ptr) { + CHECK(top_reactions_.is_being_reloaded_); + top_reactions_.is_being_reloaded_ = false; + + if (reactions_ptr == nullptr) { + // failed to get top reactions + return; + } + + int32 constructor_id = reactions_ptr->get_id(); + if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + + CHECK(constructor_id == telegram_api::messages_reactions::ID); + auto reactions = move_tl_object_as(reactions_ptr); + auto new_reactions = + transform(reactions->reactions_, [](const telegram_api::object_ptr &reaction) { + return get_message_reaction_string(reaction); + }); + if (new_reactions == top_reactions_.reactions_ && top_reactions_.hash_ == reactions->hash_) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + top_reactions_.reactions_ = std::move(new_reactions); + top_reactions_.hash_ = reactions->hash_; + + save_top_reactions(); +} + void StickersManager::on_get_installed_sticker_sets(StickerType sticker_type, tl_object_ptr &&stickers_ptr) { auto type = static_cast(sticker_type); @@ -5151,13 +5478,11 @@ void StickersManager::on_update_disable_animated_emojis() { } } -void StickersManager::on_update_sticker_sets() { - // TODO better support - for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { - archived_sticker_set_ids_[type].clear(); - total_archived_sticker_set_count_[type] = -1; - reload_installed_sticker_sets(static_cast(type), true); - } +void StickersManager::on_update_sticker_sets(StickerType sticker_type) { + auto type = static_cast(sticker_type); + archived_sticker_set_ids_[type].clear(); + total_archived_sticker_set_count_[type] = -1; + reload_installed_sticker_sets(sticker_type, true); } void StickersManager::try_update_animated_emoji_messages() { @@ -5324,10 +5649,13 @@ void StickersManager::register_emoji(const string &emoji, int64 custom_emoji_id, } auto &emoji_messages = *emoji_messages_ptr; if (emoji_messages.full_message_ids_.empty()) { - emoji_messages.sticker_id_ = get_custom_animated_emoji_sticker_id(custom_emoji_id); - if (!disable_animated_emojis_) { - get_custom_emoji_stickers({custom_emoji_id}, true, Promise>()); + if (!disable_animated_emojis_ && custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { + load_custom_emoji_sticker_from_database_force(custom_emoji_id); + if (custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) { + get_custom_emoji_stickers({custom_emoji_id}, false, Promise>()); + } } + emoji_messages.sticker_id_ = get_custom_animated_emoji_sticker_id(custom_emoji_id); } emoji_messages.full_message_ids_.insert(full_message_id); return; @@ -5437,6 +5765,107 @@ void StickersManager::get_all_animated_emojis(bool is_recursive, promise.set_value(td_api::make_object(std::move(emojis))); } +void StickersManager::get_custom_emoji_reaction_generic_animations( + bool is_recursive, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::generic_animations()); + auto sticker_set = get_sticker_set(special_sticker_set.id_); + if (sticker_set == nullptr || !sticker_set->was_loaded_) { + if (is_recursive) { + return promise.set_value(td_api::make_object()); + } + + pending_get_generic_animations_queries_.push_back(PromiseCreator::lambda( + [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &StickersManager::get_custom_emoji_reaction_generic_animations, true, + std::move(promise)); + } + })); + load_special_sticker_set(special_sticker_set); + return; + } + + auto files = transform(sticker_set->sticker_ids_, + [&](FileId sticker_id) { return td_->file_manager_->get_file_object(sticker_id); }); + promise.set_value(td_api::make_object(std::move(files))); +} + +void StickersManager::get_default_emoji_statuses(bool is_recursive, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses()); + auto sticker_set = get_sticker_set(special_sticker_set.id_); + if (sticker_set == nullptr || !sticker_set->was_loaded_) { + if (is_recursive) { + return promise.set_value(td_api::make_object()); + } + + pending_get_default_statuses_queries_.push_back(PromiseCreator::lambda( + [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &StickersManager::get_default_emoji_statuses, true, std::move(promise)); + } + })); + load_special_sticker_set(special_sticker_set); + return; + } + + vector> statuses; + for (auto sticker_id : sticker_set->sticker_ids_) { + auto custom_emoji_id = get_custom_emoji_id(sticker_id); + if (custom_emoji_id == 0) { + LOG(ERROR) << "Ignore wrong sticker " << sticker_id; + continue; + } + statuses.emplace_back(td_api::make_object(custom_emoji_id)); + if (statuses.size() >= 8) { + break; + } + } + promise.set_value(td_api::make_object(std::move(statuses))); +} + +bool StickersManager::is_default_emoji_status(int64 custom_emoji_id) { + auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses()); + auto sticker_set = get_sticker_set(special_sticker_set.id_); + if (sticker_set == nullptr || !sticker_set->was_loaded_) { + return false; + } + for (auto sticker_id : sticker_set->sticker_ids_) { + if (get_custom_emoji_id(sticker_id) == custom_emoji_id) { + return true; + } + } + return false; +} + +void StickersManager::load_custom_emoji_sticker_from_database_force(int64 custom_emoji_id) { + if (!G()->parameters().use_file_db) { + return; + } + + auto value = G()->td_db()->get_sqlite_sync_pmc()->get(get_custom_emoji_database_key(custom_emoji_id)); + if (value.empty()) { + LOG(INFO) << "Failed to load custom emoji " << custom_emoji_id << " from database"; + return; + } + + LOG(INFO) << "Synchronously loaded custom emoji " << custom_emoji_id << " of size " << value.size() + << " from database"; + CustomEmojiLogEvent log_event; + if (log_event_parse(log_event, value).is_error()) { + LOG(ERROR) << "Delete invalid custom emoji " << custom_emoji_id << " value from database"; + G()->td_db()->get_sqlite_sync_pmc()->erase(get_custom_emoji_database_key(custom_emoji_id)); + } +} + void StickersManager::load_custom_emoji_sticker_from_database(int64 custom_emoji_id, Promise &&promise) { CHECK(custom_emoji_id != 0); auto &queries = custom_emoji_load_queries_[custom_emoji_id]; @@ -5484,13 +5913,17 @@ td_api::object_ptr StickersManager::get_custom_emoji_stickers_ vector reload_document_ids; for (auto document_id : document_ids) { auto file_id = custom_emoji_to_sticker_id_.get(document_id); - auto sticker = get_sticker_object(file_id); - if (sticker != nullptr && sticker->type_->get_id() == td_api::stickerTypeCustomEmoji::ID) { + if (file_id.is_valid()) { auto s = get_sticker(file_id); + CHECK(s != nullptr); + CHECK(s->type_ == StickerType::CustomEmoji); if (s->emoji_receive_date_ < update_before_date && !s->is_being_reloaded_) { s->is_being_reloaded_ = true; reload_document_ids.push_back(document_id); } + + auto sticker = get_sticker_object(file_id); + CHECK(sticker != nullptr); stickers.push_back(std::move(sticker)); } } @@ -6364,6 +6797,7 @@ void StickersManager::on_get_featured_sticker_sets_failed(StickerType sticker_ty } void StickersManager::load_featured_sticker_sets(StickerType sticker_type, Promise &&promise) { + CHECK(sticker_type != StickerType::Mask); auto type = static_cast(sticker_type); if (td_->auth_manager_->is_bot()) { are_featured_sticker_sets_loaded_[type] = true; @@ -6667,6 +7101,40 @@ void StickersManager::on_update_sticker_sets_order(StickerType sticker_type, } } +// -1 - sticker set can't be moved to top, 0 - order wasn't changed, 1 - sticker set was moved to top +int StickersManager::move_installed_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id) { + LOG(INFO) << "Move " << sticker_set_id << " to top of " << sticker_type; + auto type = static_cast(sticker_type); + if (!are_installed_sticker_sets_loaded_[type]) { + return -1; + } + + vector ¤t_sticker_set_ids = installed_sticker_set_ids_[type]; + auto it = std::find(current_sticker_set_ids.begin(), current_sticker_set_ids.end(), sticker_set_id); + if (it == current_sticker_set_ids.end()) { + return -1; + } + if (sticker_set_id == current_sticker_set_ids[0]) { + CHECK(it == current_sticker_set_ids.begin()); + return 0; + } + + std::rotate(current_sticker_set_ids.begin(), it, it + 1); + + need_update_installed_sticker_sets_[type] = true; + return 1; +} + +void StickersManager::on_update_move_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id) { + int result = move_installed_sticker_set_to_top(sticker_type, sticker_set_id); + if (result < 0) { + return reload_installed_sticker_sets(sticker_type, true); + } + if (result > 0) { + send_update_installed_sticker_sets(); + } +} + void StickersManager::reorder_installed_sticker_sets(StickerType sticker_type, const vector &sticker_set_ids, Promise &&promise) { @@ -6682,6 +7150,48 @@ void StickersManager::reorder_installed_sticker_sets(StickerType sticker_type, promise.set_value(Unit()); } +void StickersManager::move_sticker_set_to_top_by_sticker_id(FileId sticker_id) { + LOG(INFO) << "Move to top sticker set of " << sticker_id; + const auto *s = get_sticker(sticker_id); + if (s == nullptr || !s->set_id_.is_valid()) { + return; + } + if (s->type_ == StickerType::CustomEmoji) { + // just in case + return; + } + if (move_installed_sticker_set_to_top(s->type_, s->set_id_) > 0) { + send_update_installed_sticker_sets(); + } +} + +void StickersManager::move_sticker_set_to_top_by_custom_emoji_ids(const vector &custom_emoji_ids) { + LOG(INFO) << "Move to top sticker set of " << custom_emoji_ids; + StickerSetId sticker_set_id; + for (auto custom_emoji_id : custom_emoji_ids) { + auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id); + if (!sticker_id.is_valid()) { + return; + } + const auto *s = get_sticker(sticker_id); + CHECK(s != nullptr); + CHECK(s->type_ == StickerType::CustomEmoji); + if (!s->set_id_.is_valid()) { + return; + } + if (s->set_id_ != sticker_set_id) { + if (sticker_set_id.is_valid()) { + return; + } + sticker_set_id = s->set_id_; + } + } + CHECK(sticker_set_id.is_valid()); + if (move_installed_sticker_set_to_top(StickerType::CustomEmoji, sticker_set_id) > 0) { + send_update_installed_sticker_sets(); + } +} + Result> StickersManager::prepare_input_sticker( td_api::inputSticker *sticker, StickerType sticker_type) { if (sticker == nullptr) { @@ -6822,7 +7332,7 @@ tl_object_ptr StickersManager::get_input_stic void StickersManager::get_suggested_sticker_set_name(string title, Promise &&promise) { title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH); if (title.empty()) { - return promise.set_error(Status::Error(400, "Sticker set title can't be empty")); + return promise.set_error(Status::Error(400, "Sticker set title must be non-empty")); } td_->create_handler(std::move(promise))->send(title); @@ -6879,12 +7389,12 @@ void StickersManager::create_new_sticker_set(UserId user_id, string title, strin title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH); if (title.empty()) { - return promise.set_error(Status::Error(400, "Sticker set title can't be empty")); + return promise.set_error(Status::Error(400, "Sticker set title must be non-empty")); } short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH); if (short_name.empty()) { - return promise.set_error(Status::Error(400, "Sticker set name can't be empty")); + return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } if (stickers.empty()) { @@ -7119,7 +7629,7 @@ void StickersManager::add_sticker_to_set(UserId user_id, string short_name, short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH)); if (short_name.empty()) { - return promise.set_error(Status::Error(400, "Sticker set name can't be empty")); + return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); @@ -7213,7 +7723,7 @@ void StickersManager::set_sticker_set_thumbnail(UserId user_id, string short_nam short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH)); if (short_name.empty()) { - return promise.set_error(Status::Error(400, "Sticker set name can't be empty")); + return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); @@ -8812,8 +9322,8 @@ void StickersManager::get_current_state(vector> &&promise); + void get_custom_emoji_reaction_generic_animations(bool is_recursive, + Promise> &&promise); + + void get_default_emoji_statuses(bool is_recursive, Promise> &&promise); + + bool is_default_emoji_status(int64 custom_emoji_id); + void get_custom_emoji_stickers(vector &&document_ids, bool use_database, Promise> &&promise); @@ -166,12 +173,30 @@ class StickersManager final : public Actor { void view_featured_sticker_sets(const vector &sticker_set_ids); + td_api::object_ptr get_emoji_reaction_object(const string &emoji); + + vector get_recent_reactions(); + + vector get_top_reactions(); + + void add_recent_reaction(const string &reaction); + + void clear_recent_reactions(Promise &&promise); + void reload_reactions(); + void reload_recent_reactions(); + + void reload_top_reactions(); + void reload_special_sticker_set_by_type(SpecialStickerSetType type, bool is_recursive = false); void on_get_available_reactions(tl_object_ptr &&available_reactions_ptr); + void on_get_recent_reactions(tl_object_ptr &&reactions_ptr); + + void on_get_top_reactions(tl_object_ptr &&reactions_ptr); + void on_get_installed_sticker_sets(StickerType sticker_type, tl_object_ptr &&stickers_ptr); @@ -207,10 +232,12 @@ class StickersManager final : public Actor { void on_update_emoji_sounds(); - void on_update_sticker_sets(); + void on_update_sticker_sets(StickerType sticker_type); void on_update_sticker_sets_order(StickerType sticker_type, const vector &sticker_set_ids); + void on_update_move_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id); + std::pair> get_archived_sticker_sets(StickerType sticker_type, StickerSetId offset_sticker_set_id, int32 limit, bool force, Promise &&promise); @@ -236,6 +263,10 @@ class StickersManager final : public Actor { void reorder_installed_sticker_sets(StickerType sticker_type, const vector &sticker_set_ids, Promise &&promise); + void move_sticker_set_to_top_by_sticker_id(FileId sticker_id); + + void move_sticker_set_to_top_by_custom_emoji_ids(const vector &custom_emoji_ids); + FileId upload_sticker_file(UserId user_id, tl_object_ptr &&sticker, Promise &&promise); void get_suggested_sticker_set_name(string title, Promise &&promise); @@ -380,6 +411,8 @@ class StickersManager final : public Actor { static constexpr int32 EMOJI_KEYWORDS_UPDATE_DELAY = 3600; static constexpr double MIN_ANIMATED_EMOJI_CLICK_DELAY = 0.2; + static constexpr int32 MAX_RECENT_REACTIONS = 100; // some reasonable value + class Sticker { public: StickerSetId set_id_; @@ -538,6 +571,18 @@ class StickersManager final : public Actor { void parse(ParserT &parser); }; + struct ReactionList { + int64 hash_ = 0; + bool is_being_reloaded_ = false; + vector reactions_; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + class CustomEmojiLogEvent; class StickerListLogEvent; class StickerSetListLogEvent; @@ -570,6 +615,8 @@ class StickersManager final : public Actor { static string get_custom_emoji_database_key(int64 custom_emoji_id); + void load_custom_emoji_sticker_from_database_force(int64 custom_emoji_id); + void load_custom_emoji_sticker_from_database(int64 custom_emoji_id, Promise &&promise); void on_load_custom_emoji_from_database(int64 custom_emoji_id, string value); @@ -597,6 +644,8 @@ class StickersManager final : public Actor { int apply_installed_sticker_sets_order(StickerType sticker_type, const vector &sticker_set_ids); + int move_installed_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id); + void on_update_sticker_set(StickerSet *sticker_set, bool is_installed, bool is_archived, bool is_changed, bool from_database = false); @@ -809,13 +858,25 @@ class StickersManager final : public Actor { void tear_down() final; + void save_active_reactions(); + void save_reactions(); + void save_recent_reactions(); + + void save_top_reactions(); + + void load_active_reactions(); + void load_reactions(); + void load_recent_reactions(); + + void load_top_reactions(); + void update_active_reactions(); - td_api::object_ptr get_update_reactions_object() const; + td_api::object_ptr get_update_active_emoji_reactions_object() const; SpecialStickerSet &add_special_sticker_set(const SpecialStickerSetType &type); @@ -968,6 +1029,8 @@ class StickersManager final : public Actor { vector> pending_get_animated_emoji_queries_; vector> pending_get_premium_gift_option_sticker_queries_; + vector> pending_get_generic_animations_queries_; + vector> pending_get_default_statuses_queries_; double next_click_animated_emoji_message_time_ = 0; double next_update_animated_emoji_clicked_time_ = 0; @@ -990,6 +1053,14 @@ class StickersManager final : public Actor { FlatHashMap>, FileIdHash> being_uploaded_files_; Reactions reactions_; + vector active_reactions_; + + ReactionList recent_reactions_; + ReactionList top_reactions_; + + bool are_reactions_loaded_from_database_ = false; + bool are_recent_reactions_loaded_from_database_ = false; + bool are_top_reactions_loaded_from_database_ = false; FlatHashMap> emoji_language_codes_; FlatHashMap emoji_language_code_versions_; diff --git a/td/telegram/StickersManager.hpp b/td/telegram/StickersManager.hpp index 683f8d6c3..ababf8bbe 100644 --- a/td/telegram/StickersManager.hpp +++ b/td/telegram/StickersManager.hpp @@ -113,13 +113,7 @@ FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) { } else { sticker->format_ = StickerFormat::Webp; } - if (is_emoji) { - sticker->type_ = StickerType::CustomEmoji; - } else if (is_mask) { - sticker->type_ = StickerType::Mask; - } else { - sticker->type_ = StickerType::Regular; - } + sticker->type_ = ::td::get_sticker_type(is_mask, is_emoji); if (in_sticker_set_stored != in_sticker_set) { Slice data = parser.template fetch_string_raw(parser.get_left_len()); for (auto c : data) { @@ -299,12 +293,7 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser } else { sticker_format = StickerFormat::Webp; } - StickerType sticker_type = StickerType::Regular; - if (is_emojis) { - sticker_type = StickerType::CustomEmoji; - } else if (is_masks) { - sticker_type = StickerType::Mask; - } + auto sticker_type = ::td::get_sticker_type(is_masks, is_emojis); if (sticker_set->is_inited_) { string title; @@ -499,6 +488,8 @@ void StickersManager::Reaction::parse(ParserT &parser) { if (has_center_animation) { center_animation_ = stickers_manager->parse_sticker(false, parser); } + + is_premium_ = false; } template @@ -525,4 +516,28 @@ void StickersManager::Reactions::parse(ParserT &parser) { } } +template +void StickersManager::ReactionList::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 +void StickersManager::ReactionList::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 diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 06e32ceb5..93e91a5c4 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -41,6 +41,7 @@ #include "td/telegram/DocumentsManager.h" #include "td/telegram/DownloadManager.h" #include "td/telegram/DownloadManagerCallback.h" +#include "td/telegram/EmojiStatus.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileGcParameters.h" #include "td/telegram/files/FileId.h" @@ -103,6 +104,7 @@ #include "td/telegram/SecretChatsManager.h" #include "td/telegram/SecureManager.h" #include "td/telegram/SecureValue.h" +#include "td/telegram/SentEmailCode.h" #include "td/telegram/SponsoredMessageManager.h" #include "td/telegram/StateManager.h" #include "td/telegram/StickerSetId.h" @@ -2776,11 +2778,11 @@ void Td::set_is_bot_online(bool is_bot_online) { bool Td::is_authentication_request(int32 id) { switch (id) { case td_api::setTdlibParameters::ID: - case td_api::checkDatabaseEncryptionKey::ID: - case td_api::setDatabaseEncryptionKey::ID: case td_api::getAuthorizationState::ID: case td_api::setAuthenticationPhoneNumber::ID: + case td_api::setAuthenticationEmailAddress::ID: case td_api::resendAuthenticationCode::ID: + case td_api::checkAuthenticationEmailCode::ID: case td_api::checkAuthenticationCode::ID: case td_api::registerUser::ID: case td_api::requestQrCodeAuthentication::ID: @@ -2900,8 +2902,6 @@ td_api::object_ptr Td::get_fake_authorization_state_ switch (state_) { case State::WaitParameters: return td_api::make_object(); - case State::Decrypt: - return td_api::make_object(is_database_encrypted_); case State::Run: UNREACHABLE(); return nullptr; @@ -2964,10 +2964,6 @@ void Td::run_request(uint64 id, tl_object_ptr function) { pending_set_parameters_requests_.emplace_back(id, std::move(function)); return; } - if (init_request_id_ > 0) { - pending_init_requests_.emplace_back(id, std::move(function)); - return; - } int32 function_id = function->get_id(); if (state_ != State::Run) { @@ -2991,18 +2987,22 @@ void Td::run_request(uint64 id, tl_object_ptr function) { case State::WaitParameters: { switch (function_id) { case td_api::setTdlibParameters::ID: { - auto status = set_parameters(std::move(move_tl_object_as(function)->parameters_)); + auto parameters = move_tl_object_as(function); + auto database_encryption_key = as_db_key(std::move(parameters->database_encryption_key_), Global::get_use_custom_database(parameters->database_directory_)); + auto status = set_parameters(std::move(parameters)); if (status.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, std::move(status)); } - VLOG(td_init) << "Begin to check parameters"; + VLOG(td_init) << "Begin to open database"; set_parameters_request_id_ = id; + auto promise = - PromiseCreator::lambda([actor_id = actor_id(this)](Result r_checked_parameters) { - send_closure(actor_id, &Td::on_parameters_checked, std::move(r_checked_parameters)); + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_opened_database) { + send_closure(actor_id, &Td::init, std::move(r_opened_database)); }); - return TdDb::check_parameters(get_database_scheduler_id(), parameters_, std::move(promise)); + return TdDb::open(get_database_scheduler_id(), parameters_, std::move(database_encryption_key), + std::move(promise)); } default: if (is_preinitialization_request(function_id)) { @@ -3017,34 +3017,6 @@ void Td::run_request(uint64 id, tl_object_ptr function) { } break; } - case State::Decrypt: { - switch (function_id) { - case td_api::checkDatabaseEncryptionKey::ID: { - auto check_key = move_tl_object_as(function); - return start_init(id, std::move(check_key->encryption_key_)); - } - case td_api::setDatabaseEncryptionKey::ID: { - auto set_key = move_tl_object_as(function); - return start_init(id, std::move(set_key->new_encryption_key_)); - } - case td_api::destroy::ID: - // need to send response synchronously before actual destroying - send_closure(actor_id(this), &Td::send_result, id, td_api::make_object()); - send_closure(actor_id(this), &Td::destroy); - return; - default: - if (is_preinitialization_request(function_id)) { - break; - } - if (is_preauthentication_request(function_id)) { - pending_preauthentication_requests_.emplace_back(id, std::move(function)); - return; - } - return send_error_impl( - id, make_error(400, "Database encryption key is needed: call checkDatabaseEncryptionKey first")); - } - break; - } case State::Close: if (destroy_flag_) { return send_error_impl(id, make_error(401, "Unauthorized")); @@ -3519,11 +3491,8 @@ void Td::close_impl(bool destroy_flag) { } LOG(WARNING) << (destroy_flag ? "Destroy" : "Close") << " Td in state " << static_cast(state_); - if (state_ == State::WaitParameters || state_ == State::Decrypt) { + if (state_ == State::WaitParameters) { clear_requests(); - if (destroy_flag && state_ == State::Decrypt) { - TdDb::destroy(parameters_).ignore(); - } state_ = State::Close; close_flag_ = 4; G()->set_close_flag(); @@ -3594,29 +3563,6 @@ int32 Td::get_database_scheduler_id() { return min(current_scheduler_id + 1, scheduler_count - 1); } -void Td::on_parameters_checked(Result r_checked_parameters) { - CHECK(set_parameters_request_id_ != 0); - if (r_checked_parameters.is_error()) { - send_closure(actor_id(this), &Td::send_error, set_parameters_request_id_, - Status::Error(400, r_checked_parameters.error().message())); - return finish_set_parameters(); - } - auto checked_parameters = r_checked_parameters.move_as_ok(); - - parameters_.database_directory = std::move(checked_parameters.database_directory); - parameters_.files_directory = std::move(checked_parameters.files_directory); - is_database_encrypted_ = checked_parameters.is_database_encrypted; - - state_ = State::Decrypt; - VLOG(td_init) << "Send authorizationStateWaitEncryptionKey"; - send_closure(actor_id(this), &Td::send_update, - td_api::make_object( - td_api::make_object(is_database_encrypted_))); - VLOG(td_init) << "Finish set parameters"; - send_closure(actor_id(this), &Td::send_result, set_parameters_request_id_, td_api::make_object()); - return finish_set_parameters(); -} - void Td::finish_set_parameters() { CHECK(set_parameters_request_id_ != 0); set_parameters_request_id_ = 0; @@ -3632,32 +3578,23 @@ void Td::finish_set_parameters() { } CHECK(pending_set_parameters_requests_.size() < requests.size()); } - -void Td::start_init(uint64 id, string &&key) { - VLOG(td_init) << "Begin to init database"; - init_request_id_ = id; - - auto promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result r_opened_database) { - send_closure(actor_id, &Td::init, std::move(r_opened_database)); - }); - TdDb::open(get_database_scheduler_id(), parameters_, as_db_key(std::move(key), parameters_.use_custom_db_format), - std::move(promise)); -} - void Td::init(Result r_opened_database) { - CHECK(init_request_id_ != 0); + CHECK(set_parameters_request_id_ != 0); if (r_opened_database.is_error()) { LOG(WARNING) << "Failed to open database: " << r_opened_database.error(); - send_closure(actor_id(this), &Td::send_error, init_request_id_, + send_closure(actor_id(this), &Td::send_error, set_parameters_request_id_, Status::Error(400, r_opened_database.error().message())); - return finish_init(); + return finish_set_parameters(); } + auto events = r_opened_database.move_as_ok(); + + parameters_.database_directory = std::move(events.database_directory); + parameters_.files_directory = std::move(events.files_directory); LOG(INFO) << "Successfully inited database in " << tag("database_directory", parameters_.database_directory) << " and " << tag("files_directory", parameters_.files_directory); VLOG(td_init) << "Successfully inited database"; - auto events = r_opened_database.move_as_ok(); G()->init(parameters_, actor_id(this), std::move(events.database)).ensure(); init_options_and_network(); @@ -3793,24 +3730,8 @@ void Td::init(Result r_opened_database) { state_ = State::Run; - send_closure(actor_id(this), &Td::send_result, init_request_id_, td_api::make_object()); - return finish_init(); -} - -void Td::finish_init() { - CHECK(init_request_id_ > 0); - init_request_id_ = 0; - - if (pending_init_requests_.empty()) { - return; - } - - VLOG(td_init) << "Continue to execute " << pending_init_requests_.size() << " pending requests"; - auto requests = std::move(pending_init_requests_); - for (auto &request : requests) { - run_request(request.first, std::move(request.second)); - } - CHECK(pending_init_requests_.size() < requests.size()); + send_closure(actor_id(this), &Td::send_result, set_parameters_request_id_, td_api::make_object()); + return finish_set_parameters(); } void Td::init_options_and_network() { @@ -4077,6 +3998,7 @@ void Td::send_update(tl_object_ptr &&object) { case td_api::updateFileAddedToDownloads::ID / 2: case td_api::updateFileDownload::ID / 2: case td_api::updateFileRemovedFromDownloads::ID / 2: + case td_api::updateDefaultReactionType::ID / 2: LOG(ERROR) << "Sending update: " << oneline(to_string(object)); break; default: @@ -4198,13 +4120,8 @@ Status Td::fix_parameters(TdParameters ¶meters) { return Status::OK(); } -Status Td::set_parameters(td_api::object_ptr parameters) { +Status Td::set_parameters(td_api::object_ptr parameters) { VLOG(td_init) << "Begin to set TDLib parameters"; - if (parameters == nullptr) { - VLOG(td_init) << "Empty parameters"; - return Status::Error(400, "Parameters aren't specified"); - } - if (!clean_input_string(parameters->api_hash_) || !clean_input_string(parameters->system_language_code_) || !clean_input_string(parameters->device_model_) || !clean_input_string(parameters->system_version_) || !clean_input_string(parameters->application_version_)) { @@ -4265,10 +4182,6 @@ void Td::on_request(uint64 id, const td_api::setTdlibParameters &request) { send_error_raw(id, 400, "Unexpected setTdlibParameters"); } -void Td::on_request(uint64 id, const td_api::checkDatabaseEncryptionKey &request) { - send_error_raw(id, 400, "Unexpected checkDatabaseEncryptionKey"); -} - void Td::on_request(uint64 id, td_api::setDatabaseEncryptionKey &request) { CREATE_OK_REQUEST_PROMISE(); G()->td_db()->get_binlog()->change_key(as_db_key(std::move(request.new_encryption_key_), parameters_.use_custom_db_format), std::move(promise)); @@ -4284,10 +4197,19 @@ void Td::on_request(uint64 id, td_api::setAuthenticationPhoneNumber &request) { std::move(request.settings_)); } +void Td::on_request(uint64 id, td_api::setAuthenticationEmailAddress &request) { + CLEAN_INPUT_STRING(request.email_address_); + send_closure(auth_manager_actor_, &AuthManager::set_email_address, id, std::move(request.email_address_)); +} + void Td::on_request(uint64 id, const td_api::resendAuthenticationCode &request) { send_closure(auth_manager_actor_, &AuthManager::resend_authentication_code, id); } +void Td::on_request(uint64 id, td_api::checkAuthenticationEmailCode &request) { + send_closure(auth_manager_actor_, &AuthManager::check_email_code, id, EmailVerification(std::move(request.code_))); +} + void Td::on_request(uint64 id, td_api::checkAuthenticationCode &request) { CLEAN_INPUT_STRING(request.code_); send_closure(auth_manager_actor_, &AuthManager::check_code, id, std::move(request.code_)); @@ -4420,6 +4342,41 @@ void Td::on_request(uint64 id, td_api::setPassword &request) { std::move(request.new_recovery_email_address_), std::move(promise)); } +void Td::on_request(uint64 id, td_api::setLoginEmailAddress &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.new_login_email_address_); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_email_address_authentication_code_info_object()); + } + }); + send_closure(password_manager_, &PasswordManager::set_login_email_address, + std::move(request.new_login_email_address_), std::move(query_promise)); +} + +void Td::on_request(uint64 id, const td_api::resendLoginEmailAddressCode &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_email_address_authentication_code_info_object()); + } + }); + send_closure(password_manager_, &PasswordManager::resend_login_email_address_code, std::move(query_promise)); +} + +void Td::on_request(uint64 id, td_api::checkLoginEmailAddressCode &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + send_closure(password_manager_, &PasswordManager::check_login_email_address_code, + EmailVerification(std::move(request.code_)), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setRecoveryEmailAddress &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.password_); @@ -4454,7 +4411,14 @@ void Td::on_request(uint64 id, const td_api::resendRecoveryEmailAddressCode &req void Td::on_request(uint64 id, td_api::requestPasswordRecovery &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - send_closure(password_manager_, &PasswordManager::request_password_recovery, std::move(promise)); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_email_address_authentication_code_info_object()); + } + }); + send_closure(password_manager_, &PasswordManager::request_password_recovery, std::move(query_promise)); } void Td::on_request(uint64 id, td_api::checkPasswordRecoveryCode &request) { @@ -5272,36 +5236,62 @@ 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::getEmojiReaction &request) { + CHECK_IS_USER(); + send_closure(actor_id(this), &Td::send_result, id, stickers_manager_->get_emoji_reaction_object(request.emoji_)); +} + +void Td::on_request(uint64 id, const td_api::getCustomEmojiReactionAnimations &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + stickers_manager_->get_custom_emoji_reaction_generic_animations(false, std::move(promise)); +} + 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_)}); + auto r_reactions = messages_manager_->get_message_available_reactions( + {DialogId(request.chat_id_), MessageId(request.message_id_)}, request.row_size_); if (r_reactions.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_reactions.move_as_error()); } else { - auto reactions = - transform(r_reactions.ok(), [](auto &reaction) { return reaction.get_available_reaction_object(); }); - send_closure(actor_id(this), &Td::send_result, id, - td_api::make_object(std::move(reactions))); + send_closure(actor_id(this), &Td::send_result, id, r_reactions.move_as_ok()); } } -void Td::on_request(uint64 id, td_api::setMessageReaction &request) { +void Td::on_request(uint64 id, const td_api::clearRecentReactions &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)); + stickers_manager_->clear_recent_reactions(std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::addMessageReaction &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->add_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)}, + get_message_reaction_string(request.reaction_type_), request.is_big_, + request.update_recent_reactions_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::removeMessageReaction &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->remove_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)}, + get_message_reaction_string(request.reaction_type_), 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)); + get_message_reaction_string(request.reaction_type_), std::move(request.offset_), + request.limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::setDefaultReactionType &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + set_default_reaction(this, get_message_reaction_string(request.reaction_type_), std::move(promise)); } void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) { @@ -6125,9 +6115,6 @@ void Td::on_request(uint64 id, const td_api::toggleBotIsAddedToAttachmentMenu &r } 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)); @@ -6762,6 +6749,36 @@ void Td::on_request(uint64 id, td_api::setUsername &request) { contacts_manager_->set_username(request.username_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::setEmojiStatus &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->set_emoji_status(EmojiStatus(request.emoji_status_, request.duration_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getThemedEmojiStatuses &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + stickers_manager_->get_default_emoji_statuses(false, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getDefaultEmojiStatuses &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_default_emoji_statuses(this, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getRecentEmojiStatuses &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_recent_emoji_statuses(this, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::clearRecentEmojiStatuses &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + clear_recent_emoji_statuses(this, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setCommands &request) { CHECK_IS_BOT(); CREATE_OK_REQUEST_PROMISE(); @@ -7207,6 +7224,14 @@ void Td::on_request(uint64 id, td_api::reportChatPhoto &request) { r_report_reason.move_as_ok(), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::reportMessageReactions &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + TRY_RESULT_PROMISE(promise, sender_dialog_id, get_message_sender_dialog_id(this, request.sender_id_, false, false)); + report_message_reactions(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, sender_dialog_id, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getChatStatistics &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -7417,6 +7442,7 @@ void Td::on_request(uint64 id, td_api::answerInlineQuery &request) { void Td::on_request(uint64 id, td_api::getWebAppUrl &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.url_); + CLEAN_INPUT_STRING(request.application_name_); CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { if (result.is_error()) { @@ -7426,7 +7452,8 @@ void Td::on_request(uint64 id, td_api::getWebAppUrl &request) { } }); inline_queries_manager_->get_simple_web_view_url(UserId(request.bot_user_id_), std::move(request.url_), - std::move(request.theme_), std::move(query_promise)); + std::move(request.theme_), std::move(request.application_name_), + std::move(query_promise)); } void Td::on_request(uint64 id, td_api::sendWebAppData &request) { @@ -7441,10 +7468,11 @@ void Td::on_request(uint64 id, td_api::sendWebAppData &request) { void Td::on_request(uint64 id, td_api::openWebApp &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.url_); + CLEAN_INPUT_STRING(request.application_name_); CREATE_REQUEST_PROMISE(); - attach_menu_manager_->request_web_view(DialogId(request.chat_id_), UserId(request.bot_user_id_), - MessageId(request.reply_to_message_id_), std::move(request.url_), - std::move(request.theme_), std::move(promise)); + attach_menu_manager_->request_web_view( + DialogId(request.chat_id_), UserId(request.bot_user_id_), MessageId(request.reply_to_message_id_), + std::move(request.url_), std::move(request.theme_), std::move(request.application_name_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::closeWebApp &request) { @@ -7640,14 +7668,28 @@ void Td::on_request(uint64 id, td_api::sendEmailAddressVerificationCode &request CHECK_IS_USER(); CLEAN_INPUT_STRING(request.email_address_); CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_email_address_authentication_code_info_object()); + } + }); send_closure(password_manager_, &PasswordManager::send_email_address_verification_code, - std::move(request.email_address_), std::move(promise)); + std::move(request.email_address_), std::move(query_promise)); } void Td::on_request(uint64 id, const td_api::resendEmailAddressVerificationCode &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - send_closure(password_manager_, &PasswordManager::resend_email_address_verification_code, std::move(promise)); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(result.ok().get_email_address_authentication_code_info_object()); + } + }); + send_closure(password_manager_, &PasswordManager::resend_email_address_verification_code, std::move(query_promise)); } void Td::on_request(uint64 id, td_api::checkEmailAddressVerificationCode &request) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 8dcb5cb37..ad2dd3462 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -300,10 +300,8 @@ class Td final : public Actor { bool destroy_flag_ = false; int close_flag_ = 0; - enum class State : int32 { WaitParameters, Decrypt, Run, Close } state_ = State::WaitParameters; - uint64 init_request_id_ = 0; + enum class State : int32 { WaitParameters, Run, Close } state_ = State::WaitParameters; uint64 set_parameters_request_id_ = 0; - bool is_database_encrypted_ = false; FlatHashMap> result_handlers_; enum : int8 { RequestActorIdType = 1, ActorIdType = 2 }; @@ -399,16 +397,16 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::setTdlibParameters &request); - void on_request(uint64 id, const td_api::checkDatabaseEncryptionKey &request); - - void on_request(uint64 id, td_api::setDatabaseEncryptionKey &request); - void on_request(uint64 id, const td_api::getAuthorizationState &request); void on_request(uint64 id, td_api::setAuthenticationPhoneNumber &request); + void on_request(uint64 id, td_api::setAuthenticationEmailAddress &request); + void on_request(uint64 id, const td_api::resendAuthenticationCode &request); + void on_request(uint64 id, td_api::checkAuthenticationEmailCode &request); + void on_request(uint64 id, td_api::checkAuthenticationCode &request); void on_request(uint64 id, td_api::registerUser &request); @@ -433,12 +431,20 @@ class Td final : public Actor { void on_request(uint64 id, td_api::confirmQrCodeAuthentication &request); + void on_request(uint64 id, td_api::setDatabaseEncryptionKey &request); + void on_request(uint64 id, const td_api::getCurrentState &request); void on_request(uint64 id, td_api::getPasswordState &request); void on_request(uint64 id, td_api::setPassword &request); + void on_request(uint64 id, td_api::setLoginEmailAddress &request); + + void on_request(uint64 id, const td_api::resendLoginEmailAddressCode &request); + + void on_request(uint64 id, td_api::checkLoginEmailAddressCode &request); + void on_request(uint64 id, td_api::getRecoveryEmailAddress &request); void on_request(uint64 id, td_api::setRecoveryEmailAddress &request); @@ -661,12 +667,22 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatScheduledMessages &request); + void on_request(uint64 id, const td_api::getEmojiReaction &request); + + void on_request(uint64 id, const td_api::getCustomEmojiReactionAnimations &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, const td_api::clearRecentReactions &request); + + void on_request(uint64 id, td_api::addMessageReaction &request); + + void on_request(uint64 id, td_api::removeMessageReaction &request); void on_request(uint64 id, td_api::getMessageAddedReactions &request); + void on_request(uint64 id, td_api::setDefaultReactionType &request); + void on_request(uint64 id, td_api::getMessagePublicForwards &request); void on_request(uint64 id, const td_api::removeNotification &request); @@ -1021,6 +1037,16 @@ class Td final : public Actor { void on_request(uint64 id, td_api::setUsername &request); + void on_request(uint64 id, const td_api::setEmojiStatus &request); + + void on_request(uint64 id, const td_api::getThemedEmojiStatuses &request); + + void on_request(uint64 id, const td_api::getDefaultEmojiStatuses &request); + + void on_request(uint64 id, const td_api::getRecentEmojiStatuses &request); + + void on_request(uint64 id, const td_api::clearRecentEmojiStatuses &request); + void on_request(uint64 id, td_api::setCommands &request); void on_request(uint64 id, td_api::deleteCommands &request); @@ -1161,6 +1187,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::reportChatPhoto &request); + void on_request(uint64 id, const td_api::reportMessageReactions &request); + void on_request(uint64 id, const td_api::getChatStatistics &request); void on_request(uint64 id, const td_api::getMessageStatistics &request); @@ -1453,12 +1481,8 @@ class Td final : public Actor { static int32 get_database_scheduler_id(); - void on_parameters_checked(Result r_checked_parameters); - void finish_set_parameters(); - void start_init(uint64 id, string &&key); - void init(Result r_opened_database); void init_options_and_network(); @@ -1469,15 +1493,13 @@ class Td final : public Actor { void init_managers(); - void finish_init(); - void clear(); void close_impl(bool destroy_flag); static Status fix_parameters(TdParameters ¶meters) TD_WARN_UNUSED_RESULT; - Status set_parameters(td_api::object_ptr parameters) TD_WARN_UNUSED_RESULT; + Status set_parameters(td_api::object_ptr parameters) TD_WARN_UNUSED_RESULT; static td_api::object_ptr make_error(int32 code, CSlice error) { return td_api::make_object(code, error.str()); diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index 8c0dd20ea..4783cd50d 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -288,7 +288,7 @@ Status TdDb::init_sqlite(const TdParameters ¶meters, const DbKey &key, const bool use_dialog_db = parameters.use_message_db; bool use_message_db = parameters.use_message_db; if (!use_sqlite) { - unlink(sql_database_path).ignore(); + SqliteDb::destroy(sql_database_path).ignore(); return Status::OK(); } @@ -393,7 +393,11 @@ void TdDb::open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise< } void TdDb::open_impl(TdParameters parameters, DbKey key, Promise &&promise) { + TRY_STATUS_PROMISE(promise, check_parameters(parameters)); + OpenedDatabase result; + result.database_directory = parameters.database_directory; + result.files_directory = parameters.files_directory; // Init pmc Binlog *binlog_ptr = nullptr; @@ -415,6 +419,11 @@ void TdDb::open_impl(TdParameters parameters, DbKey key, Promise config_pmc->external_init_finish(binlog); VLOG(td_init) << "Finish initialization of config PMC"; + if (parameters.use_file_db && binlog_pmc->get("auth").empty()) { + LOG(INFO) << "Destroy SQLite database, because wasn't authorized yet"; + SqliteDb::destroy(get_sqlite_path(parameters)).ignore(); + } + DbKey new_sqlite_key; DbKey old_sqlite_key; bool encrypt_sqlite = encrypt_binlog; @@ -486,16 +495,7 @@ void TdDb::open_impl(TdParameters parameters, DbKey key, Promise TdDb::TdDb() = default; TdDb::~TdDb() = default; -void TdDb::check_parameters(int32 scheduler_id, TdParameters parameters, Promise promise) { - Scheduler::instance()->run_on_scheduler( - scheduler_id, [parameters = std::move(parameters), promise = std::move(promise)](Unit) mutable { - TdDb::check_parameters_impl(std::move(parameters), std::move(promise)); - }); -} - -void TdDb::check_parameters_impl(TdParameters parameters, Promise promise) { - CheckedParameters result; - +Status TdDb::check_parameters(TdParameters ¶meters) { auto prepare_dir = [](string dir) -> Result { CHECK(!dir.empty()); if (dir.back() != TD_DIR_SLASH) { @@ -512,31 +512,20 @@ void TdDb::check_parameters_impl(TdParameters parameters, Promise promise) { diff --git a/td/telegram/TdDb.h b/td/telegram/TdDb.h index f38fb7c42..f249f2cc5 100644 --- a/td/telegram/TdDb.h +++ b/td/telegram/TdDb.h @@ -47,14 +47,10 @@ class TdDb { TdDb &operator=(TdDb &&) = delete; ~TdDb(); - struct CheckedParameters { + struct OpenedDatabase { string database_directory; string files_directory; - bool is_database_encrypted{false}; - }; - static void check_parameters(int32 scheduler_id, TdParameters parameters, Promise promise); - struct OpenedDatabase { unique_ptr database; vector to_secret_chats_manager; @@ -127,7 +123,7 @@ class TdDb { static void open_impl(TdParameters parameters, DbKey key, Promise &&promise); - static void check_parameters_impl(TdParameters parameters, Promise promise); + static Status check_parameters(TdParameters ¶meters); Status init_sqlite(const TdParameters ¶meters, const DbKey &key, const DbKey &old_key, BinlogKeyValue &binlog_pmc); diff --git a/td/telegram/TopDialogManager.cpp b/td/telegram/TopDialogManager.cpp index e57752cd0..48e0519ec 100644 --- a/td/telegram/TopDialogManager.cpp +++ b/td/telegram/TopDialogManager.cpp @@ -375,7 +375,7 @@ void TopDialogManager::do_get_top_dialogs(GetTopDialogsQuery &&query) { } send_closure(actor_id, &TopDialogManager::on_load_dialogs, std::move(query), r_dialog_ids.move_as_ok()); }); - td_->messages_manager_->load_dialogs(std::move(dialog_ids), std::move(promise)); + send_closure(td_->messages_manager_actor_, &MessagesManager::load_dialogs, std::move(dialog_ids), std::move(promise)); } void TopDialogManager::on_load_dialogs(GetTopDialogsQuery &&query, vector &&dialog_ids) { diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index ffde03cc9..4a99e2e82 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -20,6 +20,7 @@ #include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogParticipant.h" #include "td/telegram/DownloadManager.h" +#include "td/telegram/EmojiStatus.h" #include "td/telegram/FolderId.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" @@ -473,7 +474,7 @@ Promise<> UpdatesManager::set_pts(int32 pts, const char *source) { last_get_difference_pts_ = get_pts(); schedule_get_difference("rare pts getDifference"); } - } else if (pts < get_pts()) { + } else if (pts < get_pts() && (pts > 1 || td_->option_manager_->get_option_integer("session_count") <= 1)) { LOG(ERROR) << "Receive wrong pts = " << pts << " from " << source << ". Current pts = " << get_pts(); } return result; @@ -1063,7 +1064,10 @@ void UpdatesManager::on_get_updates_state(tl_object_ptrpts_; // restoring right pts CHECK(pending_pts_updates_.empty()); + auto real_running_get_difference = running_get_difference_; + running_get_difference_ = false; process_postponed_pts_updates(); // drop all updates with old pts + running_get_difference_ = real_running_get_difference; pts_manager_.init(state->pts_); last_get_difference_pts_ = get_pts(); last_pts_save_time_ = Time::now() - 2 * MAX_PTS_SAVE_DELAY; @@ -1697,6 +1701,7 @@ void UpdatesManager::try_reload_data() { td_->animations_manager_->get_saved_animations(Auto()); td_->contacts_manager_->reload_created_public_dialogs(PublicDialogType::HasUsername, Auto()); td_->contacts_manager_->reload_created_public_dialogs(PublicDialogType::IsLocationBased, Auto()); + get_default_emoji_statuses(td_, Auto()); td_->notification_settings_manager_->reload_saved_ringtones(Auto()); td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Private, Auto()); @@ -1705,6 +1710,8 @@ void UpdatesManager::try_reload_data() { td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Auto()); td_->stickers_manager_->reload_reactions(); + td_->stickers_manager_->reload_recent_reactions(); + td_->stickers_manager_->reload_top_reactions(); for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { auto sticker_type = static_cast(type); td_->stickers_manager_->get_installed_sticker_sets(sticker_type, Auto()); @@ -1716,6 +1723,8 @@ void UpdatesManager::try_reload_data() { td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji()); td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji_click()); td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::premium_gifts()); + td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::generic_animations()); + td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::default_statuses()); schedule_data_reload(); } @@ -2940,6 +2949,11 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->stickers_manager_->reload_recent_reactions(); + promise.set_value(Unit()); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->attach_menu_manager_->reload_attach_menu_bots(std::move(promise)); } @@ -3165,11 +3179,20 @@ void UpdatesManager::on_update(tl_object_ptr upda } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - // TODO update->previous_, update->date_ td_->contacts_manager_->on_update_user_photo(UserId(update->user_id_), std::move(update->photo_)); promise.set_value(Unit()); } +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->contacts_manager_->on_update_user_emoji_status(UserId(update->user_id_), std::move(update->emoji_status_)); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + get_recent_emoji_statuses(td_, Auto()); + promise.set_value(Unit()); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->messages_manager_->on_update_dialog_is_blocked(DialogId(update->peer_id_), update->blocked_); promise.set_value(Unit()); @@ -3352,22 +3375,24 @@ void UpdatesManager::on_update(tl_object_ptr } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->stickers_manager_->on_update_sticker_sets(); + auto sticker_type = get_sticker_type(update->masks_, update->emojis_); + td_->stickers_manager_->on_update_sticker_sets(sticker_type); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - StickerType sticker_type = StickerType::Regular; - if (update->emojis_) { - sticker_type = StickerType::CustomEmoji; - } else if (update->masks_) { - sticker_type = StickerType::Mask; - } + auto sticker_type = get_sticker_type(update->masks_, update->emojis_); td_->stickers_manager_->on_update_sticker_sets_order(sticker_type, StickersManager::convert_sticker_set_ids(update->order_)); promise.set_value(Unit()); } +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + auto sticker_type = get_sticker_type(update->masks_, update->emojis_); + td_->stickers_manager_->on_update_move_sticker_set_to_top(sticker_type, StickerSetId(update->stickerset_)); + promise.set_value(Unit()); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->stickers_manager_->reload_featured_sticker_sets(StickerType::Regular, true); diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index dd4e9291e..0d2c008a1 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -406,6 +406,8 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); @@ -420,6 +422,8 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); @@ -489,6 +493,7 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); diff --git a/td/telegram/Version.h b/td/telegram/Version.h index 7d74db9b8..b53466f6a 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -10,7 +10,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 144; +constexpr int32 MTPROTO_LAYER = 145; enum class Version : int32 { Initial, // 0 diff --git a/td/telegram/VoiceNotesManager.cpp b/td/telegram/VoiceNotesManager.cpp index 6ca890556..0fa497a37 100644 --- a/td/telegram/VoiceNotesManager.cpp +++ b/td/telegram/VoiceNotesManager.cpp @@ -182,7 +182,7 @@ FileId VoiceNotesManager::on_get_voice_note(unique_ptr new_voice_note v->transcription_id = new_voice_note->transcription_id; v->text = std::move(new_voice_note->text); v->last_transcription_error = Status::OK(); - on_voice_note_transcription_updated(file_id); + on_voice_note_transcription_completed(file_id); } } @@ -316,7 +316,7 @@ void VoiceNotesManager::on_voice_note_transcribed(FileId file_id, string &&text, auto promises = std::move(it->second); speech_recognition_queries_.erase(it); - on_voice_note_transcription_updated(file_id); + on_voice_note_transcription_completed(file_id); set_promises(promises); } else { if (is_changed) { @@ -386,6 +386,15 @@ void VoiceNotesManager::on_voice_note_transcription_updated(FileId file_id) { } } +void VoiceNotesManager::on_voice_note_transcription_completed(FileId file_id) { + auto it = voice_note_messages_.find(file_id); + if (it != voice_note_messages_.end()) { + for (const auto &full_message_id : it->second) { + td_->messages_manager_->on_update_message_content(full_message_id); + } + } +} + void VoiceNotesManager::rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise &&promise) { if (!td_->messages_manager_->have_message_force(full_message_id, "rate_speech_recognition")) { return promise.set_error(Status::Error(400, "Message not found")); diff --git a/td/telegram/VoiceNotesManager.h b/td/telegram/VoiceNotesManager.h index ce5fb3f55..e77b94f57 100644 --- a/td/telegram/VoiceNotesManager.h +++ b/td/telegram/VoiceNotesManager.h @@ -100,6 +100,8 @@ class VoiceNotesManager final : public Actor { void on_voice_note_transcription_updated(FileId file_id); + void on_voice_note_transcription_completed(FileId file_id); + void tear_down() final; Td *td_; diff --git a/td/telegram/WebPageBlock.cpp b/td/telegram/WebPageBlock.cpp index 229c00bd1..bdf2ab925 100644 --- a/td/telegram/WebPageBlock.cpp +++ b/td/telegram/WebPageBlock.cpp @@ -53,7 +53,7 @@ struct GetWebPageBlockObjectContext { std::unordered_map anchors_; // anchor -> text }; -static vector> get_page_block_objects( +static vector> get_page_blocks_object( const vector> &page_blocks, GetWebPageBlockObjectContext *context) { return transform(page_blocks, [context](const unique_ptr &page_block) { return page_block->get_page_block_object(context); @@ -61,7 +61,7 @@ static vector> get_page_block_objects( } class RichText { - static vector> get_rich_text_objects(const vector &rich_texts, + static vector> get_rich_texts_object(const vector &rich_texts, GetWebPageBlockObjectContext *context) { return transform(rich_texts, [context](const RichText &rich_text) { return rich_text.get_rich_text_object(context); }); @@ -152,7 +152,7 @@ class RichText { case RichText::Type::EmailAddress: return make_tl_object(texts[0].get_rich_text_object(context), content); case RichText::Type::Concatenation: - return make_tl_object(get_rich_text_objects(texts, context)); + return make_tl_object(get_rich_texts_object(texts, context)); case RichText::Type::Subscript: return make_tl_object(texts[0].get_rich_text_object(context)); case RichText::Type::Superscript: @@ -832,7 +832,7 @@ class WebPageBlockList final : public WebPageBlock { Context *context) { // if label is empty, then Bullet U+2022 is used as a label return td_api::make_object(item.label.empty() ? "\xE2\x80\xA2" : item.label, - get_page_block_objects(item.page_blocks, context)); + get_page_blocks_object(item.page_blocks, context)); } public: @@ -1291,7 +1291,7 @@ class WebPageBlockEmbeddedPost final : public WebPageBlock { td_api::object_ptr get_page_block_object(Context *context) const final { return make_tl_object( url, author, get_photo_object(context->td_->file_manager_.get(), author_photo), date, - get_page_block_objects(page_blocks, context), caption.get_page_block_caption_object(context)); + get_page_blocks_object(page_blocks, context), caption.get_page_block_caption_object(context)); } template @@ -1339,7 +1339,7 @@ class WebPageBlockCollage final : public WebPageBlock { } td_api::object_ptr get_page_block_object(Context *context) const final { - return make_tl_object(get_page_block_objects(page_blocks, context), + return make_tl_object(get_page_blocks_object(page_blocks, context), caption.get_page_block_caption_object(context)); } @@ -1380,7 +1380,7 @@ class WebPageBlockSlideshow final : public WebPageBlock { } td_api::object_ptr get_page_block_object(Context *context) const final { - return make_tl_object(get_page_block_objects(page_blocks, context), + return make_tl_object(get_page_blocks_object(page_blocks, context), caption.get_page_block_caption_object(context)); } @@ -1591,7 +1591,7 @@ class WebPageBlockDetails final : public WebPageBlock { td_api::object_ptr get_page_block_object(Context *context) const final { return make_tl_object(header.get_rich_text_object(context), - get_page_block_objects(page_blocks, context), is_open); + get_page_blocks_object(page_blocks, context), is_open); } template @@ -2386,19 +2386,19 @@ vector> get_web_page_blocks( return result; } -vector> get_page_block_objects( +vector> get_page_blocks_object( const vector> &page_blocks, Td *td, Slice base_url) { GetWebPageBlockObjectContext context; context.td_ = td; context.base_url_ = base_url; - auto blocks = get_page_block_objects(page_blocks, &context); + auto blocks = get_page_blocks_object(page_blocks, &context); if (context.anchors_.empty() || !context.has_anchor_urls_) { return blocks; } context.is_first_pass_ = false; context.anchors_.emplace(Slice(), nullptr); // back to top - return get_page_block_objects(page_blocks, &context); + return get_page_blocks_object(page_blocks, &context); } } // namespace td diff --git a/td/telegram/WebPageBlock.h b/td/telegram/WebPageBlock.h index 64247231d..81e5f4701 100644 --- a/td/telegram/WebPageBlock.h +++ b/td/telegram/WebPageBlock.h @@ -101,7 +101,7 @@ vector> get_web_page_blocks( const FlatHashMap &documents, const FlatHashMap> &photos, const FlatHashMap &videos, const FlatHashMap &voice_notes); -vector> get_page_block_objects( +vector> get_page_blocks_object( const vector> &page_blocks, Td *td, Slice base_url); } // namespace td diff --git a/td/telegram/WebPagesManager.cpp b/td/telegram/WebPagesManager.cpp index bc2abcd05..3aff0010a 100644 --- a/td/telegram/WebPagesManager.cpp +++ b/td/telegram/WebPagesManager.cpp @@ -1238,6 +1238,9 @@ tl_object_ptr WebPagesManager::get_web_page_object(WebPageId we if (entity.type == MessageEntity::Type::Hashtag) { return PSTRING() << "https://twitter.com/hashtag/" << url_encode(text.substr(1)); } + if (entity.type == MessageEntity::Type::Cashtag) { + return PSTRING() << "https://twitter.com/search?q=" << url_encode(text) << "&src=cashtag_click"; + } return string(); }); } else if (host == "t.me" || host == "telegram.me" || host == "telegram.dog" || host == "telesco.pe") { @@ -1311,7 +1314,7 @@ tl_object_ptr WebPagesManager::get_web_page_instant_ auto feedback_link = td_api::make_object( "previews", PSTRING() << "webpage" << web_page_id.get(), true); return td_api::make_object( - get_page_block_objects(web_page_instant_view->page_blocks, td_, web_page_instant_view->url), + get_page_blocks_object(web_page_instant_view->page_blocks, td_, web_page_instant_view->url), web_page_instant_view->view_count, web_page_instant_view->is_v2 ? 2 : 1, web_page_instant_view->is_rtl, web_page_instant_view->is_full, std::move(feedback_link)); } diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 97ef04e2a..7f9fcadaf 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -400,25 +400,22 @@ class CliClient final : public Actor { authorization_state_ = std::move(state); switch (authorization_state_->get_id()) { case td_api::authorizationStateWaitTdlibParameters::ID: { - auto parameters = td_api::make_object(); - parameters->use_test_dc_ = use_test_dc_; - parameters->use_message_database_ = false; - parameters->use_chat_info_database_ = false; - parameters->use_file_database_ = false; - parameters->use_secret_chats_ = false; - parameters->api_id_ = api_id_; - parameters->api_hash_ = api_hash_; - parameters->system_language_code_ = "en"; - parameters->device_model_ = "Desktop"; - parameters->application_version_ = "1.0"; + auto request = td_api::make_object(); + request->use_test_dc_ = use_test_dc_; + request->use_message_database_ = false; + request->use_chat_info_database_ = false; + request->use_file_database_ = false; + request->use_secret_chats_ = false; + request->api_id_ = api_id_; + request->api_hash_ = api_hash_; + request->system_language_code_ = "en"; + request->device_model_ = "Desktop"; + request->application_version_ = "1.0"; send_request( td_api::make_object("use_pfs", td_api::make_object(true))); - send_request(td_api::make_object(std::move(parameters))); + send_request(std::move(request)); break; } - case td_api::authorizationStateWaitEncryptionKey::ID: - send_request(td_api::make_object()); - break; case td_api::authorizationStateReady::ID: LOG(INFO) << "Logged in"; break; @@ -725,6 +722,18 @@ class CliClient final : public Actor { return td_api::make_object(to_double(latitude), to_double(longitude), to_double(accuracy)); } + static td_api::object_ptr as_reaction_type(Slice type) { + type = trim(type); + if (type.empty()) { + return nullptr; + } + auto r_custom_emoji_id = to_integer_safe(type); + if (r_custom_emoji_id.is_ok()) { + return td_api::make_object(r_custom_emoji_id.ok()); + } + return td_api::make_object(type.str()); + } + static bool as_bool(string str) { str = to_lower(trim(str)); return str == "true" || str == "1"; @@ -1154,11 +1163,11 @@ class CliClient final : public Actor { td_api::make_object())); send_request(td_api::make_object(0)); - auto bad_parameters = td_api::make_object(); - bad_parameters->database_directory_ = "/.."; - bad_parameters->api_id_ = api_id_; - bad_parameters->api_hash_ = api_hash_; - send_request(td_api::make_object(std::move(bad_parameters))); + auto bad_request = td_api::make_object(); + bad_request->database_directory_ = "/.."; + bad_request->api_id_ = api_id_; + bad_request->api_hash_ = api_hash_; + send_request(std::move(bad_request)); } } @@ -1580,6 +1589,17 @@ class CliClient final : public Actor { return nullptr; } + static td_api::object_ptr as_email_address_authentication(Slice arg) { + if (begins_with(arg, "a ")) { + return td_api::make_object(arg.substr(2).str()); + } else if (begins_with(arg, "g ")) { + return td_api::make_object(arg.substr(2).str()); + } else if (!arg.empty()) { + return td_api::make_object(arg.str()); + } + return nullptr; + } + static td_api::object_ptr as_passport_element_type(Slice passport_element_type) { if (passport_element_type == "address" || passport_element_type == "a") { return td_api::make_object(); @@ -1797,7 +1817,7 @@ class CliClient final : public Actor { bool disable_notification = false, bool from_background = false, int64 reply_to_message_id = 0) { auto id = send_request(td_api::make_object( chat_id, as_message_thread_id(message_thread_id_), reply_to_message_id, - td_api::make_object(disable_notification, from_background, true, + td_api::make_object(disable_notification, from_background, true, true, as_message_scheduling_state(schedule_date_)), nullptr, std::move(input_message_content))); if (id != 0) { @@ -1806,7 +1826,7 @@ class CliClient final : public Actor { } td_api::object_ptr default_message_send_options() const { - return td_api::make_object(false, false, false, + return td_api::make_object(false, false, false, true, as_message_scheduling_state(schedule_date_)); } @@ -1831,12 +1851,14 @@ class CliClient final : public Actor { } else if (op == "sap" || op == "sapn") { send_request( td_api::make_object(args, get_phone_number_authentication_settings())); + } else if (op == "sae" || op == "saea") { + send_request(td_api::make_object(args)); } else if (op == "rac") { send_request(td_api::make_object()); - } else if (op == "cdek" || op == "CheckDatabaseEncryptionKey") { - send_request(td_api::make_object(args)); - } else if (op == "sdek" || op == "SetDatabaseEncryptionKey") { + } else if (op == "sdek") { send_request(td_api::make_object(args)); + } else if (op == "caec") { + send_request(td_api::make_object(as_email_address_authentication(args))); } else if (op == "cac") { send_request(td_api::make_object(args)); } else if (op == "ru") { @@ -1968,6 +1990,12 @@ class CliClient final : public Actor { send_request(td_api::make_object(args)); } else if (op == "reavc" || op == "ResendEmailAddressVerificationCode") { send_request(td_api::make_object()); + } else if (op == "slea") { + send_request(td_api::make_object(args)); + } else if (op == "rleac") { + send_request(td_api::make_object()); + } else if (op == "cleac") { + send_request(td_api::make_object(as_email_address_authentication(args))); } else if (op == "srea" || op == "SetRecoveryEmailAddress") { string password; string recovery_email_address; @@ -2205,18 +2233,38 @@ class CliClient final : public Actor { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); + } else if (op == "sdrt") { + string reaction; + get_args(args, reaction); + send_request(td_api::make_object(as_reaction_type(reaction))); + } else if (op == "ger") { + string emoji; + get_args(args, emoji); + send_request(td_api::make_object(emoji)); + } else if (op == "gcera") { + send_request(td_api::make_object()); } else if (op == "gmar") { ChatId chat_id; MessageId message_id; get_args(args, chat_id, message_id); - send_request(td_api::make_object(chat_id, message_id)); - } else if (op == "react") { + send_request(td_api::make_object(chat_id, message_id, 8)); + } else if (op == "crr") { + send_request(td_api::make_object()); + } else if (op == "amr" || 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(chat_id, message_id, reaction, is_big)); + bool update_recent_reactions; + get_args(args, chat_id, message_id, reaction, is_big, update_recent_reactions); + send_request(td_api::make_object(chat_id, message_id, as_reaction_type(reaction), + is_big, update_recent_reactions)); + } else if (op == "rmr") { + ChatId chat_id; + MessageId message_id; + string reaction; + get_args(args, chat_id, message_id, reaction); + send_request(td_api::make_object(chat_id, message_id, as_reaction_type(reaction))); } else if (op == "gmars") { ChatId chat_id; MessageId message_id; @@ -2224,8 +2272,8 @@ class CliClient final : public Actor { string offset; string limit; get_args(args, chat_id, message_id, reaction, offset, limit); - send_request(td_api::make_object(chat_id, message_id, reaction, offset, - as_limit(limit))); + send_request(td_api::make_object( + chat_id, message_id, as_reaction_type(reaction), offset, as_limit(limit))); } else if (op == "gmpf") { ChatId chat_id; MessageId message_id; @@ -3418,7 +3466,7 @@ class CliClient final : public Actor { send_request(td_api::make_object(args)); } else if (op == "gte") { send_request(td_api::make_object(args)); - } else if (op == "gtes") { + } else if (op == "gtee") { execute(td_api::make_object(args)); } else if (op == "pm") { send_request( @@ -3568,7 +3616,7 @@ class CliClient final : public Actor { UserId user_id; string url; get_args(args, user_id, url); - send_request(td_api::make_object(user_id, url, get_theme_parameters())); + send_request(td_api::make_object(user_id, url, get_theme_parameters(), "android")); } else if (op == "swad") { UserId user_id; string button_text; @@ -3581,7 +3629,7 @@ class CliClient final : public Actor { string url; MessageId reply_to_message_id; get_args(args, chat_id, bot_user_id, url, reply_to_message_id); - send_request(td_api::make_object(chat_id, bot_user_id, url, get_theme_parameters(), + send_request(td_api::make_object(chat_id, bot_user_id, url, get_theme_parameters(), "android", reply_to_message_id)); } else if (op == "cwa") { int64 launch_id; @@ -4459,6 +4507,22 @@ class CliClient final : public Actor { send_request(td_api::make_object("\n" + args + "\n" + args + "\n")); } else if (op == "sun") { send_request(td_api::make_object(args)); + } else if (op == "sese") { + send_request(td_api::make_object(nullptr, 0)); + } else if (op == "ses") { + int64 custom_emoji_id; + int32 until_date; + get_args(args, custom_emoji_id, until_date); + send_request(td_api::make_object( + td_api::make_object(custom_emoji_id), until_date)); + } else if (op == "gtes") { + send_request(td_api::make_object()); + } else if (op == "gdes") { + send_request(td_api::make_object()); + } else if (op == "gres") { + send_request(td_api::make_object()); + } else if (op == "cres") { + send_request(td_api::make_object()); } else if (op == "ccun") { ChatId chat_id; string username; @@ -4507,7 +4571,15 @@ class CliClient final : public Actor { ChatId chat_id; string available_reactions; get_args(args, chat_id, available_reactions); - send_request(td_api::make_object(chat_id, autosplit_str(available_reactions))); + td_api::object_ptr chat_available_reactions; + if (available_reactions == "all") { + chat_available_reactions = td_api::make_object(); + } else if (!available_reactions.empty()) { + auto reactions = transform(autosplit_str(available_reactions), as_reaction_type); + chat_available_reactions = td_api::make_object(std::move(reactions)); + } + send_request( + td_api::make_object(chat_id, std::move(chat_available_reactions))); } else if (op == "scd") { ChatId chat_id; string description; @@ -4732,6 +4804,13 @@ class CliClient final : public Actor { get_args(args, chat_id, file_id, reason, text); send_request( td_api::make_object(chat_id, file_id, get_chat_report_reason(reason), text)); + } else if (op == "reportmr") { + ChatId chat_id; + MessageId message_id; + string sender_id; + get_args(args, chat_id, message_id, sender_id); + send_request( + td_api::make_object(chat_id, message_id, as_message_sender(sender_id))); } else if (op == "gcst") { ChatId chat_id; bool is_dark; @@ -5181,8 +5260,7 @@ void main(int argc, char **argv) { } { - ConcurrentScheduler scheduler; - scheduler.init(3); + ConcurrentScheduler scheduler(3, 0); class CreateClient final : public Actor { public: diff --git a/td/telegram/files/FileDb.cpp b/td/telegram/files/FileDb.cpp index efcc48b8d..d763a97bf 100644 --- a/td/telegram/files/FileDb.cpp +++ b/td/telegram/files/FileDb.cpp @@ -101,6 +101,7 @@ class FileDb final : public FileDbInterface { pmc.commit_transaction().ensure(); } + void store_file_data(FileDbId id, const string &file_data, const string &remote_key, const string &local_key, const string &generate_key) { auto &pmc = file_pmc(); @@ -125,6 +126,7 @@ class FileDb final : public FileDbInterface { pmc.commit_transaction().ensure(); } + void store_file_data_ref(FileDbId id, FileDbId new_id) { auto &pmc = file_pmc(); pmc.begin_write_transaction().ensure(); @@ -202,6 +204,7 @@ class FileDb final : public FileDbInterface { } send_closure(file_db_actor_, &FileDbActor::clear_file_data, id, remote_key, local_key, generate_key); } + void set_file_data(FileDbId id, const FileData &file_data, bool new_remote, bool new_local, bool new_generate) final { string remote_key; if (file_data.remote_.type() == RemoteFileLocation::Type::Full && new_remote) { diff --git a/td/telegram/files/FileGcWorker.cpp b/td/telegram/files/FileGcWorker.cpp index 0156c4d75..cefdeba4b 100644 --- a/td/telegram/files/FileGcWorker.cpp +++ b/td/telegram/files/FileGcWorker.cpp @@ -30,7 +30,7 @@ int VERBOSITY_NAME(file_gc) = VERBOSITY_NAME(INFO); void FileGcWorker::run_gc(const FileGcParameters ¶meters, std::vector files, Promise promise) { auto begin_time = Time::now(); - VLOG(file_gc) << "Start files gc with " << parameters; + VLOG(file_gc) << "Start files GC with " << parameters; // quite stupid implementations // needs a lot of memory // may write something more clever, but i will need at least 2 passes over the files @@ -88,7 +88,7 @@ void FileGcWorker::run_gc(const FileGcParameters ¶meters, std::vectorfile_manager(), &FileManager::on_file_unlink, FullLocalFileLocation(info.file_type, info.path, info.mtime_nsec)); }; @@ -117,7 +117,7 @@ void FileGcWorker::run_gc(const FileGcParameters ¶meters, std::vector(info.mtime_nsec) * 1e-9 > now - parameters.immunity_delay_) { - // new files are immune to gc + // new files are immune to GC time_immunity_ignored_cnt++; new_stats.add_copy(info); return true; @@ -177,7 +177,7 @@ void FileGcWorker::run_gc(const FileGcParameters ¶meters, std::vector 1.0) { + LOG(WARNING) << "Finish file GC: " << tag("time", end_time - begin_time) << tag("total", file_cnt) + << tag("removed", remove_by_atime_cnt + remove_by_count_cnt + remove_by_size_cnt) + << tag("total_size", format::as_size(total_size)) + << tag("total_removed_size", format::as_size(total_removed_size)); + } promise.set_value({std::move(new_stats), std::move(removed_stats)}); } diff --git a/td/telegram/files/FileGenerateManager.cpp b/td/telegram/files/FileGenerateManager.cpp index 00f7e1507..b823f74c3 100644 --- a/td/telegram/files/FileGenerateManager.cpp +++ b/td/telegram/files/FileGenerateManager.cpp @@ -258,7 +258,7 @@ class WebFileDownloadGenerateActor final : public FileGenerateActor { } void hangup_shared() final { - on_error(Status::Error(1, "Canceled")); + on_error(Status::Error(-1, "Canceled")); } }; @@ -328,7 +328,7 @@ class FileExternalGenerateActor final : public FileGenerateActor { static_cast(query_id_), generate_location_.original_path_, path_, generate_location_.conversion_)); } void hangup() final { - check_status(Status::Error(1, "Canceled")); + check_status(Status::Error(-1, "Canceled")); } Status do_file_generate_write_part(int64 offset, const string &data) { @@ -365,7 +365,7 @@ class FileExternalGenerateActor final : public FileGenerateActor { void check_status(Status status, Promise<> promise = Promise<>()) { if (promise) { - if (status.is_ok() || status.code() == 1) { + if (status.is_ok() || status.code() == -1) { promise.set_value(Unit()); } else { promise.set_error(Status::Error(400, status.message())); diff --git a/td/telegram/files/FileLoadManager.cpp b/td/telegram/files/FileLoadManager.cpp index e7656d2d1..9549cba3f 100644 --- a/td/telegram/files/FileLoadManager.cpp +++ b/td/telegram/files/FileLoadManager.cpp @@ -156,7 +156,7 @@ void FileLoadManager::cancel(QueryId id) { if (it == query_id_to_node_id_.end()) { return; } - on_error_impl(it->second, Status::Error(1, "Canceled")); + on_error_impl(it->second, Status::Error(-1, "Canceled")); } void FileLoadManager::update_local_file_location(QueryId id, const LocalFileLocation &local) { if (stop_flag_) { @@ -298,7 +298,7 @@ void FileLoadManager::on_error_impl(NodeId node_id, Status status) { void FileLoadManager::hangup_shared() { auto node_id = get_link_token(); - on_error_impl(node_id, Status::Error(1, "Canceled")); + on_error_impl(node_id, Status::Error(-1, "Canceled")); } void FileLoadManager::loop() { diff --git a/td/telegram/files/FileLoader.cpp b/td/telegram/files/FileLoader.cpp index 0c7c22dc3..1ac07c10b 100644 --- a/td/telegram/files/FileLoader.cpp +++ b/td/telegram/files/FileLoader.cpp @@ -153,7 +153,7 @@ void FileLoader::loop() { } auto status = do_loop(); if (status.is_error()) { - if (status.code() == 1) { + if (status.code() == -1) { return; } on_error(std::move(status)); diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index cff4be731..972a12077 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -2194,7 +2194,7 @@ void FileManager::download(FileId file_id, std::shared_ptr cal if (!node) { LOG(INFO) << "File " << file_id << " not found"; if (callback) { - callback->on_download_error(file_id, Status::Error("File not found")); + callback->on_download_error(file_id, Status::Error(400, "File not found")); } return; } @@ -2221,7 +2221,7 @@ void FileManager::download(FileId file_id, std::shared_ptr cal if (!file_view.can_download_from_server() && !file_view.can_generate()) { LOG(INFO) << "File " << file_id << " can't be downloaded"; if (callback) { - callback->on_download_error(file_id, Status::Error("Can't download or generate file")); + callback->on_download_error(file_id, Status::Error(400, "Can't download or generate file")); } return; } @@ -2440,7 +2440,7 @@ class FileManager::ForceUploadActor final : public Actor { if (callback_.empty()) { return; } - send_closure(std::move(callback_), &ForceUploadActor::on_upload_error, Status::Error("Canceled")); + send_closure(std::move(callback_), &ForceUploadActor::on_upload_error, Status::Error(200, "Canceled")); } private: @@ -2518,7 +2518,7 @@ class FileManager::ForceUploadActor final : public Actor { void tear_down() final { if (callback_) { - callback_->on_upload_error(file_id_, Status::Error("Canceled")); + callback_->on_upload_error(file_id_, Status::Error(200, "Canceled")); } } }; @@ -2537,7 +2537,7 @@ void FileManager::resume_upload(FileId file_id, vector bad_parts, std::shar if (!node) { LOG(INFO) << "File " << file_id << " not found"; if (callback) { - callback->on_upload_error(file_id, Status::Error("File not found")); + callback->on_upload_error(file_id, Status::Error(400, "File not found")); } return; } @@ -2546,7 +2546,7 @@ void FileManager::resume_upload(FileId file_id, vector bad_parts, std::shar if (node->last_successful_force_reupload_time_ >= Time::now() - 60) { LOG(INFO) << "Recently reuploaded file " << file_id << ", do not try again"; if (callback) { - callback->on_upload_error(file_id, Status::Error("Failed to reupload file")); + callback->on_upload_error(file_id, Status::Error(400, "Failed to reupload file")); } return; } @@ -2586,8 +2586,8 @@ void FileManager::resume_upload(FileId file_id, vector bad_parts, std::shar if (!file_view.has_local_location() && !file_view.has_generate_location() && !file_view.has_alive_remote_location()) { LOG(INFO) << "File " << file_id << " can't be uploaded"; if (callback) { - callback->on_upload_error(file_id, - Status::Error("Need full local (or generate, or inactive remote) location for upload")); + callback->on_upload_error( + file_id, Status::Error(400, "Need full local (or generate, or inactive remote) location for upload")); } return; } @@ -2595,7 +2595,7 @@ void FileManager::resume_upload(FileId file_id, vector bad_parts, std::shar (!file_view.has_local_location() && file_view.can_download_from_server())) { // TODO if (callback) { - callback->on_upload_error(file_id, Status::Error("Failed to upload thumbnail without local location")); + callback->on_upload_error(file_id, Status::Error(400, "Failed to upload thumbnail without local location")); } return; } @@ -2914,7 +2914,7 @@ Result FileManager::from_persistent_id(CSlice persistent_id, FileType fi } auto binary = r_binary.move_as_ok(); if (binary.empty()) { - return Status::Error(400, "Remote file identifier can't be empty"); + return Status::Error(400, "Remote file identifier must be non-empty"); } if (binary.back() == FileNode::PERSISTENT_ID_VERSION_OLD) { return from_persistent_id_v2(binary, file_type); @@ -3146,7 +3146,7 @@ Result FileManager::get_input_thumbnail_file_id(const tl_object_ptr FileManager::get_input_file_id(FileType type, const tl_object_ptr &file, DialogId owner_dialog_id, bool allow_zero, bool is_encrypted, - bool get_by_hash, bool is_secure) { + bool get_by_hash, bool is_secure, bool force_reuse) { if (file == nullptr) { if (allow_zero) { return FileId(); @@ -3177,8 +3177,13 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr auto file_id = file_hash_to_file_id_.get(hash); if (file_id.is_valid()) { auto file_view = get_file_view(file_id); - if (!file_view.empty() && file_view.has_remote_location() && !file_view.remote_location().is_web()) { - return file_id; + if (!file_view.empty()) { + if (force_reuse) { + return file_id; + } + if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { + return file_id; + } } } } @@ -3850,9 +3855,13 @@ void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_act return; } - if (status.code() != 1 && !G()->close_flag()) { - LOG(WARNING) << "Failed to " << type << " file " << node->main_file_id_ << " of type " << FileView(node).get_type() - << ": " << status; + if (G()->close_flag() && status.code() < 400) { + status = Global::request_aborted_error(); + } else { + if (status.code() != -1) { + LOG(WARNING) << "Failed to " << type << " file " << node->main_file_id_ << " of type " + << FileView(node).get_type() << ": " << status; + } if (status.code() == 0) { // Remove partial locations if (node->local_.type() == LocalFileLocation::Type::Partial && @@ -3868,8 +3877,8 @@ void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_act } } node->delete_partial_remote_location(); - status = Status::Error(400, status.message()); } + status = Status::Error(400, status.message()); } // Stop everything on error diff --git a/td/telegram/files/FileManager.h b/td/telegram/files/FileManager.h index c3b2d4455..f29c61419 100644 --- a/td/telegram/files/FileManager.h +++ b/td/telegram/files/FileManager.h @@ -487,7 +487,8 @@ class FileManager final : public FileLoadManager::Callback { DialogId owner_dialog_id, bool is_encrypted) TD_WARN_UNUSED_RESULT; Result get_input_file_id(FileType type, const tl_object_ptr &file, DialogId owner_dialog_id, bool allow_zero, bool is_encrypted, - bool get_by_hash = false, bool is_secure = false) TD_WARN_UNUSED_RESULT; + bool get_by_hash = false, bool is_secure = false, + bool force_reuse = false) TD_WARN_UNUSED_RESULT; Result get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height, int32 scale, DialogId owner_dialog_id) TD_WARN_UNUSED_RESULT; diff --git a/td/telegram/files/FileUploader.cpp b/td/telegram/files/FileUploader.cpp index ff6360c29..fbda0d6bd 100644 --- a/td/telegram/files/FileUploader.cpp +++ b/td/telegram/files/FileUploader.cpp @@ -239,7 +239,7 @@ Status FileUploader::generate_iv_map() { Status FileUploader::before_start_parts() { auto status = acquire_fd(); if (status.is_error() && !local_is_ready_) { - return Status::Error(1, "Can't open temporary file"); + return Status::Error(-1, "Can't open temporary file"); } return status; } diff --git a/td/telegram/files/PartsManager.cpp b/td/telegram/files/PartsManager.cpp index 3e6a7c8af..ffe9890da 100644 --- a/td/telegram/files/PartsManager.cpp +++ b/td/telegram/files/PartsManager.cpp @@ -281,7 +281,7 @@ Result PartsManager::start_part() { update_first_empty_part(); auto part_i = first_streaming_empty_part_; if (known_prefix_flag_ && part_i >= static_cast(known_prefix_size_ / part_size_)) { - return Status::Error(1, "Wait for prefix to be known"); + return Status::Error(-1, "Wait for prefix to be known"); } if (part_i == part_count_) { if (unknown_size_flag_) { diff --git a/td/telegram/misc.h b/td/telegram/misc.h index f9b6de664..57c62d165 100644 --- a/td/telegram/misc.h +++ b/td/telegram/misc.h @@ -31,7 +31,7 @@ string strip_empty_characters(string str, size_t max_length, bool strip_rtlo = f // checks if string is empty after strip_empty_characters bool is_empty_string(const string &str) TD_WARN_UNUSED_RESULT; -// calculates hash of list of uint32 +// calculates hash of list of uint64 int64 get_vector_hash(const vector &numbers) TD_WARN_UNUSED_RESULT; // returns emoji corresponding to the specified number diff --git a/tdactor/example/example.cpp b/tdactor/example/example.cpp index ef85aad6b..8f182d835 100644 --- a/tdactor/example/example.cpp +++ b/tdactor/example/example.cpp @@ -36,8 +36,7 @@ class MainActor final : public td::Actor { }; int main() { - td::ConcurrentScheduler scheduler; - scheduler.init(4 /*threads_count*/); + td::ConcurrentScheduler scheduler(4 /*thread_count*/, 0); scheduler.start(); { auto guard = scheduler.get_main_guard(); diff --git a/tdactor/td/actor/ConcurrentScheduler.cpp b/tdactor/td/actor/ConcurrentScheduler.cpp index f944eb459..c904295ad 100644 --- a/tdactor/td/actor/ConcurrentScheduler.cpp +++ b/tdactor/td/actor/ConcurrentScheduler.cpp @@ -15,18 +15,19 @@ namespace td { -void ConcurrentScheduler::init(int32 threads_n) { +ConcurrentScheduler::ConcurrentScheduler(int32 additional_thread_count, uint64 thread_affinity_mask) { #if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED - threads_n = 0; + additional_thread_count = 0; #endif - threads_n++; - std::vector>> outbound(threads_n); + additional_thread_count++; + std::vector>> outbound(additional_thread_count); #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED - for (int32 i = 0; i < threads_n; i++) { + for (int32 i = 0; i < additional_thread_count; i++) { auto queue = std::make_shared>(); queue->init(); outbound[i] = queue; } + thread_affinity_mask_ = thread_affinity_mask; #endif // +1 for extra scheduler for IOCP and send_closure from unrelated threads @@ -37,13 +38,13 @@ void ConcurrentScheduler::init(int32 threads_n) { extra_scheduler_ = 0; #endif - schedulers_.resize(threads_n + extra_scheduler_); - for (int32 i = 0; i < threads_n + extra_scheduler_; i++) { + schedulers_.resize(additional_thread_count + extra_scheduler_); + for (int32 i = 0; i < additional_thread_count + extra_scheduler_; i++) { auto &sched = schedulers_[i]; sched = make_unique(); #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED - if (i >= threads_n) { + if (i >= additional_thread_count) { auto queue = std::make_shared>(); queue->init(); outbound.push_back(std::move(queue)); @@ -75,9 +76,14 @@ void ConcurrentScheduler::start() { #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED for (size_t i = 1; i + extra_scheduler_ < schedulers_.size(); i++) { auto &sched = schedulers_[i]; - threads_.push_back(td::thread([&] { + threads_.push_back(td::thread([&, thread_affinity_mask = thread_affinity_mask_] { #if TD_PORT_WINDOWS detail::Iocp::Guard iocp_guard(iocp_.get()); +#endif +#if TD_HAVE_THREAD_AFFINITY + if (thread_affinity_mask != 0) { + thread::set_affinity_mask(this_thread::get_id(), thread_affinity_mask).ignore(); + } #endif while (!is_finished()) { sched->run(Timestamp::in(10)); diff --git a/tdactor/td/actor/ConcurrentScheduler.h b/tdactor/td/actor/ConcurrentScheduler.h index 0cf8c9a2c..490deb218 100644 --- a/tdactor/td/actor/ConcurrentScheduler.h +++ b/tdactor/td/actor/ConcurrentScheduler.h @@ -26,7 +26,7 @@ namespace td { class ConcurrentScheduler final : private Scheduler::Callback { public: - void init(int32 threads_n); + explicit ConcurrentScheduler(int32 additional_thread_count, uint64 thread_affinity_mask = 0); void finish_async() { schedulers_[0]->finish(); @@ -90,6 +90,7 @@ class ConcurrentScheduler final : private Scheduler::Callback { std::atomic is_finished_{false}; #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED vector threads_; + uint64 thread_affinity_mask_ = 0; #endif #if TD_PORT_WINDOWS unique_ptr iocp_; diff --git a/tdactor/test/actors_bugs.cpp b/tdactor/test/actors_bugs.cpp index 9f3841430..0720f0ed6 100644 --- a/tdactor/test/actors_bugs.cpp +++ b/tdactor/test/actors_bugs.cpp @@ -14,9 +14,7 @@ #include "td/utils/tests.h" TEST(MultiTimeout, bug) { - td::ConcurrentScheduler sched; - int threads_n = 0; - sched.init(threads_n); + td::ConcurrentScheduler sched(0, 0); sched.start(); td::unique_ptr multi_timeout; @@ -91,9 +89,7 @@ class TimeoutManager final : public td::Actor { td::int32 TimeoutManager::count; TEST(MultiTimeout, Destroy) { - td::ConcurrentScheduler sched; - int threads_n = 0; - sched.init(threads_n); + td::ConcurrentScheduler sched(0, 0); auto timeout_manager = sched.create_actor_unsafe(0, "TimeoutManager"); TimeoutManager *manager = timeout_manager.get().get_actor_unsafe(); diff --git a/tdactor/test/actors_main.cpp b/tdactor/test/actors_main.cpp index 4bb9ac54b..628b74a94 100644 --- a/tdactor/test/actors_main.cpp +++ b/tdactor/test/actors_main.cpp @@ -394,9 +394,8 @@ class SendToDead final : public td::Actor { TEST(Actors, send_to_dead) { //TODO: fix CHECK(storage_count_.load() == 0) return; - td::ConcurrentScheduler sched; int threads_n = 5; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); sched.create_actor_unsafe(0, "SendToDead").release(); sched.start(); @@ -407,9 +406,8 @@ TEST(Actors, send_to_dead) { } TEST(Actors, main_simple) { - td::ConcurrentScheduler sched; int threads_n = 3; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); sched.create_actor_unsafe(threads_n > 1 ? 1 : 0, "simple", threads_n).release(); sched.start(); @@ -420,9 +418,8 @@ TEST(Actors, main_simple) { } TEST(Actors, main) { - td::ConcurrentScheduler sched; int threads_n = 9; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); sched.create_actor_unsafe(threads_n > 1 ? 1 : 0, "MainQuery", threads_n).release(); sched.start(); @@ -446,9 +443,8 @@ class DoAfterStop final : public td::Actor { }; TEST(Actors, do_after_stop) { - td::ConcurrentScheduler sched; int threads_n = 0; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); sched.create_actor_unsafe(0, "DoAfterStop").release(); sched.start(); @@ -492,9 +488,8 @@ static void check_context() { } TEST(Actors, context_during_destruction) { - td::ConcurrentScheduler sched; int threads_n = 0; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); { auto guard = sched.get_main_guard(); diff --git a/tdactor/test/actors_simple.cpp b/tdactor/test/actors_simple.cpp index 58f14ac48..78d32d543 100644 --- a/tdactor/test/actors_simple.cpp +++ b/tdactor/test/actors_simple.cpp @@ -256,8 +256,7 @@ TEST(Actors, simple_migrate) { sb.clear(); sb2.clear(); - td::ConcurrentScheduler scheduler; - scheduler.init(2); + td::ConcurrentScheduler scheduler(2, 0); auto pong = scheduler.create_actor_unsafe(2, "Pong").release(); scheduler.create_actor_unsafe(1, "Ping", pong).release(); scheduler.start(); @@ -300,8 +299,7 @@ class OpenClose final : public td::Actor { }; TEST(Actors, open_close) { - td::ConcurrentScheduler scheduler; - scheduler.init(2); + td::ConcurrentScheduler scheduler(2, 0); int cnt = 10000; // TODO(perf) optimize scheduler.create_actor_unsafe(1, "A", cnt).release(); scheduler.create_actor_unsafe(2, "B", cnt).release(); @@ -425,8 +423,7 @@ class LinkTokenMasterActor final : public td::Actor { }; TEST(Actors, link_token) { - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); auto cnt = 100000; scheduler.create_actor_unsafe(0, "A", cnt).release(); scheduler.start(); @@ -485,8 +482,7 @@ class LaterMasterActor final : public td::Actor { TEST(Actors, later) { sb.clear(); - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); scheduler.create_actor_unsafe(0, "A").release(); scheduler.start(); while (scheduler.run_main(10)) { @@ -524,8 +520,7 @@ class MultiPromise1 final : public td::Actor { }; TEST(Actors, MultiPromise) { - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); scheduler.create_actor_unsafe(0, "A").release(); scheduler.start(); while (scheduler.run_main(10)) { @@ -546,8 +541,7 @@ class FastPromise final : public td::Actor { }; TEST(Actors, FastPromise) { - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); scheduler.create_actor_unsafe(0, "A").release(); scheduler.start(); while (scheduler.run_main(10)) { @@ -566,8 +560,7 @@ class StopInTeardown final : public td::Actor { }; TEST(Actors, stop_in_teardown) { - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); scheduler.create_actor_unsafe(0, "A").release(); scheduler.start(); while (scheduler.run_main(10)) { @@ -601,8 +594,7 @@ class AlwaysWaitForMailbox final : public td::Actor { }; TEST(Actors, always_wait_for_mailbox) { - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); scheduler.create_actor_unsafe(0, "A").release(); scheduler.start(); while (scheduler.run_main(10)) { @@ -612,8 +604,7 @@ TEST(Actors, always_wait_for_mailbox) { #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED TEST(Actors, send_from_other_threads) { - td::ConcurrentScheduler scheduler; - scheduler.init(1); + td::ConcurrentScheduler scheduler(1, 0); int thread_n = 10; class Listener final : public td::Actor { public: @@ -680,8 +671,7 @@ class MultiPromiseSendClosureLaterTest final : public td::Actor { }; TEST(Actors, MultiPromiseSendClosureLater) { - td::ConcurrentScheduler scheduler; - scheduler.init(0); + td::ConcurrentScheduler scheduler(0, 0); scheduler.create_actor_unsafe(0, "MultiPromiseSendClosureLaterTest").release(); scheduler.start(); while (scheduler.run_main(1)) { diff --git a/tdactor/test/actors_workers.cpp b/tdactor/test/actors_workers.cpp index 748edd4e8..bac42e3fd 100644 --- a/tdactor/test/actors_workers.cpp +++ b/tdactor/test/actors_workers.cpp @@ -106,8 +106,7 @@ class Manager final : public td::Actor { }; static void test_workers(int threads_n, int workers_n, int queries_n, int query_size) { - td::ConcurrentScheduler sched; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); td::vector> workers; for (int i = 0; i < workers_n; i++) { diff --git a/tdnet/td/net/SslStream.cpp b/tdnet/td/net/SslStream.cpp index 2c88632f3..2888bd1f5 100644 --- a/tdnet/td/net/SslStream.cpp +++ b/tdnet/td/net/SslStream.cpp @@ -151,6 +151,7 @@ using SslCtx = std::shared_ptr; struct SslHandleDeleter { void operator()(SSL *ssl_handle) { + auto start_time = Time::now(); if (SSL_is_init_finished(ssl_handle)) { clear_openssl_errors("Before SSL_shutdown"); SSL_set_quiet_shutdown(ssl_handle, 1); @@ -158,6 +159,10 @@ struct SslHandleDeleter { clear_openssl_errors("After SSL_shutdown"); } SSL_free(ssl_handle); + auto elapsed_time = Time::now() - start_time; + if (elapsed_time >= 0.001) { + LOG(ERROR) << "SSL_free took " << elapsed_time << " seconds"; + } } }; @@ -289,7 +294,13 @@ Result create_ssl_ctx(CSlice cert_file, SslStream::VerifyPeer verify_pee return get_default_unverified_ssl_ctx(); } } - return do_create_ssl_ctx(cert_file, verify_peer); + auto start_time = Time::now(); + auto result = do_create_ssl_ctx(cert_file, verify_peer); + auto elapsed_time = Time::now() - start_time; + if (elapsed_time >= 0.01) { + LOG(ERROR) << "do_create_ssl_ctx took " << elapsed_time << " seconds"; + } + return result; } } // namespace @@ -372,7 +383,13 @@ class SslStreamImpl { Result write(Slice slice) { clear_openssl_errors("Before SslFd::write"); + auto start_time = Time::now(); auto size = SSL_write(ssl_handle_.get(), slice.data(), static_cast(slice.size())); + auto elapsed_time = Time::now() - start_time; + if (elapsed_time >= 0.001) { + LOG(ERROR) << "SSL_write of size " << slice.size() << " took " << elapsed_time << " seconds and returned " << size + << ' ' << SSL_get_error(ssl_handle_.get(), size); + } if (size <= 0) { return process_ssl_error(size); } @@ -381,7 +398,13 @@ class SslStreamImpl { Result read(MutableSlice slice) { clear_openssl_errors("Before SslFd::read"); + auto start_time = Time::now(); auto size = SSL_read(ssl_handle_.get(), slice.data(), static_cast(slice.size())); + auto elapsed_time = Time::now() - start_time; + if (elapsed_time >= 0.001) { + LOG(ERROR) << "SSL_read took " << elapsed_time << " seconds and returned " << size << ' ' + << SSL_get_error(ssl_handle_.get(), size); + } if (size <= 0) { return process_ssl_error(size); } diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index b273157e0..3c8cdafc3 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -399,7 +399,8 @@ if (ANDROID) endif() if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") - target_link_libraries(tdutils PUBLIC /usr/pkg/gcc5/i486--netbsdelf/lib/libatomic.so) + target_link_directories(tdutils PUBLIC /usr/pkg/gcc12/x86_64--netbsd/lib /usr/pkg/gcc12/i486--netbsdelf/lib) + target_link_libraries(tdutils PUBLIC atomic) endif() install(TARGETS tdutils EXPORT TdTargets diff --git a/tdutils/td/utils/FileLog.cpp b/tdutils/td/utils/FileLog.cpp index 18b4cc1a2..16d294233 100644 --- a/tdutils/td/utils/FileLog.cpp +++ b/tdutils/td/utils/FileLog.cpp @@ -18,7 +18,7 @@ namespace td { Status FileLog::init(string path, int64 rotate_threshold, bool redirect_stderr) { if (path.empty()) { - return Status::Error("Log file path can't be empty"); + return Status::Error("Log file path must be non-empty"); } if (path == path_) { set_rotate_threshold(rotate_threshold); diff --git a/tdutils/td/utils/HttpUrl.cpp b/tdutils/td/utils/HttpUrl.cpp index ced0a2860..e1d46964b 100644 --- a/tdutils/td/utils/HttpUrl.cpp +++ b/tdutils/td/utils/HttpUrl.cpp @@ -197,7 +197,7 @@ HttpUrlQuery parse_url_query(Slice query) { HttpUrlQuery result; result.path_ = full_split(url_decode(query.substr(0, path_size), false), '/'); - if (!result.path_.empty() && result.path_.back().empty()) { + while (!result.path_.empty() && result.path_.back().empty()) { result.path_.pop_back(); } diff --git a/tdutils/td/utils/Time.cpp b/tdutils/td/utils/Time.cpp index 78d222be6..93ed7d1ac 100644 --- a/tdutils/td/utils/Time.cpp +++ b/tdutils/td/utils/Time.cpp @@ -6,6 +6,8 @@ // #include "td/utils/Time.h" +#include "td/utils/port/Clocks.h" + #include #include diff --git a/tdutils/td/utils/Time.h b/tdutils/td/utils/Time.h index c0653e978..bf1dce6e1 100644 --- a/tdutils/td/utils/Time.h +++ b/tdutils/td/utils/Time.h @@ -7,7 +7,6 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/port/Clocks.h" namespace td { @@ -58,9 +57,6 @@ class Timestamp { static Timestamp at(double timeout) { return Timestamp{timeout}; } - static Timestamp at_unix(double timeout) { - return Timestamp{timeout - Clocks::system() + Time::now()}; - } static Timestamp in(double timeout, Timestamp now = now_cached()) { return Timestamp{now.at() + timeout}; @@ -80,9 +76,6 @@ class Timestamp { double at() const { return at_; } - double at_unix() const { - return at_ + Clocks::system() - Time::now(); - } double in() const { return at_ - Time::now_cached(); @@ -110,14 +103,4 @@ inline bool operator<(const Timestamp &a, const Timestamp &b) { return a.at() < b.at(); } -template -void store(const Timestamp ×tamp, StorerT &storer) { - storer.store_binary(timestamp.at() - Time::now() + Clocks::system()); -} - -template -void parse(Timestamp ×tamp, ParserT &parser) { - timestamp = Timestamp::in(parser.fetch_double() - Clocks::system()); -} - } // namespace td diff --git a/tdutils/td/utils/buffer.h b/tdutils/td/utils/buffer.h index ec81c85ad..798ceb9d5 100644 --- a/tdutils/td/utils/buffer.h +++ b/tdutils/td/utils/buffer.h @@ -678,14 +678,10 @@ class ChainBufferReader { class ChainBufferWriter { public: - ChainBufferWriter() { - init(); - } - - void init(size_t size = 0) { - writer_ = BufferWriter(size); - tail_ = ChainBufferNodeAllocator::create(writer_.as_buffer_slice(), true); - head_ = ChainBufferNodeAllocator::clone(tail_); + ChainBufferWriter() + : writer_(0) + , tail_(ChainBufferNodeAllocator::create(writer_.as_buffer_slice(), true)) + , head_(ChainBufferNodeAllocator::clone(tail_)) { } MutableSlice prepare_append(size_t hint = 0) { @@ -770,9 +766,9 @@ class ChainBufferWriter { return !tail_; } - ChainBufferNodeReaderPtr head_; - ChainBufferNodeWriterPtr tail_; BufferWriter writer_; + ChainBufferNodeWriterPtr tail_; + ChainBufferNodeReaderPtr head_; }; class BufferBuilder { diff --git a/tdutils/td/utils/crypto.cpp b/tdutils/td/utils/crypto.cpp index fd077946b..cca3215f4 100644 --- a/tdutils/td/utils/crypto.cpp +++ b/tdutils/td/utils/crypto.cpp @@ -328,24 +328,64 @@ class Evp { } void init_encrypt_ecb(Slice key) { - init(Type::Ecb, true, EVP_aes_256_ecb(), key); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-ECB"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_ecb(); +#endif + init(true, evp_cipher, key); } void init_decrypt_ecb(Slice key) { - init(Type::Ecb, false, EVP_aes_256_ecb(), key); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-ECB"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_ecb(); +#endif + init(false, evp_cipher, key); } void init_encrypt_cbc(Slice key) { - init(Type::Cbc, true, EVP_aes_256_cbc(), key); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-CBC"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_cbc(); +#endif + init(true, evp_cipher, key); } void init_decrypt_cbc(Slice key) { - init(Type::Cbc, false, EVP_aes_256_cbc(), key); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-CBC"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_cbc(); +#endif + init(false, evp_cipher, key); } #if OPENSSL_VERSION_NUMBER >= 0x10100000L void init_encrypt_ctr(Slice key) { - init(Type::Ctr, true, EVP_aes_256_ctr(), key); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-CTR"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_ctr(); +#endif + init(true, evp_cipher, key); } #endif @@ -355,8 +395,6 @@ class Evp { } void encrypt(const uint8 *src, uint8 *dst, int size) { - // CHECK(type_ != Type::Empty && is_encrypt_); - // CHECK(size % AES_BLOCK_SIZE == 0); int len; int res = EVP_EncryptUpdate(ctx_, dst, &len, src, size); LOG_IF(FATAL, res != 1); @@ -364,7 +402,6 @@ class Evp { } void decrypt(const uint8 *src, uint8 *dst, int size) { - // CHECK(type_ != Type::Empty && !is_encrypt_); CHECK(size % AES_BLOCK_SIZE == 0); int len; int res = EVP_DecryptUpdate(ctx_, dst, &len, src, size); @@ -374,17 +411,23 @@ class Evp { private: EVP_CIPHER_CTX *ctx_{nullptr}; - enum class Type : int8 { Empty, Ecb, Cbc, Ctr }; - // Type type_{Type::Empty}; - // bool is_encrypt_ = false; - void init(Type type, bool is_encrypt, const EVP_CIPHER *cipher, Slice key) { - // type_ = type; - // is_encrypt_ = is_encrypt; - int res = EVP_CipherInit_ex(ctx_, cipher, nullptr, key.ubegin(), nullptr, is_encrypt ? 1 : 0); + void init(bool is_encrypt, const EVP_CIPHER *evp_cipher, Slice key) { + int res = EVP_CipherInit_ex(ctx_, evp_cipher, nullptr, key.ubegin(), nullptr, is_encrypt ? 1 : 0); LOG_IF(FATAL, res != 1); EVP_CIPHER_CTX_set_padding(ctx_, 0); } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static void init_thread_local_evp_cipher(const EVP_CIPHER *&evp_cipher, const char *algorithm) { + evp_cipher = EVP_CIPHER_fetch(nullptr, algorithm, nullptr); + LOG_IF(FATAL, evp_cipher == nullptr); + detail::add_thread_local_destructor(create_destructor([&evp_cipher]() mutable { + EVP_CIPHER_free(const_cast(evp_cipher)); + evp_cipher = nullptr; + })); + } +#endif }; struct AesState::Impl { @@ -678,21 +721,41 @@ void AesCtrState::decrypt(Slice from, MutableSlice to) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) static void make_digest(Slice data, MutableSlice output, const EVP_MD *evp_md) { - EVP_MD_CTX *ctx = EVP_MD_CTX_new(); - LOG_IF(FATAL, ctx == nullptr); + static TD_THREAD_LOCAL EVP_MD_CTX *ctx; + if (unlikely(ctx == nullptr)) { + ctx = EVP_MD_CTX_new(); + LOG_IF(FATAL, ctx == nullptr); + detail::add_thread_local_destructor(create_destructor([] { + EVP_MD_CTX_free(ctx); + ctx = nullptr; + })); + } int res = EVP_DigestInit_ex(ctx, evp_md, nullptr); LOG_IF(FATAL, res != 1); res = EVP_DigestUpdate(ctx, data.ubegin(), data.size()); LOG_IF(FATAL, res != 1); res = EVP_DigestFinal_ex(ctx, output.ubegin(), nullptr); LOG_IF(FATAL, res != 1); - EVP_MD_CTX_free(ctx); + EVP_MD_CTX_reset(ctx); +} + +static void init_thread_local_evp_md(const EVP_MD *&evp_md, const char *algorithm) { + evp_md = EVP_MD_fetch(nullptr, algorithm, nullptr); + LOG_IF(FATAL, evp_md == nullptr); + detail::add_thread_local_destructor(create_destructor([&evp_md]() mutable { + EVP_MD_free(const_cast(evp_md)); + evp_md = nullptr; + })); } #endif void sha1(Slice data, unsigned char output[20]) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) - make_digest(data, MutableSlice(output, 20), EVP_sha1()); + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha1"); + } + make_digest(data, MutableSlice(output, 20), evp_md); #else auto result = SHA1(data.ubegin(), data.size(), output); CHECK(result == output); @@ -702,7 +765,11 @@ void sha1(Slice data, unsigned char output[20]) { void sha256(Slice data, MutableSlice output) { CHECK(output.size() >= 32); #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) - make_digest(data, output, EVP_sha256()); + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha256"); + } + make_digest(data, output, evp_md); #else auto result = SHA256(data.ubegin(), data.size(), output.ubegin()); CHECK(result == output.ubegin()); @@ -712,7 +779,11 @@ void sha256(Slice data, MutableSlice output) { void sha512(Slice data, MutableSlice output) { CHECK(output.size() >= 64); #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) - make_digest(data, output, EVP_sha512()); + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha512"); + } + make_digest(data, output, evp_md); #else auto result = SHA512(data.ubegin(), data.size(), output.ubegin()); CHECK(result == output.ubegin()); @@ -792,7 +863,11 @@ void Sha256State::init() { } CHECK(!is_inited_); #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) - int err = EVP_DigestInit_ex(impl_->ctx_, EVP_sha256(), nullptr); + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha256"); + } + int err = EVP_DigestInit_ex(impl_->ctx_, evp_md, nullptr); #else int err = SHA256_Init(&impl_->ctx_); #endif @@ -830,7 +905,11 @@ void Sha256State::extract(MutableSlice output, bool destroy) { void md5(Slice input, MutableSlice output) { CHECK(output.size() >= 16); #if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) - make_digest(input, output, EVP_md5()); + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "md5"); + } + make_digest(input, output, evp_md); #else auto result = MD5(input.ubegin(), input.size(), output.ubegin()); CHECK(result == output.ubegin()); diff --git a/tdutils/td/utils/port/config.h b/tdutils/td/utils/port/config.h index bd3a1593d..59fd94bf0 100644 --- a/tdutils/td/utils/port/config.h +++ b/tdutils/td/utils/port/config.h @@ -46,10 +46,10 @@ #if TD_EMSCRIPTEN #define TD_THREAD_UNSUPPORTED 1 -#elif TD_TIZEN || TD_LINUX || TD_DARWIN - #define TD_THREAD_PTHREAD 1 -#else +#elif TD_WINDOWS #define TD_THREAD_STL 1 +#else + #define TD_THREAD_PTHREAD 1 #endif #if TD_LINUX diff --git a/tdutils/td/utils/port/detail/ThreadPthread.cpp b/tdutils/td/utils/port/detail/ThreadPthread.cpp index 64ea20dc0..3fa0d1938 100644 --- a/tdutils/td/utils/port/detail/ThreadPthread.cpp +++ b/tdutils/td/utils/port/detail/ThreadPthread.cpp @@ -11,10 +11,20 @@ char disable_linker_warning_about_empty_file_thread_pthread_cpp TD_UNUSED; #if TD_THREAD_PTHREAD #include "td/utils/misc.h" +#include "td/utils/port/detail/skip_eintr.h" +#if TD_NETBSD +#include "td/utils/ScopeGuard.h" +#endif #include +#if TD_FREEBSD +#include +#endif #include #include +#if TD_FREEBSD +#include +#endif #if TD_FREEBSD || TD_OPENBSD || TD_NETBSD #include #endif @@ -94,6 +104,108 @@ int ThreadPthread::do_pthread_create(pthread_t *thread, const pthread_attr_t *at return pthread_create(thread, attr, start_routine, arg); } +#if TD_HAVE_THREAD_AFFINITY +Status ThreadPthread::set_affinity_mask(id thread_id, uint64 mask) { +#if TD_LINUX || TD_FREEBSD +#if TD_FREEBSD + cpuset_t cpuset; +#else + cpu_set_t cpuset; +#endif + CPU_ZERO(&cpuset); + for (int j = 0; j < 64 && j < CPU_SETSIZE; j++) { + if ((mask >> j) & 1) { + CPU_SET(j, &cpuset); + } + } + + auto res = skip_eintr([&] { return pthread_setaffinity_np(thread_id, sizeof(cpuset), &cpuset); }); + if (res) { + return OS_ERROR("Failed to set thread affinity mask"); + } + return Status::OK(); +#elif TD_NETBSD + cpuset_t *cpuset = cpuset_create(); + if (cpuset == nullptr) { + return OS_ERROR("Failed to create cpuset"); + } + SCOPE_EXIT { + cpuset_destroy(cpuset); + }; + for (int j = 0; j < 64; j++) { + if ((mask >> j) & 1) { + if (cpuset_set(j, cpuset) != 0) { + return OS_ERROR("Failed to set CPU identifier"); + } + } + } + + auto res = skip_eintr([&] { return pthread_setaffinity_np(thread_id, cpuset_size(cpuset), cpuset); }); + if (res) { + return OS_ERROR("Failed to set thread affinity mask"); + } + if (get_affinity_mask(thread_id) != mask) { + return Status::Error("Failed to set exact thread affinity mask"); + } + return Status::OK(); +#else + return Status::Error("Unsupported"); +#endif +} + +uint64 ThreadPthread::get_affinity_mask(id thread_id) { +#if TD_LINUX || TD_FREEBSD +#if TD_FREEBSD + cpuset_t cpuset; +#else + cpu_set_t cpuset; +#endif + CPU_ZERO(&cpuset); + auto res = skip_eintr([&] { return pthread_getaffinity_np(thread_id, sizeof(cpuset), &cpuset); }); + if (res) { + return 0; + } + + uint64 mask = 0; + for (int j = 0; j < 64 && j < CPU_SETSIZE; j++) { + if (CPU_ISSET(j, &cpuset)) { + mask |= static_cast(1) << j; + } + } + return mask; +#elif TD_NETBSD + cpuset_t *cpuset = cpuset_create(); + if (cpuset == nullptr) { + return 0; + } + SCOPE_EXIT { + cpuset_destroy(cpuset); + }; + auto res = skip_eintr([&] { return pthread_getaffinity_np(thread_id, cpuset_size(cpuset), cpuset); }); + if (res) { + return 0; + } + + uint64 mask = 0; + for (int j = 0; j < 64; j++) { + if (cpuset_isset(j, cpuset) > 0) { + mask |= static_cast(1) << j; + } + } + if (mask == 0) { + // the mask wasn't set, all CPUs are allowed + auto proc_count = sysconf(_SC_NPROCESSORS_ONLN); + for (int j = 0; j < 64 && j < proc_count; j++) { + mask |= static_cast(1) << j; + } + } + return mask; +#else + return 0; +#endif +} +#endif + namespace this_thread_pthread { ThreadPthread::id get_id() { return pthread_self(); diff --git a/tdutils/td/utils/port/detail/ThreadPthread.h b/tdutils/td/utils/port/detail/ThreadPthread.h index ff54633c6..76f6bebe1 100644 --- a/tdutils/td/utils/port/detail/ThreadPthread.h +++ b/tdutils/td/utils/port/detail/ThreadPthread.h @@ -17,13 +17,21 @@ #include "td/utils/port/detail/ThreadIdGuard.h" #include "td/utils/port/thread_local.h" #include "td/utils/Slice.h" +#include "td/utils/Status.h" #include #include #include +#if TD_OPENBSD || TD_SOLARIS +#include +#endif #include +#if TD_LINUX || TD_FREEBSD || TD_NETBSD +#define TD_HAVE_THREAD_AFFINITY 1 +#endif + namespace td { namespace detail { @@ -66,6 +74,12 @@ class ThreadPthread { static void send_real_time_signal(id thread_id, int real_time_signal_number); +#if TD_HAVE_THREAD_AFFINITY + static Status set_affinity_mask(id thread_id, uint64 mask); + + static uint64 get_affinity_mask(id thread_id); +#endif + private: MovableValue is_inited_; pthread_t thread_; diff --git a/tdutils/td/utils/port/detail/ThreadStl.h b/tdutils/td/utils/port/detail/ThreadStl.h index 474ef6ecc..b5313a36c 100644 --- a/tdutils/td/utils/port/detail/ThreadStl.h +++ b/tdutils/td/utils/port/detail/ThreadStl.h @@ -12,15 +12,23 @@ #include "td/utils/common.h" #include "td/utils/invoke.h" +#if TD_WINDOWS +#include "td/utils/port/detail/NativeFd.h" +#endif #include "td/utils/port/detail/ThreadIdGuard.h" #include "td/utils/port/thread_local.h" #include "td/utils/Slice.h" +#include "td/utils/Status.h" #include #include #include #include +#if TD_WINDOWS +#define TD_HAVE_THREAD_AFFINITY 1 +#endif + namespace td { namespace detail { @@ -65,12 +73,51 @@ class ThreadStl { return std::thread::hardware_concurrency(); } +#if TD_WINDOWS + using id = DWORD; +#else using id = std::thread::id; +#endif static void send_real_time_signal(id thread_id, int real_time_signal_number) { // not supported } +#if TD_HAVE_THREAD_AFFINITY + static Status set_affinity_mask(id thread_id, uint64 mask) { + if (static_cast(mask) != mask) { + return Status::Error("Invalid thread affinity mask specified"); + } + auto handle = OpenThread(THREAD_SET_LIMITED_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id); + if (handle == nullptr) { + return Status::Error("Failed to access thread"); + } + NativeFd thread_handle(handle); + if (SetThreadAffinityMask(thread_handle.fd(), static_cast(mask))) { + return Status::OK(); + } + return OS_ERROR("Failed to set thread affinity mask"); + } + + static uint64 get_affinity_mask(id thread_id) { + DWORD_PTR process_mask = 0; + DWORD_PTR system_mask = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, &system_mask)) { + auto handle = OpenThread(THREAD_SET_LIMITED_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id); + if (handle == nullptr) { + return 0; + } + NativeFd thread_handle(handle); + auto result = SetThreadAffinityMask(thread_handle.fd(), process_mask); + if (result != 0 && result != process_mask) { + SetThreadAffinityMask(thread_handle.fd(), result); + } + return result; + } + return 0; + } +#endif + private: std::thread thread_; @@ -81,7 +128,13 @@ class ThreadStl { }; namespace this_thread_stl { +#if TD_WINDOWS +inline ThreadStl::id get_id() { + return GetCurrentThreadId(); +} +#else using std::this_thread::get_id; +#endif } // namespace this_thread_stl } // namespace detail diff --git a/tdutils/td/utils/port/thread.h b/tdutils/td/utils/port/thread.h index e1b7b167f..35053a32f 100644 --- a/tdutils/td/utils/port/thread.h +++ b/tdutils/td/utils/port/thread.h @@ -22,9 +22,6 @@ namespace td { using thread = detail::ThreadStl; namespace this_thread = detail::this_thread_stl; #elif TD_THREAD_UNSUPPORTED - namespace this_thread { - inline void yield() {} - } #else #error "Thread's implementation is not defined" #endif diff --git a/tdutils/test/HashSet.cpp b/tdutils/test/HashSet.cpp index 07bc1ce81..cd577d69b 100644 --- a/tdutils/test/HashSet.cpp +++ b/tdutils/test/HashSet.cpp @@ -315,7 +315,7 @@ TEST(FlatHashMap, stress_test) { ASSERT_EQ(ref[key], tbl[key]); }); - add_step("reserve", 10, [&] { tbl.reserve(rnd() % max_table_size); }); + add_step("reserve", 10, [&] { tbl.reserve(static_cast(rnd() % max_table_size)); }); add_step("find", 1000, [&] { auto key = gen_key(); @@ -398,7 +398,7 @@ TEST(FlatHashSet, stress_test) { tbl.insert(key); }); - add_step("reserve", 10, [&] { tbl.reserve(rnd() % max_table_size); }); + add_step("reserve", 10, [&] { tbl.reserve(static_cast(rnd() % max_table_size)); }); add_step("find", 1000, [&] { auto key = gen_key(); diff --git a/tdutils/test/port.cpp b/tdutils/test/port.cpp index a7c673e18..b5c90fd75 100644 --- a/tdutils/test/port.cpp +++ b/tdutils/test/port.cpp @@ -284,3 +284,31 @@ TEST(Port, EventFdAndSignals) { } #endif #endif + +#if TD_HAVE_THREAD_AFFINITY +TEST(Port, ThreadAffinityMask) { + auto thread_id = td::this_thread::get_id(); + auto old_mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << "Initial thread " << thread_id << " affinity mask: " << old_mask; + for (size_t i = 0; i < 64; i++) { + auto mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << mask; + auto result = td::thread::set_affinity_mask(thread_id, static_cast(1) << i); + LOG(INFO) << i << ": " << result << ' ' << td::thread::get_affinity_mask(thread_id); + + if (i <= 1) { + td::thread thread([] { + auto thread_id = td::this_thread::get_id(); + auto mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << "New thread " << thread_id << " affinity mask: " << mask; + auto result = td::thread::set_affinity_mask(thread_id, 1); + LOG(INFO) << "Thread " << thread_id << ": " << result << ' ' << td::thread::get_affinity_mask(thread_id); + }); + } + } + auto result = td::thread::set_affinity_mask(thread_id, old_mask); + LOG(INFO) << result; + old_mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << old_mask; +} +#endif diff --git a/test/db.cpp b/test/db.cpp index b51a8cf20..61757e989 100644 --- a/test/db.cpp +++ b/test/db.cpp @@ -684,8 +684,7 @@ TEST(DB, persistent_key_value) { int ref_cnt_; }; - td::ConcurrentScheduler sched; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); sched.create_actor_unsafe
(0, "Main", threads_n, &queries, &res).release(); sched.start(); while (sched.run_main(10)) { diff --git a/test/link.cpp b/test/link.cpp index eab877481..256c57bf8 100644 --- a/test/link.cpp +++ b/test/link.cpp @@ -27,7 +27,8 @@ static void check_find_urls(const td::string &url, bool is_valid) { { if (is_valid && (td::begins_with(url_lower, "http") || td::begins_with(url_lower, "t.me")) && - url.find('.') != td::string::npos && url.find(' ') == td::string::npos && url != "http://..") { + url.find('.') != td::string::npos && url.find(' ') == td::string::npos && url != "http://.." && + url.find("ra.ph") == td::string::npos && url.find("Aph") == td::string::npos) { auto urls = td::find_urls(url); ASSERT_EQ(1u, urls.size()); ASSERT_STREQ(url, urls[0].first); @@ -86,8 +87,9 @@ static void parse_internal_link(const td::string &url, td::td_api::object_ptrget_id() == td::td_api::internalLinkTypeMessageDraft::ID) { static_cast(object.get())->text_->entities_.clear(); } - ASSERT_STREQ(url + " " + to_string(expected), url + " " + to_string(object)); + ASSERT_STREQ(url + ' ' + to_string(expected), url + ' ' + to_string(object)); } else { + LOG_IF(ERROR, expected != nullptr) << url; ASSERT_TRUE(expected == nullptr); } @@ -155,6 +157,9 @@ TEST(Link, parse_internal_link) { auto game = [](const td::string &bot_username, const td::string &game_short_name) { return td::td_api::make_object(bot_username, game_short_name); }; + auto instant_view = [](const td::string &url) { + return td::td_api::make_object(url); + }; auto invoice = [](const td::string &invoice_name) { return td::td_api::make_object(invoice_name); }; @@ -638,6 +643,20 @@ TEST(Link, parse_internal_link) { parse_internal_link("tg:setlanguage?lang=abc%30ef", language_pack("abc0ef")); parse_internal_link("tg://setlanguage?lang=", unknown_deep_link("tg://setlanguage?lang=")); + parse_internal_link("http://telegram.dog/iv?url=https://telegram.org&rhash=abcdef&test=1&tg_rhash=1", + instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash=abcdef")); + parse_internal_link("t.me/iva?url=https://telegram.org&rhash=abcdef", public_chat("iva")); + parse_internal_link("t.me/iv?url=&rhash=abcdef", nullptr); + parse_internal_link("t.me/iv?url=https://telegram.org&rhash=", + instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash")); + parse_internal_link("t.me/iv//////?url=https://telegram.org&rhash=", + instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash")); + parse_internal_link("t.me/iv/////1/?url=https://telegram.org&rhash=", nullptr); + parse_internal_link("t.me/iv", nullptr); + parse_internal_link("t.me/iv?#url=https://telegram.org&rhash=abcdef", nullptr); + parse_internal_link("tg:iv?url=https://telegram.org&rhash=abcdef", + unknown_deep_link("tg://iv?url=https://telegram.org&rhash=abcdef")); + parse_internal_link("t.me/addtheme?slug=abcdef", nullptr); parse_internal_link("t.me/addtheme", nullptr); parse_internal_link("t.me/addtheme/", nullptr); @@ -921,4 +940,35 @@ TEST(Link, parse_internal_link) { parse_internal_link("tg://settings/filters", settings()); parse_internal_link("tg://settings/language", language_settings()); parse_internal_link("tg://settings/privacy", privacy_and_security_settings()); + + parse_internal_link("username.t.me////0/a//s/as?start=", bot_start("username", "")); + parse_internal_link("username.t.me?start=as", bot_start("username", "as")); + parse_internal_link("username.t.me", public_chat("username")); + parse_internal_link("aAAb.t.me/12345?single", message("tg:resolve?domain=aaab&post=12345&single")); + parse_internal_link("telegram.t.me/195", message("tg:resolve?domain=telegram&post=195")); + parse_internal_link("shares.t.me", public_chat("shares")); + + parse_internal_link("c.t.me/12345?single", nullptr); + parse_internal_link("aaa.t.me/12345?single", nullptr); + parse_internal_link("aaa_.t.me/12345?single", nullptr); + parse_internal_link("0aaa.t.me/12345?single", nullptr); + parse_internal_link("_aaa.t.me/12345?single", nullptr); + parse_internal_link("addemoji.t.me", nullptr); + parse_internal_link("addstickers.t.me", nullptr); + parse_internal_link("addtheme.t.me", nullptr); + parse_internal_link("auth.t.me", nullptr); + parse_internal_link("confirmphone.t.me", nullptr); + parse_internal_link("invoice.t.me", nullptr); + parse_internal_link("joinchat.t.me", nullptr); + parse_internal_link("login.t.me", nullptr); + parse_internal_link("proxy.t.me", nullptr); + parse_internal_link("setlanguage.t.me", nullptr); + parse_internal_link("share.t.me", nullptr); + parse_internal_link("socks.t.me", nullptr); + + parse_internal_link("www.telegra.ph/", nullptr); + parse_internal_link("www.telegrA.ph/#", nullptr); + parse_internal_link("www.telegrA.ph/?", instant_view("https://telegra.ph/?")); + parse_internal_link("http://te.leGra.ph/?", instant_view("https://telegra.ph/?")); + parse_internal_link("https://grAph.org/12345", instant_view("https://telegra.ph/12345")); } diff --git a/test/mtproto.cpp b/test/mtproto.cpp index f15c5ca10..89f5441ab 100644 --- a/test/mtproto.cpp +++ b/test/mtproto.cpp @@ -47,9 +47,8 @@ #include "td/utils/Time.h" TEST(Mtproto, GetHostByNameActor) { - td::ConcurrentScheduler sched; int threads_n = 1; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); int cnt = 1; td::vector> actors; @@ -139,9 +138,8 @@ TEST(Time, parse_http_date) { } TEST(Mtproto, config) { - td::ConcurrentScheduler sched; int threads_n = 0; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); int cnt = 1; { @@ -273,7 +271,6 @@ class Mtproto_ping final : public td::Test { using Test::Test; bool step() final { if (!is_inited_) { - sched_.init(0); sched_.create_actor_unsafe(0, "Pinger", get_default_ip_address(), &result_).release(); sched_.start(); is_inited_ = true; @@ -292,7 +289,7 @@ class Mtproto_ping final : public td::Test { private: bool is_inited_ = false; - td::ConcurrentScheduler sched_; + td::ConcurrentScheduler sched_{0, 0}; td::Status result_; }; td::RegisterTest mtproto_ping("Mtproto_ping"); @@ -416,7 +413,6 @@ class Mtproto_handshake final : public td::Test { using Test::Test; bool step() final { if (!is_inited_) { - sched_.init(0); sched_.create_actor_unsafe(0, "HandshakeTestActor", get_default_dc_id(), &result_).release(); sched_.start(); is_inited_ = true; @@ -435,7 +431,7 @@ class Mtproto_handshake final : public td::Test { private: bool is_inited_ = false; - td::ConcurrentScheduler sched_; + td::ConcurrentScheduler sched_{0, 0}; td::Status result_; }; td::RegisterTest mtproto_handshake("Mtproto_handshake"); @@ -484,9 +480,8 @@ class Socks5TestActor final : public td::Actor { TEST(Mtproto, socks5) { return; - td::ConcurrentScheduler sched; int threads_n = 0; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); sched.create_actor_unsafe(0, "Socks5TestActor").release(); sched.start(); @@ -640,7 +635,6 @@ class Mtproto_FastPing final : public td::Test { using Test::Test; bool step() final { if (!is_inited_) { - sched_.init(0); sched_.create_actor_unsafe(0, "FastPingTestActor", &result_).release(); sched_.start(); is_inited_ = true; @@ -659,7 +653,7 @@ class Mtproto_FastPing final : public td::Test { private: bool is_inited_ = false; - td::ConcurrentScheduler sched_; + td::ConcurrentScheduler sched_{0, 0}; td::Status result_; }; td::RegisterTest mtproto_fastping("Mtproto_FastPing"); @@ -676,9 +670,8 @@ TEST(Mtproto, Grease) { } TEST(Mtproto, TlsTransport) { - td::ConcurrentScheduler sched; int threads_n = 1; - sched.init(threads_n); + td::ConcurrentScheduler sched(threads_n, 0); { auto guard = sched.get_main_guard(); class RunTest final : public td::Actor { diff --git a/test/online.cpp b/test/online.cpp index db6832886..6bcdcec83 100644 --- a/test/online.cpp +++ b/test/online.cpp @@ -230,27 +230,23 @@ class InitTask : public Task { start_flag_ = true; td::tl_object_ptr function; switch (authorization_state->get_id()) { - case td::td_api::authorizationStateWaitEncryptionKey::ID: - send(td::make_tl_object()); - break; case td::td_api::authorizationStateReady::ID: promise_.set_value({}); stop(); break; case td::td_api::authorizationStateWaitTdlibParameters::ID: { - auto parameters = td::td_api::make_object(); - parameters->use_test_dc_ = true; - parameters->database_directory_ = options_.name + TD_DIR_SLASH; - parameters->use_message_database_ = true; - parameters->use_secret_chats_ = true; - parameters->api_id_ = options_.api_id; - parameters->api_hash_ = options_.api_hash; - parameters->system_language_code_ = "en"; - parameters->device_model_ = "Desktop"; - parameters->application_version_ = "tdclient-test"; - parameters->ignore_file_names_ = false; - parameters->enable_storage_optimizer_ = true; - send(td::td_api::make_object(std::move(parameters))); + auto request = td::td_api::make_object(); + request->use_test_dc_ = true; + request->database_directory_ = options_.name + TD_DIR_SLASH; + request->use_message_database_ = true; + request->use_secret_chats_ = true; + request->api_id_ = options_.api_id; + request->api_hash_ = options_.api_hash; + request->system_language_code_ = "en"; + request->device_model_ = "Desktop"; + request->application_version_ = "tdclient-test"; + request->enable_storage_optimizer_ = true; + send(std::move(request)); break; } default: @@ -621,8 +617,7 @@ int main(int argc, char **argv) { } SET_VERBOSITY_LEVEL(new_verbosity_level); - td::ConcurrentScheduler sched; - sched.init(4); + td::ConcurrentScheduler sched(4, 0); sched.create_actor_unsafe(0, "TestTd", std::move(test_options)).release(); sched.start(); while (sched.run_main(10)) { diff --git a/test/secret.cpp b/test/secret.cpp index 3c1d351ea..5a602fe85 100644 --- a/test/secret.cpp +++ b/test/secret.cpp @@ -999,9 +999,7 @@ void FakeSecretChatContext::on_read_message(int64, Promise<> promise) { TEST(Secret, go) { return; - ConcurrentScheduler sched; - int threads_n = 0; - sched.init(threads_n); + ConcurrentScheduler sched(0, 0); Status result; sched.create_actor_unsafe(0, "HandshakeTestActor", &result).release(); diff --git a/test/tdclient.cpp b/test/tdclient.cpp index 81cb2aad4..ca95fa4d8 100644 --- a/test/tdclient.cpp +++ b/test/tdclient.cpp @@ -211,12 +211,16 @@ class DoAuthentication final : public TestClinetTask { start_flag_ = true; td::tl_object_ptr function; switch (authorization_state->get_id()) { - case td::td_api::authorizationStateWaitEncryptionKey::ID: - function = td::make_tl_object(); - break; case td::td_api::authorizationStateWaitPhoneNumber::ID: function = td::make_tl_object(phone_, nullptr); break; + case td::td_api::authorizationStateWaitEmailAddress::ID: + function = td::make_tl_object("alice_test@gmail.com"); + break; + case td::td_api::authorizationStateWaitEmailCode::ID: + function = td::make_tl_object( + td::make_tl_object(code_)); + break; case td::td_api::authorizationStateWaitCode::ID: function = td::make_tl_object(code_); break; @@ -224,19 +228,18 @@ class DoAuthentication final : public TestClinetTask { function = td::make_tl_object(name_, ""); break; case td::td_api::authorizationStateWaitTdlibParameters::ID: { - auto parameters = td::td_api::make_object(); - parameters->use_test_dc_ = true; - parameters->database_directory_ = name_ + TD_DIR_SLASH; - parameters->use_message_database_ = true; - parameters->use_secret_chats_ = true; - parameters->api_id_ = 94575; - parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2"; - parameters->system_language_code_ = "en"; - parameters->device_model_ = "Desktop"; - parameters->application_version_ = "tdclient-test"; - parameters->ignore_file_names_ = false; - parameters->enable_storage_optimizer_ = true; - function = td::td_api::make_object(std::move(parameters)); + auto request = td::td_api::make_object(); + request->use_test_dc_ = true; + request->database_directory_ = name_ + TD_DIR_SLASH; + request->use_message_database_ = true; + request->use_secret_chats_ = true; + request->api_id_ = 94575; + request->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2"; + request->system_language_code_ = "en"; + request->device_model_ = "Desktop"; + request->application_version_ = "tdclient-test"; + request->enable_storage_optimizer_ = true; + function = std::move(request); break; } case td::td_api::authorizationStateReady::ID: @@ -829,7 +832,6 @@ class Tdclient_login final : public td::Test { using Test::Test; bool step() final { if (!is_inited_) { - sched_.init(4); sched_.create_actor_unsafe(0, "LoginTestActor", &result_).release(); sched_.start(); is_inited_ = true; @@ -849,7 +851,7 @@ class Tdclient_login final : public td::Test { private: bool is_inited_ = false; - td::ConcurrentScheduler sched_; + td::ConcurrentScheduler sched_{4, 0}; td::Status result_; }; //RegisterTest Tdclient_login("Tdclient_login");